Commit Graph
149 Commits
Author SHA1 Message Date
chenos 693e43d86b ci(skills): distribute v3 skills on source updates (#10769)
* ci(skills): distribute v3 skills on source updates

* refactor: rename app plugin development skill
2026-10-12 11:17:39 +08:00
Charles Wu 5382b6c3ca docs: translate capability illustrations into English (#10768) 2026-10-12 10:37:34 +08:00
Junyi 49ab345855 docs(capabilities): expand approval and workflow business guidance (#10743)
* docs(approval): complete bilingual guides and refine workflow tutorials

* docs(capabilities): consolidate approval and workflow guidance
2026-10-11 23:22:36 +08:00
Charles Wu 2f209092c7 docs: promote illustrated capabilities as the sole chapter (#10754) 2026-10-11 22:40:17 +08:00
Charles Wu 810e0de627 docs: complete AI employee and knowledge capabilities (#10752) 2026-10-11 22:03:07 +08:00
Charles Wu 7b3d6cfe0f docs: add illustrated capabilities chapter (#10751) 2026-10-11 21:49:00 +08:00
Charles Wu c6ccf90733 docs: sharpen capability screenshots and add AI guides (#10749) 2026-10-11 20:09:30 +08:00
Charles Wu 159dcfbc79 docs: improve quick start screenshot resolution (#10747) 2026-10-11 19:32:42 +08:00
Charles Wu 54bbb6b6fa docs: promote refreshed capabilities and remove Studio from site (#10746) 2026-10-11 19:10:37 +08:00
Charles Wu bdfbeb010e docs: 完善能力2工作流、审批和邮件示例及配图 (#10744)
* docs: clarify authentication order and external data source example

* docs: complete workflow approval and mail capability examples
2026-10-11 18:50:49 +08:00
Charles Wu bf64690927 docs: add Northstar capabilities walkthrough and update Studio flow (#10741)
* docs: add Northstar capabilities walkthrough and update Studio flow

* docs: show authorization results and simplify workflow guidance

* docs: align quick-start prompt with the demonstrated business pages

* docs: link account and session management from authentication overview

* docs: use current blue sign-in screenshot in authentication overview

* docs: align sign-in page reference with updated overview

* docs: present password sign-in before login customization

* docs: explain business needs before capability prompts

* docs: describe general capability needs and emphasize example outcomes

* docs: remove duplicate account result emphasis
2026-10-11 17:21:32 +08:00
Junyi 8de421ab4b docs(workflow): add process diagrams and invoicing walkthroughs (#10727)
* docs(workflow): add process diagrams and invoicing walkthroughs

* docs(approval): remove duplicate overview routes
2026-10-11 11:04:29 +08:00
Charles Wu 186921ed2f docs(studio): document project setup and Preview deployment (#10729) 2026-10-11 10:05:51 +08:00
Charles Wu 4448fa272f docs(studio): document GitHub connection setup and registration blocker (#10712) 2026-10-10 23:51:26 +08:00
Charles Wu e5708e659c docs: clarify Studio initialization blockers and workarounds (#10708) 2026-10-10 23:09:28 +08:00
Charles Wu 1a1cf30fb8 docs: add Studio onboarding and first project walkthrough (#10659) 2026-10-10 16:36:53 +08:00
Junyi 98e79a5a65 feat(lifecycle): durable examples (#10644)
* docs(lifecycle): add examples and docs

* refactor(workflow): prune workflow

* docs(workflow): prune workflow
2026-10-10 14:50:27 +08:00
PiEgg c796cb909b feat(app-client)!: remove settings and dev route surfaces, Hub and AI employee (#10642) 2026-10-10 02:02:13 -04:00
PiEgg eb92050dd3 fix(docs): temporarily hide Ask AI button (#10638) 2026-10-09 21:32:05 -04:00
Charles Wu 64c0cdb5d7 docs: remove tutorials and deployment from top navigation (#10637) 2026-10-10 09:14:45 +08:00
Charles Wu 049cd58f15 docs: explain mail with complete workflows and screenshots (#10636) 2026-10-10 09:13:39 +08:00
Charles Wu 990021e2c2 docs: explain scheduled tasks with an approval reminder example (#10635) 2026-10-10 08:51:24 +08:00
Charles Wu 2edd6d0974 docs: improve capability guides with practical examples (#10625)
* docs: improve capability guides with practical examples

* docs: fix screenshot paths and template download links
2026-10-09 22:02:38 +08:00
Junyi 1408643095 refactor: disable Workflow by default and unpublish its documentation (#10603)
* refactor: disable workflow in default application templates

* docs(scheduler): clarify optional workflow integration

* test(workflow): isolate CLI plugin registration

* test(examples): defer flaky waiting-task focus assertion

* test(workflow): pause capability test suites

* fix(templates): synchronize workflow test exclusions
2026-10-09 19:54:02 +08:00
Charles Wu d0d3f07e63 docs: remove development and plugins from top navigation (#10617) 2026-10-09 19:46:51 +08:00
Charles Wu 37a954b5ae docs: restructure authentication guides with practical examples (#10616) 2026-10-09 19:36:25 +08:00
Charles Wu 09ab9fbe06 docs: simplify capability overview and rewrite notification guide (#10605) 2026-10-09 18:47:39 +08:00
Drol d86a77c746 chore(v3): deprecate AI employee plugin in application templates (#10599)
* chore: deprecate AI employee plugin in application templates

* fix(v3): wait for enabled controls in workflow and mail flows
2026-10-09 06:29:53 -04:00
PiEgg a695d1987f chore(v3): use public npm and unify workflow naming (#10588) 2026-10-09 03:33:32 -04:00
YANG QIA bc1e83ffac feat(plugins): update the plugins, templates and UI Library for the new runtime (#553)
* Update the plugins, templates and UI Library for the new runtime

Device authorization, scoped API keys, service accounts, user invitations and preferences, secrets-backed credentials, the inbox and the cn package across the existing plugins, examples and the three templates, with the UI Library's auth, inbox, authorization and component items.

* Declare the UI Library site's favicon

* Use the NocoBase favicon on the UI Library site

* Fix generated-app shell tests and the Hub's user deletion test

Inline the authentication client in the templates' Vitest config so its guards share the test's react-router, and update the Hub test to the users plugin's new copy and spinner label.
2026-10-08 01:10:33 +08:00
YANG QIA 37c8d20aa6 feat(app): add the runtime, CLI client and agent runner for agent-run applications (#552)
Encrypted secrets, a command manifest derived from the API document, environment-variable manifests, sample data and on-demand Apps in the runtime, plus the new agent protocol, application CLI client, agent runner and Docker App Host packages.
2026-10-07 23:54:20 +08:00
Drol d631536271 feat(app-plugin-ai-employee): grant AI settings per page under System management, and drop unused management routes (#548)
Replace the single `page:ai.settings` grant with seven settings items under
System management → AI, one per settings page: `ai.employees`,
`ai.llmServices` and `ai.mcpServers` with `read` and `manage`, and
`ai.skills`, `ai.tools`, `ai.usage` and `ai.conversations` with `read`.
Every management route names the permissions it accepts, pages open with
`read` and turn their controls off without `manage`, and previewing another
user's AI file requires `read` on `ai.conversations`.

A migration gives every item and action to each Permission Set that held the
page grant. A grant of every page no longer opens the AI settings.

Remove the management routes no settings page called: employee templates,
create and delete, skill and tool writes, single LLM service and MCP server
reads, and both MCP connection tests. The package moves to the 3.0.0
prerelease line.
2026-10-07 09:53:24 -04:00
PiEgg 0b933b39c3 feat: generate the OpenAPI document and serve Swagger UI (#533)
Generates an OpenAPI 3.1 document for every application at GET /api/swagger and serves Swagger UI at GET /api/swagger/docs to signed-in users and API keys. Every hand-written /api route is declared or explicitly hidden, data endpoints are documented per field, Better Auth's endpoints are merged, routes behind runtime dispatchers are registered generically, and pnpm openapi:check enforces the declarations in CI. All changed packages carry changesets.
2026-10-05 09:23:03 +08:00
YANG QIA 7e5b7d4e72 refactor(templates): build the sidebars from shadcn Sidebar primitives (#534)
* refactor(templates): build the sidebars from shadcn Sidebar primitives

Replace the hand-rolled LayoutSidebar and NavigationTree in the default,
examples and hub templates with shadcn's Sidebar, keeping the collapsed
menu behaviour from #317: zero-delay tooltips, hover and keyboard-focus
popovers with permission-filtered children, nested expansion, parent
navigation and the selected group's highlight.

components/ui/sidebar.tsx stays as the shadcn CLI writes it. The layouts
customize it from outside: widths through the provider style in spacing
units, a phone Sheet with a translated title rendered by app-sidebar.tsx,
and a document keydown listener that keeps Ctrl/Cmd+B from reaching the
provider's window listener.

Claude-Session: https://claude.ai/code/session_01KLvKwu4oUSHnFgGAy3M4v2

* fix(templates): tidy the sidebar footer and add the edge rail

Space the footer like the menu above it: the shield aligned to the first
line, the slogan's two lines, then the name and version on their own
truncated lines in the body font. In icon mode only the shield remains,
with a tooltip carrying the same text.

Add shadcn's SidebarRail with a translated label and title, passed as
props that override the primitive's built-in "Toggle Sidebar".

Claude-Session: https://claude.ai/code/session_01KLvKwu4oUSHnFgGAy3M4v2

* refactor(ui-library): split auth-ui into presentational auth components

Replace the auth-ui block with four presentational registry components built
from shadcn primitives: auth-forms (Field, InputGroup, Alert), auth-methods
(Tabs, FieldSeparator, Button), auth-centered-layout and auth-split-layout.
They call no plugin and take every string as a prop with an English default.

The templates' sign-in, sign-up and password pages become application code
that wires the forms to the authentication plugin's headless actions,
translates them from client/locales, and keeps the NocoBase brand panel as
the split layout's aside. The ui-library preview loads shadcn/tailwind.css
and the shadcn Label so the data-attribute variants render.

Claude-Session: https://claude.ai/code/session_01KLvKwu4oUSHnFgGAy3M4v2

* fix(ui-library): keep auth demo links inside their demo

Claude-Session: https://claude.ai/code/session_01KLvKwu4oUSHnFgGAy3M4v2

* refactor(templates): install the auth registry blocks under client/extensions

Claude-Session: https://claude.ai/code/session_01KLvKwu4oUSHnFgGAy3M4v2
2026-10-05 04:54:36 +08:00
PiEgg 21d274cc71 refactor: move every existing /api route onto the HTTP API specification (#532)
Moves every /api route in the repository onto the HTTP API specification: camelCase plugin namespaces, standard methods and slash-separated custom methods, { data } / { data, meta } responses, the standard error body with reason and domain, permission before validation, side-effect-free GETs, Repository errors that declare their own status, save-time validation of write grants, duplicate route detection at start, and optional global API limits in config.yml. All changed packages carry changesets.
2026-10-04 16:39:20 +08:00
chenos e44f49cc5e feat(hub-cli)!: remotes, hub auth, build for the Hub, releases/status and resumable uploads (#498)
* feat(hub-cli)!: deploy to remotes, save keys with hub auth, build for the Hub

hub-cli keeps the Apps an application deploys to as named remotes in the
committed .nocobase/hub.json (`hub remote add/list/remove`), saves one API
key per remote in the user's configuration directory (`hub auth
login/logout/status`), and no longer reads HUB_URL, HUB_APP_ID, HUB_API_KEY,
the App root .env or the --hub/--app-id/--api-key flags. Every command takes
--remote.

`hub deploy` and `hub upload` ask the Hub which platform its Host runs Apps
on and run `nocobase build --target … --node-version … --tar` for it before
uploading; --no-build and --file upload an existing archive after checking
its recorded target. `hub deploy` uploads and then deploys through the
deploy endpoint, so an archive the Hub already has is deployed as its
existing Release.

The Hub's upload route only uploads: the configuration-prefixed body and the
deployment-intent and wait headers are gone. Uploads are checked against the
Host's build target, the App detail reports it, and which routes a
publishing key may call is declared with each route. The Host reports its
runtime platform in its status.

* feat(hub-cli): add hub releases and hub status

`hub releases` lists an App's Releases newest first with the platform each
archive targets, whether it runs now and whether it ever deployed, and
`hub status` reports the platform the Hub builds for, what the App runs and
its last deployment. Finding the Release to roll back to no longer needs the
Hub's web page.

The Hub opens its Release list, single Release and deployment list to
publishing keys holding either scope. The Release list takes a `limit`, each
Release reports `buildTarget`, `running` and `everDeployed`, computed from
existing records, and deployment list items carry `finishedAt`.

* feat(hub-cli): upload archives resumably

hub-cli sends the archive through the Hub's resumable upload: a session for
the archive's size and checksum, chunks in order at the offset the Hub has
reached, then completion. A chunk whose answer is lost is resent from the
offset the Hub reports, a session left unfinished by an earlier run is
resumed, and an archive the Hub already has is not sent again. Archives may
be up to 2 GiB, and a reverse proxy only needs to allow one 8 MiB chunk.

The Hub stages sessions on local disk under a new `uploadsDir` option,
which the Hub template sets to storage/hub/uploads, expires them 24 hours
after their last chunk, and creates the Release on completion through the
same checks as the single upload, which the management console keeps using.

* fix(hub-cli): name the command to run when no remote is configured

* feat(app-cli): let a CLI plugin describe its nested topics

`defineCliPlugin` accepts `topics`, a one-line description for each topic
nested under the plugin's own. Help showed one of a nested topic's commands'
summaries in its place, so `hub remote` read "Add a Hub App to deploy this
application to." A topic is described only where a command under it is
registered, and a described topic with no command under it is rejected.
hub-cli describes `hub remote` and `hub auth`.

* fix(hub-cli): address review findings on remotes, retries and deadlines

- Stop create-app from ignoring .nocobase/ in a generated application's
  .gitignore, so .nocobase/hub.json is committed as documented, and warn
  from hub remote add when an existing checkout still ignores it.
- Count a failed offset read as one of the consecutive chunk failures
  instead of ending the upload, and go on from the offset an
  UPLOAD_OFFSET_MISMATCH reports without a second request.
- Give each Hub request its own deadline, report one that outlasts it as
  TIMEOUT, and bound the wait for a deployment separately, so --timeout
  no longer caps a whole resumable upload.
- Report a key as rejected in hub auth status only when the Hub rejects
  it; anything else leaves it unchecked.
- Look remotes up as own properties, so a name like constructor is not a
  remote, and reject a default that is not a remote name.
- Settle the hidden prompt when standard input ends or errors.
- Stage upload sessions one directory per App, scan only the App's own
  sessions when an upload starts, and sweep every App's expired sessions
  at most once an hour.
- Drop the unreachable not-found branch from getReleaseSummary.

* fix(hub-cli): roll back under the default key, name a missing Hub, cancel login cleanly

- `hub deploy --release-id` of a Release the App deployed before and has since
  moved on from was answered with that earlier deployment, so the documented
  rollback deployed nothing and exited 0. A reused deployment that is not the
  App's latest now leads to the next key, derived from it; a retry walks the
  same keys and still repeats nothing. A given --idempotency-key stays exact.
- A 404 without a Hub error code, as at a mistyped mount path, fails with
  `HUB_NOT_FOUND` and suggests `hub remote list` instead of a bare
  `HUB_REQUEST_FAILED` or an "unknown result".
- Ctrl-C at the `hub auth login` prompt reports `LOGIN_CANCELLED` (exit 130)
  rather than `LOGIN_FAILED`.
2026-10-01 08:49:24 -04:00
Drol e77641ba3c feat(queue): rebuild @nocobase/queue on BullMQ 6.3.6 (#516)
* feat(queue): rebuild @nocobase/queue on BullMQ 6.3.6

Replace the @boringnode/queue wrapper with a per-application QueueService
whose producers, consumers and managers are bound to named queues. A
queueBackend of redis (or a registered BullMQ backend factory) runs on
BullMQ's public Queue and Worker APIs under a hash-tagged prefix; inMemory
runs in the process and persists unfinished jobs to a state file at
shutdown. Both implementations share one consumer layer and one contract
test suite.

- app-server: QueueServiceProvider, queueServiceToken and AppQueueConfig
  replace QueueProvider and its tokens; the queue migration source and
  planAppRuntimeDatabaseTasks are removed; queue.jobs is deprecated,
  ignored and reported once per plugin
- templates: compose QueueServiceProvider, declare memory and redis keys,
  drop the @/jobs alias
- create-plugin: remove the server.jobs capability
- examples: rewrite the queue example on the new API
- docs, Skills and AGENTS.md describe the new contract

* docs(jobs): make JobExecutor the default for background work

The previous commit rewrote every Queue Job reference onto the new queue
API, but most of what those jobs did belongs to @nocobase/jobs now.

- create-plugin: server.jobs generates a JobExecutor job and the provider
  that owns the plugin's executor instead of being removed
- plugin and application development Skills: choose JobExecutor for
  one-off work, ScheduleExecutor for recurring work, Scheduler when
  administrators track it, and a queue only for delays, priorities,
  deduplicating IDs, batches, rate limits and fan-out; jobs receive
  services by closure
- Scheduler Skill, evals and docs: hand a target's lengthy work to a
  JobExecutor with the occurrence's execution record as the reference,
  and let the job decide its terminal outcome; describe Scheduler's own
  jobs backend instead of the removed queue connection
- deployment and Notification Skills: cover the jobs and queue backends
  instead of a queue manager

* refactor(queue): select the implementation with adapter

queueBackend used to pick both the implementation and a BullMQ backend
factory. A configuration now selects the implementation with
`adapter: 'inMemory' | 'redis'`, and the optional `queueBackend` only
names a factory registered with `registerBackend()` for the redis
adapter, defaulting to BullMQ's Redis backend. A registered factory
interprets its own connection, so `connection` is required only on the
Redis backend. `inMemory` is no longer a reserved factory name.
2026-09-30 11:46:47 -04:00
Zeke ZhangandClaude Opus 5.5 7534fb6b99 refactor(templates): add shadcn/ui primitives on demand with an embedded shadcn/ui skill (#464)
* docs(skills): route frontend work through an embedded shadcn/ui skill

The application development Skill now carries the shadcn/ui skill from
shadcn 4.21.0, unchanged, under references/frontend/shadcn/, and
references/frontend/references/shadcn.md says what to read there and
where an application departs from it: `pnpm exec shadcn`, no `apply` or
`--preset`, overlays as child routes, labels above inputs, and the
English built into a few primitives translated.

- Primitives are added on demand with `yes n | pnpm exec shadcn add`,
  which keeps installed primitives from being overwritten and lets a
  non-interactive run finish, then formatted with Prettier.
- styling.md, form.md and the other topic references link to the skill
  instead of restating it; the worked example names the primitives each
  file needs on an **Add first** line.
- The frontend handbook rework: the projects worked example split into
  one document per file, shell.md, the capture tool guide and the
  reviewer prompts.
- Both .prettierignore files skip the vendored skill so a shadcn upgrade
  can replace it wholesale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(app-template-default): ship only the primitives the shell uses

Primitives are added with the shadcn CLI when a page first needs one,
so every application, template and plugin starts from the same CLI
output. The shadcn/ui skill embedded in the development Skill replaces
the reference gallery.

- Remove client/pages/reference/ with its tests, the 49 primitives that
  neither the shell nor the shipped compositions import, and
  use-mobile.ts. The template keeps button, calendar, dialog,
  dropdown-menu, input, label, popover, select, spinner, table, toast
  and tooltip.
- Drop the devDependencies only those primitives used (recharts, cmdk,
  embla-carousel-react, input-otp, react-resizable-panels,
  @shadcn/react) and add cn, which registry primitives now import.
- toast.tsx names its close button through actions.close, as dialog.tsx
  and spinner.tsx translate theirs; primitive-labels.test.tsx fails
  when an update brings the registry's English back.
- DataTable takes showSelectedCount, so a table without row selection
  hides "0 of N row(s) selected".
- Add the page test harness and application locale coverage tests; the
  client routes test pins page grants by their authz resource id.
- AGENTS.md and CLAUDE.md send UI work to ui-workflow.md, list the
  shipped primitives, and describe adding the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(templates): ship only the primitives Hub and Examples use

Hub and Examples follow Default: primitives are added with the shadcn
CLI when a page first needs one, and the shadcn/ui skill embedded in the
development Skill replaces the reference gallery.

- Remove client/pages/reference/ with its tests and use-mobile.ts from
  both templates. Hub keeps the twelve primitives its shell and
  compositions use; Examples also keeps badge, card, field, separator,
  skeleton, textarea, toggle and toggle-group for its example pages.
- Drop recharts, cmdk, embla-carousel-react, input-otp,
  react-resizable-panels and @shadcn/react, which no template imports
  now, and add cn, which registry primitives import.
- Carry over Default's toast close label, DataTable showSelectedCount,
  the page harness, locale coverage and primitive label tests, and the
  AGENTS.md and CLAUDE.md guidance, keeping each template's own route
  grants and wording.
- Add the changeset for all three templates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): name the primitives each template ships

The frontend handbook said the template ships twelve primitives, which
holds for Default and Hub but not for Examples. shadcn.md now lists the
eight Examples adds, and frontend-dev.md and example.md refer to the
primitives the shell uses instead of a count. Add the changeset for the
embedded shadcn/ui skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): open records in place, add the back button and the dashboard template

A review of a customer-service application built from the handbook found records opening over the wrong page,
a row's Edit opening the drawer as well, a back button among the page actions, an unaligned table in a card and
unsortable date columns. The handbook now says a record opens over the page the user is on (I9), declares the
drawer under every page that opens it, gives a row's Edit its own route, puts BackButton above the title (L6),
makes date and number columns sortable (T1.8), and adds a dashboard template (T5) with a worked example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(templates): add BackButton and keep the page count on one line

BackButton is the way back from a page below another one: a link above the heading that leads to the parent
route with the query string and replaces the history entry, as closing a route overlay does. It stands on its
own, without PageHeader. DataTablePagination gives the page count a minimum width, so the Chinese label no
longer wraps. RouteChildPage and the UI Library describe leaving by the back link instead of breadcrumbs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(skills): show the success path in the unsaved-changes example

A generated application hit this exact bug: the edit dialog's save
cleared neither the dirty flag that beforeClose reads nor the form's
report, so closing after a successful save asked to discard the just
saved changes.

The overlay.md section 2.5 example now carries the whole flow: a stable
onDirtyChange callback, an EditProjectBody whose onSubmitted clears the
flag before onSaved and close, and the imports that go with it. The
frontend handbook's Common mistakes list gains the matching entry.

* fix(templates): keep the Switch and pie chart in proportion in the compact theme

* docs(app-skills): compare the whole design at acceptance and require consistent navigation icons

The acceptance review's D classification was scoped to the design's numbered
criteria, so a design's structural declarations — routes, navigation entries and
their icons, permissions, the component list and states — were only compared
with the code when they happened to be criteria. A trial feature lost two menu
icons this way and the acceptance pass did not catch it.

D now compares the whole design file with the code; the design template requires
every declaration in sections 2–4 to map to a criterion or be marked explicitly
as not a criterion; and the review checklist plus page.md require navigation
icons to be consistent within a group.

* docs(app-skills): open a record's detail under the page that opens it

I9 covered drawers and dialogs only, so a page that referenced a record whose
detail is a page (a payment list's expense number) linked into the other
module's route: the page the user was on disappeared, the menu highlight moved,
and Back landed elsewhere. The page version of "the same drawer over another
page" was undocumented, and page.md told every record page to hard-code `to`,
which blocks reusing one page module under several parents.

I9 now covers a covering child page as well, child-routes.md gains "The same
detail page over another page", page.md limits `to` to a page with one parent,
and the common-mistakes entry names the detail page next to the overlay URL.

* fix(templates): make BackButton a muted text link

* refactor(templates): group the DataTable components in one directory

* docs(app-skills): show a cut-off table cell's full content on hover

Guideline T1.11 requires a table cell that is too narrow for its content to show the full content on hover. table.md gives the pattern: the cut-off content in a Popover whose trigger opens on hover, since a Tooltip does not work well here. A cell does not wrap and the table widens to fit it, so the pattern applies only where a column limits its width.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): treat the worked example as a reference, not code to copy

Agents copied the projects example verbatim, carrying its names, fields and fixed values into features they did not fit. The frontend handbook now says the examples show the rules rather than the feature to build, and names the projects-domain values to decide again. example.md and styling.md no longer tell an agent to copy the files a task depends on; only session-expired-alert.tsx and use-url-search.ts, which are shared infrastructure, are still copied unchanged.

The acceptance review also checks the other direction of design conformance: names and copy keys from the projects domain, fields, columns, filters or actions the design does not declare, and example values that do not fit the feature's data are reported as D issues.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): end a cut-off cell in an ellipsis and reveal it in a tooltip

A generated articles table clipped long titles mid-character and showed "release-notes" as "elease-note": the title used line-clamp in a cell that never wraps, and the tags put truncate on Badge, a flex element that shows no ellipsis and loses both ends of its centered text.

Guideline T1.11 now requires the ellipsis and a reveal on hover only when the content is cut off. table.md shows text and links, the name column included, in a Tooltip whose onOpenChange cancels it when the value fits; the tooltip trigger leaves a Link a link, which a Popover trigger would turn into a button. Styled content such as a Badge stays in a Popover, since a badge is unreadable on the inverted tooltip. It also says where truncate works and that line-clamp does not belong in a cell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(templates): line DataTable up with a card by itself

A generated analytics page put a DataTable in a CardContent with px-0, as the handbook's "Table in a card" said, but not the cell padding that went with it. Its own border then sat on the card's edge, a strip was left below it, and the text started to the left of the card's title. The rule lived in four places and was still only half applied, because it asked the agent to pick a different component and hand-write selector classes.

DataTable now does it: inside a CardContent it drops its frame, reaches the card's edges and pads its first and last cells with --card-spacing, and a CardContent still given px-0 keeps working. The handbook builds a card's short list from DataTable with plain headers and no pagination, the dashboard example uses it, and guideline T1 says it covers list pages while a short list in a card follows T5.3, which is where the design had picked up sorting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): record every review checklist item in the review record

Both reviews of a generated content application were non-independent, since the environment could not start subagents, and neither checked T5.3: the design review raised only B1-B4 and S1-S4, and the acceptance review widened a clipped card without asking why the table sorted. The prompts already said to go through the checklist item by item, but the record kept only the items that failed, so a skipped item looked the same as a passed one.

The review record now has a "Review checklist" section with one row per checklist item: pass, fail with the issue it raised, or not applicable with the reason. Both prompts and the checklist's own introduction say to fill it in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): validate forms on submit, not as a field loses focus

A field error flashed for a frame whenever a dialog was closed without saving. Guideline T3.3 validated a field when it lost focus, so the handbook and its examples set useForm's mode to 'onTouched'; clicking Cancel, the close button or the backdrop takes the focus off the field first, and the error rendered while the dialog was fading out.

T3.3 now validates on submit, and a form closed without submitting shows no error. The handbook and the four examples leave mode at the default 'onSubmit', and it explains why the blur-based modes do not fit. After a failed submission a field is still revalidated as it changes, so its error clears once it is corrected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(templates): let a child page inside another one cover it whole

A covering page under a tab of a record's page can only render through the tab's outlet, inside the record page's RouteChildPage. That layer both positioned and scrolled, so the nested layer resolved inset-0 against it and scrolled out of sight with its content: in the recruitment application, after the candidate page had been scrolled 292px, the offer page opened at top -240 and showed only its footer over the candidate's details.

RouteChildPage is now two elements, the way the layout's content area is: the outer one positions and never scrolls, the inner one scrolls, with overscroll-contain so scrolling stops at the layer. A layer rendered inside another one anchors to that layer's outer element and covers it whole. Inside another layer it also makes everything around it inert up to that layer's scrolling element — the record page's back link, header and tab bar — not only its own siblings. The layer carries data-slot='route-child-page'; the props are unchanged.

The three templates and the UI Library registry item carry the same file. The templates' AGENTS.md add that an overlay a page's header opens on a page with tabs is declared under every tab and linked through the current tab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(app-skills): open a page header's overlays over the tab being shown

A review of the recruitment application found a record page whose header opened its edit dialog at /candidates/80/edit while the user was on /candidates/80/interviews. The handbook said a record opens over the page the user is on, but not that on a page with tabs that is the tab, and its two examples — the drawer with its stacked edit dialog, and the record page with tabs — never met. A link with a bare `edit` resolves against the route that renders it, not the URL on screen, and the header belongs to the page's route: the dialog replaced the tab in the page's outlet, and closing it went to the page's URL, which redirected to the default tab.

- Guidelines T2.1, I1, I6 and I9 define the view the user is on, the tab included, and I9 gives the test: an overlay's URL minus its own segments is the URL it was opened from, and closing returns there with no redirect.
- child-routes.md declares the header's overlays under every tab through customerHeaderRoutes(tab) inside customerDetailRoutes(owner), links the header through the tab read from the URL, and has each tab pass the page's context on to its outlet. page.md explains what a relative link resolves against, with a table of the cases.
- Going back restores the page underneath exactly (L6): links carry its whole query string down, a view below another one names its own parameters apart (table.md), and the way back removes them with withoutParams (example/url-search.md). child-routes.md no longer advises dropping the current page's filters when linking to another page's record, which lost them on the way back.
- example/detail-page-tabs.md is the complete page, a tab, a test that opens the header's "Edit" from every tab and goes back, and the route test helper; its code was compiled, linted and tested inside the default template.
- The review checklist, overlay.md, testing.md, frontend-dev.md and the run and design templates check overlays opened from a view other than the first one a page shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(templates): add DataTable and DatePicker from the UI Library on demand

The templates no longer ship DataTable or DatePicker, or the calendar,
select and table primitives only they used. Both are UI Library items
now, added with `shadcn add @nocobase/data-table` or
`@nocobase/date-picker` when a page first needs one. Examples keeps
select and table for its own pages, and Hub keeps react-day-picker,
which app-plugin-hub requires as a peer. The dataTable and datePicker
locale keys stay, so an item added later is translated at once.

- ui-library publishes data-table: DataTable and its column header,
  pagination and view menu as one item of four files under
  client/components/data-table/, with a preview demo and the
  dropdown-menu, select and table primitives it renders with.
- date-time-picker declares the calendar, popover and react-day-picker
  that the date-picker.tsx it installs imports; without calendar in the
  templates, installing it alone no longer compiled.
- ui-library gets Vitest. The DataTable and DatePicker tests move there
  from the templates and render the registry sources against the
  preview's primitives; Quality runs them on every pull request and the
  UI Library workflow before it publishes.
- The development Skill adds the items on the worked example's Add
  first lines and says which of the CLI's package.json changes to undo;
  the upgrade Skill covers an application that still has the old copies.
- The pending changesets that described a data-table directory in the
  templates now describe what ships.

* refactor(templates): drop the unused Typography components

client/components/typography.tsx held the class strings from the shadcn
Typography guide for hand-written long-form text. Nothing in the
templates or the plugins imported it, and article content usually comes
from Markdown or rich text, which these components cannot style. The
templates' AGENTS.md and the development Skill no longer list it.

* feat(ui-library): publish BackButton and keep it preinstalled in the templates

Make the UI Library the source of truth for BackButton, as it already is
for PageHeader and the route overlays.

- ui-library: add the back-button component item (back-button.tsx, its
  registry entry, a Navigation preview demo, and a test that renders it in
  the strict runtime built from the components README), document it in the
  components README with its navigation.back key, and use it in the
  RouteChildPage demo instead of a hand-written back link.
- templates: keep client/components/back-button.tsx as an exact copy,
  checked by tests/scripts/template-ui-library.test.mjs, and name
  BackButton among the UI Library components in AGENTS.md. The component
  itself is unchanged, and each template keeps its own back-button test,
  which is the one check that the template's own locales carry the key.
- MAINTAINING.md and USAGE.md list back-button among the preinstalled
  components, and MAINTAINING.md records navigation.back as the one
  exception to item-prefixed keys: it predates the item and pairs with
  the templates' navigation.breadcrumb.
- The development Skill's styling reference now says which composed
  components are preinstalled from the UI Library and which are the
  template's own, instead of saying the CLI cannot add any of them, and
  the plugin Skill lists back-button among the items a plugin copies.

* feat(ui-library): add a ConfirmDialog item built on alert-dialog

ConfirmDialog confirms one action in an alert dialog the page opens from
its own state. It runs the action: while a returned promise is pending
the confirm button shows a spinner and holds the focus, both buttons are
disabled and Escape is ignored; resolving closes the dialog with
onOpenChange(false, 'confirm'), and a rejection or a returned
{ error, retryable } keeps it open with the error inside it. Cancel
holds the initial focus, and focusAfterConfirm names where focus goes
after a success, when the opener has often gone with its record.

A result is reported to the onOpenChange of the latest render, and one
that arrives after the caller closed or unmounted the dialog is dropped,
so a handler whose close meant cancel is not called with stale state.
A ref, not render state, keeps two clicks from running the action twice.

The preview gains the alert-dialog and spinner primitives, a demo with a
slow, a failing and a refused delete, and the item's tests render it in
the strict i18n runtime built from the README's Translations table.

* docs(app-skills): fix the frontend guidance the PR review found wrong

- The toast examples in complete-button.md and public-switch.md, and the
  401 rule in api.md, used actionProps, left over from the Base UI toast
  API; the toaster's option is action: { label, onClick }.
- ui-workflow.md's development step still said to copy the worked
  example, as did frontend-dev.md and i18n.md in passing; they now say to
  write the feature's files from its design and copy only what a
  document calls shared infrastructure. i18n.md no longer lists the extra
  shared keys twice with different wording.
- The customer page test in detail-page-tabs.md assumed a mocked i18n
  client and queried keys; it renders in the strict test runtime with
  the application's locale files and queries the wording. The document
  also says detail/edit.tsx has to be written before the test imports it,
  with the customers.form keys copy.md does not list, and no longer claims
  the test checks what only the route test can.
- project-form.md and list-page.md name the select primitive they
  import, and server-table.md says what it needs and names
  data-table/index.tsx.
- The end-to-end guidance and the screenshot tool say what a Hub
  application sets up first, since the Hub template has no Playwright.
- shadcn.md no longer claims the application installs CLI 4.21.0.
- The docs site's child page example leaves by BackButton, says why a
  deeper outlet sits beside the layer as the component now behaves, and
  its shadcn add example no longer offers to overwrite the translated
  dialog primitive.
- form.md's create page is the frame to follow, not to copy.

* docs(app-skills): require RouteChildPage for a child route that is a page of its own

A generated application declared its article editor as a child route of a
dashboard and of a list and returned a bare PageContainer, so the editor
rendered at the parent's Outlet below the dashboard instead of covering it.
The handbook implied the rule without stating it.

- child-routes.md, page.md, form.md and frontend-dev.md state that only tab
  content renders inline and a page of its own returns RouteChildPage,
  including a form page shared by several parents.
- testing.md adds a covering child page test: the parent's heading sits in
  an inert element, which fails for a page rendered inline.
- The template AGENTS.md files carry the rule, since they are always read.
- The UI Library README and the docs site place the parent's Outlet at the
  end of its PageContainer, as the handbook does.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 11:10:19 -04:00
Junyi dfdd44902f refactor(workflow): refactor dsl (#478)
* feat(workflow): add typed DSL refactor demo

* refactor(plugin-workflow): refactor dsl

* fix(workflow): fix test case

* refactor(workflow): update skills

* fix(workflow): fix test case

* refactor(workflow): change unpublished route to hash
2026-09-29 23:45:16 +08:00
Katherine 23f052feab docs: move deployment to its own chapter and make it agent-first (#481)
* docs: move deployment to its own chapter and make it agent-first

Move the deployment section out of the development chapter into a
top-level `deployment/` chapter in both languages, and rewrite it around
two routes: handing the deployment to an AI Agent, with the session
location, Skill and prompt for each scenario, and doing it by hand, with
one page each for Hub and standalone deployment. Runtime configuration
and backup/troubleshooting stay as trimmed reference pages.

Update the home page cards, top navigation, development index and the
tutorial's Hub placeholder to point at the new chapter, and fix the two
links into the old path in the default template README and the
app-installer Skill.

* docs: fix deployment guidance links and rollback

* docs: make agent deployment prompts actionable

* docs: group deployment sidebar entries
2026-09-29 13:15:53 +08:00
Drol 414956d574 feat(ai-employee): discover and test models before startup (#483)
* feat(ai-employee): add model discovery and test commands

* docs(ai-employee): consolidate LLM configuration guidance
2026-09-28 12:44:17 -04:00
DrolandClaude Opus 5.5 aeff80a1d2 feat(jobs)!: add @nocobase/jobs and move Scheduler onto it (#463)
* feat(schedule): scaffold the @nocobase/schedule package (T001)

Adds packages/libs/schedule on the shared dev-config presets with BullMQ
6.3.6, cron and cron-parser as dependencies, lists it among the
isolatedDeclarations libraries, and records msgpackr-extract, which
arrives through BullMQ, as a deliberate build skip both here and in the
allowBuilds that create-app writes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(schedule): add the service contract, configuration fallback and validation (T002)

Exports the ScheduleExecuteService, ScheduleExecutor and job types from
the plan and createScheduleExecuteService. The service resolves a named
configuration, then schedule.default, then a built-in memory
configuration it reports through onFallback; returns one executor per
(scope, configuration); and rejects a repeated call whose overrides
change the effective concurrency or attempts. Scopes, job names and job
options are validated before anything reaches an adapter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(schedule): add the in-memory scheduler (T003)

Adds the executor shared by both adapters: it registers handlers, holds
jobs added before setup() and writes them there, skips a write whose
rule, payload and execution settings are unchanged, passes immediately
only for a new rule, keeps handlers after removeJob, fails firings
without a handler as handler-not-registered, and delivers subscriber
events in order.

The memory adapter plans firings with cron-parser and fires them through
one-shot cron jobs created from a Date. Every rule, its next firing and
its firing count persist to a versioned state file per namespace and
scope, written atomically; a missed firing runs once after a restart. An
exclusive lock file refuses a live holder or another host and takes over
a lock left by a crashed process. A cron rule without tz runs in UTC.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(schedule): add the redis adapter on BullMQ job schedulers (T004)

The namespace is the queue prefix and the scope the queue name. Rules
map to upsertJobScheduler with a template carrying the payload, attempts
and retention, compared through getJobScheduler before any write. A
worker runs firings with the configured concurrency and reports its
local active, completed and failed events; a firing without a handler
fails as an UnrecoverableError and is reported as handler-not-registered.
Shutdown aborts running handlers and waits for them before closing.

Only public Queue, Worker and Job API is used. The planned time is
job.timestamp plus the delay in job.opts, because BullMQ zeroes the
job's own delay once the job leaves the delayed set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* test(schedule): run the redis adapter against a real Redis (T005)

Adds test:integration, which starts Redis in a disposable Compose
project, runs tests/integration against it and removes the project. The
suite covers one execution per firing across instances, continuing on
another instance after a shutdown, planned and next firing times,
skipping an unchanged write so a due firing survives, a changed rule
replacing it, immediately firing once, re-adding a removed job, a
missing handler failing without retry, retries, shutdown waiting for a
running handler, limit, and listing.

Measuring against BullMQ 6.3.6 changed two things in the adapters. An
end date already in the past is rejected by upsertJobScheduler, so both
adapters now remove such a rule instead of writing it. A scheduler whose
limit is spent keeps reporting its last planned time as next, so a next
firing that is not after the current one is reported as none.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(app-server): compose the schedule service under @nocobase/app-server/schedule (T006)

Adds scheduleExecuteServiceToken, AppScheduleConfig and
ScheduleExecuteServiceProvider. The provider builds the service from the
schedule section with the application name as the default namespace and
storage/schedule as the built-in memory location, reports a fallback to
that configuration outside develop and development, starts nothing, and
shuts down whatever executors their owners left running.
@nocobase/schedule is a peer, provided by the application.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(app-plugin-scheduler)!: schedule through @nocobase/schedule (T007)

Scheduler now hands each definition's rule to its own executor from
@nocobase/app-server/schedule, on the @nocobase/app-plugin-scheduler
scope with concurrency 1 and a single attempt, instead of the queue's
schedule projection and ScheduleDispatchJob.

- A new migration adds nextRunAt, lastRunAt, runCount, appliedLimit and
  lastOccurrenceId to scheduleDefinitions and scheduledAt to
  scheduleOccurrences; the published migration is unchanged.
- Startup registers every definition's handler, adding rules for the
  enabled ones, before setup(); sync-only mode sets the executor up
  without consuming and starts no reconciliation.
- A rule is rewritten only when its definition changed, carrying the
  limit left after the firings already run; disabling removes the rule
  and keeps the handler.
- The occurrence id is the firing's job id; starts count once per
  occurrence; a target that is not ready now records a skipped
  occurrence instead of failing with OCCURRENCE_NOT_FOUND.
- Shutdown clears the reconciliation timer, unsubscribes, and waits for
  the executor.

BREAKING CHANGE: ScheduleDispatchJob is no longer exported, the plugin
contributes no queue jobs, and nothing is migrated from queue_schedules
or queue_jobs. Applications must compose ScheduleExecuteServiceProvider
and provide @nocobase/schedule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(templates): compose the schedule service in every template (T008)

Each template adds ScheduleExecuteServiceProvider with the application's
NODE_ENV, a server/config/schedule.ts offering memory and redis
configurations without choosing a default, and @nocobase/schedule as a
production dependency, and drops the queue that existed only for the
Scheduler. config.example.yml documents the section, and the deployment
Skill covers choosing the backend for one or several instances.

Tests that start applications now keep schedule state in their own
temporary directory: two suites starting apps on the template's
storage/ at the same time are two processes on one memory state file,
which the adapter's lock refuses. The deployable dist records
msgpackr-extract as a deliberate build skip, as the repository and
create-app already do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor!: replace @nocobase/cron with @nocobase/schedule (T009)

Removes packages/libs/cron and the dependency the default and examples
templates declared on it; nothing imported it. The application
development Skill's section on work that runs on a schedule now uses an
executor from scheduleExecuteServiceToken under the application's own
scope, set up in start() and shut down in shutdown(), and explains that
the redis adapter, not a hand-written lock, is what keeps several
instances from firing the same job. The cron catalog entry stays for
@nocobase/schedule.

BREAKING CHANGE: @nocobase/cron is no longer developed here; its
published 0.1.0 remains installable.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(scheduler): describe scheduling on the schedule service (T007)

The Scheduler capability page still described the queue's schedule
projection: a schedule queue worker, queue_schedules, a paused queue
schedule, and limit as queue claims. It now says where schedules run —
the schedule configuration, memory on one host unless schedule.default
names redis — in both languages.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore(changeset): describe the schedule service and the Scheduler move to it (T010)

Adds changesets for @nocobase/schedule and @nocobase/app-server, the
breaking Scheduler change with no migration from the queue tables, the
templates and app-skills replacing @nocobase/cron with
@nocobase/schedule, and the msgpackr-extract build skip in create-app
and app-tools.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(schedule): share the memory adapter's state across the processes of one host (T011)

The memory adapter held an exclusive lock for its whole lifetime, so a
second process — nb3 schedule:sync while the application ran, or a PM2
cluster — was refused. It now reads its state file without a lock, since
the file is replaced by rename and always whole, and takes a short
exclusive lock only for each read-modify-write, reading the file again
inside it. Each consuming process arms every planned firing and claims
it under the lock by advancing the rule past it, so a firing runs once
among them, and polls the file's signature to pick up rules the others
changed. A lock left by a process that no longer runs, or held far
longer than a write takes, is taken over.

Every write records a revision and its writer; a file another host
writes after this process started is refused, since a shared file system
gives neither atomic rename nor exclusive create reliably. The executor
passes its unchanged-rule check to the backend so memory decides it
again under the lock.

Documentation, Skills, the deployment guidance, the templates' comments
and the changesets now describe memory as one host rather than one
process.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(schedule): keep the memory adapter's state in memory and write it on shutdown (T012)

The memory adapter now reads its state file once, in setup(), changes
only its memory while it runs, and overwrites the whole file in
shutdown(), after it stops firing and running handlers finish. This
replaces the shared, lock-guarded file of T011: there is no lock, each
process holds its own copy, and the one that shuts down last overwrites
the file. A process that ends without shutting down loses what changed
since it started, and a setup() that could not read the file never
overwrites it.

The lock module, the file's revision and writer, the signature poll,
the executor's unchanged-rule argument, the multi-process fixture and
its tsx dependency are removed. Documentation, Skills, deployment
guidance, template comments and changesets describe memory as one
process whose state reaches storage when the application stops.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(app-plugin-scheduler): remove stale rules again on every synchronization (T013)

Synchronization only ever added rules; it removed one only when it
deactivated a definition itself or an administrator disabled it. A
removal that was lost stayed lost: on the memory adapter a running
application overwrites the state file nb3 schedule:sync --finalize wrote,
so the rule of a definition removed from code came back and failed as
handler-not-registered at every firing.

Every synchronization now removes again the rule of each disabled
definition in the manifest and of each definition already inactive,
once the executor is set up. It reads the definition again first, so a
schedule another instance enabled in the meantime keeps its rule. An
active definition a partial manifest leaves out is still left alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* docs(schedule): explain how applications and plugins use the schedule service

The README now covers when to use the package rather than the Scheduler
plugin or a queue job, how an application composes it, the schedule
configuration and how a consumer's configuration is chosen, the provider
pattern for plugins and application code with its scope, ordering and
shutdown rules, dependency declarations, job options and their edge
cases, receipts, handlers and orphan rules, events, both adapters'
guarantees and limits, and testing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* feat(examples): add a plugin running a recurring job on the schedule service

@nocobase/app-plugin-schedule-example shows the pattern the schedule
README describes. Its provider takes an executor of its own from
scheduleExecuteServiceToken under its package name, subscribes to its
events, registers a heartbeat that runs every minute, sets the executor
up, removes the rules of jobs it no longer defines, and shuts the
executor down with the application. The handler records each run from
its own context, since events arrive after the fact. An authenticated
GET /api/schedule-example returns the next firing and the recent runs.

The examples template registers the plugin and covers the route end to
end on the application's schedule service.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* chore: follow the consolidated app-cli after rebasing onto develop

@nocobase/app-tools is now part of @nocobase/app-cli, so the
msgpackr-extract changeset covers app-cli, whose dist workspace carries
the entry. The Scheduler's command is nocobase scheduler sync in the
changeset and the capability page.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor!: rename @nocobase/schedule to @nocobase/jobs and let Scheduler choose its configuration

The package moves to packages/libs/jobs as @nocobase/jobs, its
configuration section from schedule to jobs, and the built-in memory
configuration's state from storage/schedule to storage/jobs, so the
section no longer reads like the Scheduler plugin's. Nothing was
published under the old name.

Scheduler gains a scheduler section: scheduler.jobs, or SCHEDULER_JOBS,
names the jobs configuration its schedules run on, and without it they
follow jobs.default. The jobs service would quietly fall back to the
default for a name it does not know, so Scheduler refuses to start
instead. The default and examples templates declare the section.

@nocobase/app-server/schedule and the ScheduleExecute* identifiers keep
their names for now.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(jobs): report the planned time BullMQ stored in addJob receipts

upsertJobScheduler returns a job whose timestamp and delay come from two
clock readings, one in JavaScript and one in its script, so their sum
could miss the planned time by a millisecond even for a cron rule. The
receipt now reads the scheduler's own next, which is the planned time
itself, and falls back to the job only when there is none. The Redis
suite's check that a replaced firing runs at the receipt's time failed
on that millisecond.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* refactor(jobs): rename JobExecutorService and use configured executor settings

* fix(app-cli): resolve native platform packages from declared dependencies

* fix(jobs): exclude optional native accelerator and revert retarget changes

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 12:09:24 -04:00
DrolandClaude Opus 5.5 64cf25ac20 feat(ai-employee)!: key ai.llmServices by name and set secrets through config (#456)
* fix(ai-employee): drop obsolete SQL and naming rules from the system prompt

The database section of the system prompt depended on environment.database,
which no caller passes, so the quoting rules, the main database line and the
DB_UNDERSCORED snake_case rule never rendered. The unconditional "Database
Operations" rules told the model to write SQL against a <data_sources> tag
that is never emitted, while AI employees only read data through the
Repository-backed data tools, which take Collection and field names and apply
the Connection naming strategy themselves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(ai-employee): remove the unused knownRoles option and getter

AIEmployeeService accepted a knownRoles option and exposed a getter that fell
back to the AI_DEFAULT_ROLES environment variable, but no caller passed the
option and nothing read the getter. Roles come from the requesting actor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-employee)!: key ai.llmServices by name and set secrets through config

ai.llmServices becomes a map keyed by service name, like ai.mcpServers, so
each service has a stable configuration path that `config set` and an `env`
mapping can address. The list form and a `name` field inside an entry are
rejected at startup.

The plugin no longer expands ${NAME} in ai.llmServices or ai.mcpServers. It
read those values from process.env, which a built server does not merge .env
into. A key is now written with `pnpm nocobase config set --from-env`, or,
when the environment injects it, mapped in `env` of server/config/ai.ts like
every other section's variables. `config check` therefore no longer exempts
the two AI sections from its literal-reference warning.

The templates default ai.llmServices to an empty map and declare
server/config/ai.ts in the object form with an empty `env`. The plugin
Skill, README and Chinese docs describe the map form and hand key handling
over to the application development Skill's `config set --from-env` flow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(ai-employee): simplify key setup and model selection guidance

The Skill no longer walks an agent through building shell commands that
read a key. It tells the user the key's path and the `config set
--from-env` form, and leaves setting it to them.

Model selection now follows the order the key allows: the entry is written
without invented model ids, the user sets the key, and the models are
picked on the LLM services page, which fetches them from the provider with
the configured key. The agent no longer calls the provider itself.

Deployment configuration is handed to the nocobase-deployment Skill and the
deployment docs, a non-git application is covered where config.yml has to
stay out of the repository, the example service is named openai
throughout, and the templates' config.example.yml no longer claims that a
change applies without a restart. The changesets are rewritten to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(ai-employee): check ai.llmServices in config check and warn about missing keys

defineAIConfig declares the ai section with the plugin's validation, the
way defineAuthConfig does for auth, and both templates now use it. With
it, `pnpm nocobase config check` reports a structural problem in
ai.llmServices as an error by path instead of letting the start fail on
it, and warns about a service whose built-in provider sends
options.apiKey and has none, with the `config set --from-env` command
that sets it. ollama and App-registered providers are not checked.

The structural checks now collect every issue rather than throwing on
the first, so startup and config check share them. The Skill, the
Chinese docs and the changesets describe running config check after
writing an entry and after the user sets the key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(ai-employee): use config check in the Skill's model setup

The shortest path now runs `pnpm nocobase config check` after writing a
service entry and relays its missing-key warning, path and command
included, to the user, then runs it again once the user says the key is
set. The completion checks require config check to report nothing under
`ai.`, and the Skill says to switch a plain defineAppConfig to
defineAIConfig before relying on the check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 07:35:14 -04:00
Jiannx 8f187e204d docs: streamline Mail, Notification, and Template Print guides (#479)
* docs: simplify Chinese email, notification, and printing guides

* docs: localize email, notification, and printing guides
2026-09-28 16:43:21 +08:00
chenosandli'mac a857a087bb feat!: print the application CLI's --json envelope from app-installer, create-app and create-plugin (#471)
* fix(app-installer)!: print the application CLI's --json envelope

app-installer's doc comment promised a script that reads `pnpm nocobase ... --json` could read its output the same way, but two things differed: a failure's status was `error` rather than `failure`, and a suggestion's `run` was a shell string rather than `{ command, args }`. Both now match, including the Node.js check in bin/run.js. Suggestions that chained two commands with `&&` are split into two, and text output still prints each command as one quoted line.

`status` no longer prints `undefined` for an advice step that has no command.

tests/envelope.test.ts pins every member of the envelope, and tests/scripts/json-envelope-parity.test.mjs builds the same outcomes through both CLIs and compares the printed documents, so the two cannot drift silently again.

BREAKING CHANGE: scripts parsing app-installer --json must read `status: "failure"` and `run: { command, args }`.

* feat!: print the application CLI's --json envelope from create-app and create-plugin

create-app and create-plugin each had a JSON result of their own. create-app printed a flat object with `status: "error"` and no error codes; create-plugin named itself `operation`, kept its plan at the top level, gave suggestions as strings, and wrote failures to stderr. Both now print `{ schemaVersion, ok, command, status, result | error, warnings }` on stdout, like app-installer and every `pnpm nocobase` command.

create-app reports one code per stage a failure stops at (INVALID_USAGE, TEMPLATE_DOWNLOAD_FAILED, SCAFFOLD_FAILED, INSTALL_FAILED, DRIVER_VERIFICATION_FAILED), with the stage, directory and progress under error.details. create-plugin answers --dry-run with success-noop. Both answer an unsupported Node.js with the envelope under --json. Exit codes are unchanged.

app-installer and scripts/smoke-create-app.sh read create-app's new result and still read the flat one an earlier release prints. The parity test now covers all three standalone tools, and AGENTS.md records the rule.

BREAKING CHANGE: scripts parsing create-app or create-plugin --json read results under `result`, failures under `error` with `status: "failure"`, and create-plugin failures on stdout.

* fix(create-app,create-plugin)!: flush stdout before exiting and name the project in the install retry

The `--json` document was written and `process.exit` called in the same tick; on a platform where a piped stdout is asynchronous the document could be cut short, which the Node.js guard had already worked around on its own path. Both `bin/run.js` now exit through one helper that waits for stdout and stderr to drain, as app-installer's does.

create-app's INSTALL_FAILED suggestion ran `pnpm install` with no directory, so a caller executing `run` as given ran it where create-app was invoked rather than in the project; it now names the project with `--dir`. create-plugin's Node.js guard normalizes the version the way the other two guards do, and create-app pins its one-line output, which app-installer's `parseCreateResult` reads by line.

---------

Co-authored-by: li'mac <limac@Lis-MacBook-Air.local>
2026-09-27 19:21:46 -07:00
84cc7d2c3b feat(hub-cli)!: move Hub publishing out of app-cli into @nocobase/hub-cli (#470)
* feat(hub-cli)!: move Hub publishing out of app-cli into @nocobase/hub-cli

`release upload` and `release deploy` leave @nocobase/app-cli and
become `hub upload` and `hub deploy` in the new @nocobase/hub-cli
package. The `nocobase.cli.publishing` flag is removed: an application
gets the commands by depending on the package, and the Default template
declares it in devDependencies.

`hub deploy` uploads the archive and deploys it; with `--release-id` it
deploys a Release already on the Hub. `hub upload` only uploads.

app-cli gains a general mechanism for this. A direct @nocobase/
dependency whose package.json names a CLI entry in
`nocobase.cli.entry` contributes that entry's defineCliPlugin commands
without an entry in cli/plugins.ts, and a package that is not an
application plugin takes its topic from its name without the `-cli`
suffix. Finding such packages reads JSON only; a run imports one only
for a command under its topic, whole-tree help and `commands`, and a
declared package nobody installed is reported as PACKAGE_NOT_INSTALLED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(app-cli): accept colon-joined ids for CLI packages and check the entry's topic

The package a run imports was chosen from the first space-separated word of the command line, so `hub:deploy`, which oclif accepts as `hub deploy`, matched no topic and the package was never loaded; the same split kept a colon-joined id off the PACKAGE_NOT_INSTALLED path. Both now split on a space or a colon.

A loaded entry is also checked for the topic its package name gives, not only its package name, so an export that mounts under another topic is rejected rather than assembled under one the run did not select. The entries of several packages are imported concurrently, with the first failure in package order reported. The PACKAGE_NOT_INSTALLED message no longer asserts that an uninstalled package provides commands, since that cannot be known without its manifest, and `hub deploy --help` says the default key with --release-id includes the --config content, as the README and the code already did.

---------

Co-authored-by: li'mac <limac@Lis-MacBook-Air.local>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 19:19:46 -07:00
Katherine 16d1158bd0 docs(deployment): add a section on handing a deployment to an AI Agent (#458)
* docs(deployment): add a section on handing a deployment to an AI Agent

Keep the existing deployment pages and add one section to the index that
says where to open the session for each deployment mode, which Skill
drives it, what to state in the conversation, and what the report must
contain. Note that a Hub run from source on the build machine is built
without --target, that HUB_URL carries Hub's own mount path, and point
the Default template README at the Hub publishing page on GitHub, since
the docs site does not serve that path.

* docs(deployment): cover the plain Node.js route in the AI Agent section

Name the standalone route as Node.js or Docker so it matches the mode
table above, link both pages, and name the deployment Skill an
application ships.
2026-09-28 09:17:56 +08:00
chenos 46ce11fabb refactor!: read client runtime values from the server-rendered config and build path-free clients (#455)
* refactor(app-client): read the mount path from the server-rendered config

resolveAppBase and resolveAppUrl now read app.basePath from the client
configuration the server renders into the page, and throw when the page
carries none, instead of reading window.APP_BASE_PATH or falling back to
the bundler's base. The router basename comes from the same value, so
AppRuntimeDefinition.basename is removed and the templates no longer take
it from the deprecated portal SDK.

The AI employee plugin no longer builds an API client when its modules
load. Its service functions take the ApiClient as their first, required
argument, and pages use the new useAIEmployeeClient() hook, which binds
them to the application's client. The registry's NocoBaseAIService now
requires its client, and the nocobaseAIService singleton is gone.

The templates render a config block in their test setup, as the server
would, and drop the unused assetUrl helper.

* refactor(app-server): stop putting runtime values on window

The SPA routes render only the lang attribute and the client
configuration block. window.APP_BASE_PATH, window.NOCOBASE_API_URL and
the window.__nocobase_api_client_* storage settings are no longer
injected: the client reads the mount path and API URL from the
configuration, and nothing read the storage settings except the portal
SDK's unused AuthSession. SpaConfig.runtime, createNocoBaseSpaRuntimeGlobals,
injectSpaRuntimeGlobals and the SpaRuntimeGlobals types are removed, and
runtime-globals.ts becomes runtime-html.ts.

The templates drop the matching spa.runtime defaults and the Vite
defines that baked NOCOBASE_API_URL, NOCOBASE_AUTHENTICATOR and the
NOCOBASE_WS_* values into the client; pnpm dev no longer sets
NOCOBASE_API_URL for its child processes.

* refactor!: remove the portal SDK and rename the portal presets

@nocobase/app-portal-sdk is deleted. Its runtime helpers were already
deprecated, and after the client stopped reading window globals the
templates used nothing from it; its client and routing entry points had
no users in this repository. The peer-dependency list, the registry
dependency check, the root lint configuration and AGENTS.md drop it.

The dev-config presets that still carried the Portal name are renamed:
vite/portal with createPortalViteConfig becomes vite/app with
createAppViteConfig, and the ESLint createPortalConfig becomes
createApplicationConfig. Browser code is now linted against reading
import.meta.env beyond PROD, DEV and MODE.

The templates no longer define __PORTAL_TEMPLATE_NAME__,
__PORTAL_TEMPLATE_VERSION__ or the unread __PORTAL_DEV_SOURCE_ROOT__ at
build time. The server publishes the application's displayName and
version from its package.json as app.displayName and app.version in the
public client configuration, and the sidebar footer reads them there.
The bootstrap loading classes become app-bootstrap-*.

* feat!: build path-free clients and choose the mount path at run time

The shared Vite preset builds with a relative base, so every chunk,
preload dependency and asset resolves against the module that references
it and one build can be mounted at any path. renderBuiltUrl and its
window.APP_BASE_PATH expression are gone. The development server still
needs an absolute base and takes it from APP_BASE_PATH, which pnpm dev
now resolves once and passes to both children; it refuses to start
without one. The templates no longer set a base.

The server points the remaining ./assets/ URLs in index.html, meta
content included, at the path it is mounted at when it renders the page.

pnpm build records nocobase.relocatable instead of nocobase.basePath and
no longer copies APP_BASE_PATH into dist/.env, so the deployment chooses
the mount path. DEFAULT_APP_BASE_PATH in @nocobase/app-server/support is
the only place /main is spelled.

In PROXY_TARGET_URL mode no local server renders the page, so a
development plugin from @nocobase/app-cli/dev/proxy takes the client
configuration from the remote page and substitutes the local mount path
and API URL.

* feat!: choose the mount path when installing or running a build

app-installer takes --base-path and keeps the mount path in app.env, so one
relocatable archive installs anywhere and moves by editing APP_BASE_PATH.
An archive from before relocatable builds records the path its client was
compiled for and is only installed, upgraded to or rolled back to there.

The Hub refuses such an archive unless it was built for /<appId>. The
template Dockerfiles no longer take APP_BASE_PATH as a build argument; the
image defaults to /main (/hub for the Hub) and -e APP_BASE_PATH moves it.
The deployment Skill and documentation drop the build-time mount path.

* chore: add changesets and upgrade steps for the client runtime configuration

The removed @nocobase/app-portal-sdk is dropped from the consumed changesets
in .changeset/pre so the next prerelease run can still read them, and the
one that named only it is deleted. The nocobase-app-upgrade Skill's edge
cases gain the steps an existing application takes to move to the
server-rendered client configuration and relocatable builds.

* ci(smoke): give the deployed archive its mount path at run time

A build no longer copies APP_BASE_PATH into dist/.env, so the extracted
archive started at the server's /main default. The Hub serves at /hub, and
the smoke waited there until it timed out. Pass the path the checkout
served, as a deployment does.

* fix: resolve review findings on client runtime configuration

- app-server rewrites every `./` URL in `index.html` to the mount path,
  not only `./assets/`: a relative base writes every `public/` file the
  page names as `./<file>`, and a deep route resolved those elsewhere.
- app-installer keeps `/hub` for a Hub archive installed without
  `--base-path`; a build records no path and ships no `.env`, so the
  server's `/main` default moved it. Adds a test for `--base-path /`.
- The proxy-mode client configuration fetch reports the status it met
  and a redirect to another origin instead of blaming the URL.
- `createDevProxy` and `createDevClientConfigPlugin` share the local
  prefix computation; the JSON escaping stays local, with the reason.
- The Vite preset README says why serve-mode resolution outside
  `pnpm dev` needs `APP_BASE_PATH` and how a build is previewed.
2026-09-27 06:52:43 -07:00
Jiannx 4b086fa526 docs(mail): mail plugin (#125)
* feat(mail): add mailbox sending and sync architecture

* feat(mail): add Gmail and Microsoft providers

* feat(mail): add settings and development pages

* fix(mail): harden sending and mailbox synchronization

* feat(mail): add mailbox workspace and conversation view

* fix(mail): invalidate mailbox requests on account refresh

* fix(mail): stabilize mailbox view state

* refactor(mail): consolidate workspace under dev route

* refactor(mail): keep all UI under dev route

* feat(mail): add account management and operation logs

* feat(mail): add reply, forward, and scheduled delivery

* feat(mail): add account management actions

* feat(mail): complete workspace and provider features

* feat(mail): complete core management experience

* fix(mail): harden provider and permission boundaries

* fix(mail): harden persistence model

* feat(mail): add credential-based IMAP/SMTP provider MVP

* feat(mail): improve account and workspace management

* feat(mail): enhance mailbox management and sync flows

* feat(mail): harden mail flows and account selection

* feat(mail): complete mail workspace capabilities

* test(mail): align mail plugin test coverage

* feat(mail): bundle built-in providers and modernize workspace

* fix(mail): address provider sync and draft review issues

* feat(mail): improve composing, delivery logs and mailbox sync

* fix(mail): improve pagination and delivery safety

* feat(mail): add resumable sync and consolidate migrations

Recover mailbox synchronization, retry incomplete content, reconcile sent messages and drafts, and improve account switching and runtime logging.

Consolidate unreleased migrations and changesets, expand regression coverage, and update Mail documentation and integration guidance.

* refactor(mail): separate contracts and runtime responsibilities

* fix(mail): harden persistence, drafts and workspace refresh

* refactor(mail): remove implementation migrated to Pro

* docs(mail): reorganize guides around AI-assisted setup and usage

* docs(mail): clarify provider setup and access boundaries

* docs(mail): focus guides on agent-driven use cases
2026-09-27 21:14:17 +08:00
chenos a4ee8aa542 feat(app-installer): generalise hub-installer into app-installer for deployment archives (#451)
* feat(app-server): keep standalone storage under APP_STORAGE_DIR and record build metadata

A standalone application reads APP_STORAGE_DIR for its storage directory, so a
deployment whose code root is replaced per release keeps one storage directory.
The Hub template still honours HUB_STORAGE_DIR when APP_STORAGE_DIR is unset.

pnpm build records nocobase.basePath and nocobase.builtAt in dist/package.json,
and the templates' ecosystem.config.js derives the pm2 process name from the
application's package name, overridable with APP_PM2_NAME.

* refactor(app-installer): rename @nocobase/hub-installer to @nocobase/app-installer

A pure rename ahead of generalising the installer beyond the Hub: the package
directory, name and bin, its unreleased changesets, the smoke script and its
workflow, and pnpm unreleased:hub-smoke, now unreleased:installer-smoke. The
package was never published, so nothing outside this repository refers to the
old name. Behaviour is unchanged.

* refactor(app-installer): generalise the installer's layout, state and backups

Releases are named by version and build time (releases/<version>_<time>/app),
so two builds of one version are two releases; upgrade --rebuild becomes an
ordinary upgrade to a fresh build, which removes the staging and swap logic.
installer.json records the application name, base path and source. The runtime
variables move to app.env with APP_STORAGE_DIR (and HUB_STORAGE_DIR for Hub
releases published before it), health and URLs follow the release's base path,
and backups cover every SQLite database config.yml declares. Messages name the
application rather than the Hub, and the pm2 name defaults to the directory.

* feat(app-installer): install and upgrade from a deployment archive

install takes --archive <dist.tar.gz> or --template hub[@version], one of them
and only one. An archive is unpacked with the tar library pnpm build packs it
with, so a server needs no tar and no pnpm; its manifest names the release, and
an archive without a base path and build time, built for another machine, or
lacking the dialect's driver is refused with the build command that fixes it.
upgrade --archive requires the same application name and base path and refuses
an older version; a release writing its data inside itself is refused as well.
An upgrade takes the kind of source the install did.

* feat(app-installer): source-aware status and rollback advice, free-port suggestion

status and rollback tell an archive installation to build its archive again
for this machine's Node and upgrade to it, where a template installation gets
upgrade --rebuild; status reports the source and skips the registry for an
archive installation. install names a free port when the chosen one is taken.
The README documents both sources, release ids, app.env and every error code.

* test(app-installer): smoke both sources in CI and unreleased:installer-smoke

The smoke script takes --source template or --source archive. The archive run
installs a deployment archive, upgrades to a later build of the same version,
rolls back, restores the database after a migrating upgrade, and rolls back
automatically from a build that fails to start. CI gains a job that builds the
default template's archive from the checkout and runs it; unreleased:installer-
smoke creates and builds an application from the snapshot and runs both.

install reports the release's build time. The template alignment test no
longer expects each template to name its own pm2 process.

* docs: document app-installer and rename its global Skill

The global nocobase-hub-installer Skill becomes nocobase-app-installer and
covers both sources: building and installing a deployment archive, and the Hub
template. The nocobase-deployment Skill offers app-installer for a standalone
server, and the documentation site gains a page for it, with the Hub page,
the deployment overview, operations and standalone pages updated. Examples that
run published Hub images keep HUB_STORAGE_DIR, which those images still read.

The unreleased installer changesets, written for the Hub-only package, are
replaced by one describing the package as it now ships.

* docs: name only APP_STORAGE_DIR in the deployment pages

The Hub pages, operations page and the app-development Skill no longer
document HUB_STORAGE_DIR: nothing has been released under the old name that
the documentation needs to keep serving.

* refactor: drop HUB_STORAGE_DIR in favour of APP_STORAGE_DIR

The Hub template no longer reads HUB_STORAGE_DIR, which removes its own path
policy: the runtime applies APP_STORAGE_DIR for every standalone application.
app-installer stops writing the old name for a Hub installation. Nothing has
been released under the old name that has to keep working; a deployment that
sets only HUB_STORAGE_DIR renames it before upgrading.

* fix(app-installer): write HUB_STORAGE_DIR for a Hub until one reads APP_STORAGE_DIR

Every Hub release published so far reads only HUB_STORAGE_DIR, so a template
install of one kept its data inside the release and was refused with
STORAGE_IN_RELEASE. The installer writes the old name beside APP_STORAGE_DIR
for a Hub installation again; a Hub that reads APP_STORAGE_DIR ignores it.
Remove it once such a release is published.

* fix(app-installer): address review findings on PR 451

Recognise a Hub by its manifest's nocobase.templateKind rather than by the
install source, so a Hub project deployed from an archive keeps its App Host
child (treekill off) and its confirmations name the hosted applications;
installer.json records the kind. Suggestions no longer carry a placeholder in
a command meant to run as printed, and every rebuild suggestion made during an
upgrade builds for the installed base path. An unreadable config.yml stops an
upgrade with CONFIG_UNREADABLE instead of passing as "no databases".
rollback --to <version> resolves to the newest build of that version other
than the running one. The upgrade prechecks are hoisted out of both source
branches, and tests cover the storage check rolling an upgrade back and an
unrecorded release directory being adopted.

* docs(skills): route Hub installs and app-installer deployments to nocobase-app-installer

nocobase-create-app is for installing, creating or trying NocoBase, and hands a
request to install a Hub, or to deploy an application with app-installer, to
nocobase-app-installer. Only a Hub whose own code will change is created as a
project. Both Skills now carry the same routing table, and their descriptions
name what each is for and what it hands over.
2026-09-26 20:42:41 -07:00