@north-light/crouter 0.3.197 → 0.3.199
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +19 -19
- package/dist/api/client.js +29 -37
- package/dist/api/dto/broker-ops.d.ts +3 -1
- package/dist/api/dto/broker.d.ts +13 -2
- package/dist/api/dto/broker.js +4 -4
- package/dist/api/dto/human.d.ts +6 -5
- package/dist/api/dto/inbox.d.ts +126 -33
- package/dist/api/dto/nodes.d.ts +8 -2
- package/dist/api/dto/profiles.d.ts +23 -0
- package/dist/api/dto/profiles.js +2 -2
- package/dist/api/dto/worktree.d.ts +3 -5
- package/dist/api/routes.d.ts +3 -5
- package/dist/api/routes.js +4 -6
- package/dist/builtin-memory/00-runtime-base.md +8 -8
- package/dist/builtin-memory/01-spine/01-no-manager.md +1 -1
- package/dist/builtin-memory/02-lifecycle/00-terminal.md +3 -3
- package/dist/builtin-memory/04-orchestration-kernel.md +6 -6
- package/dist/builtin-memory/05-kinds/design/00-base.md +1 -1
- package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +2 -2
- package/dist/builtin-memory/05-kinds/review/00-base.md +2 -2
- package/dist/builtin-memory/05-kinds/review/01-orchestrator.md +1 -1
- package/dist/builtin-memory/05-kinds/spec/01-orchestrator.md +1 -1
- package/dist/builtin-memory/insights/capture.md +2 -2
- package/dist/builtin-memory/insights/init.md +3 -3
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/memory-loading.md +1 -1
- package/dist/builtin-memory/internal/nodes-and-canvas.md +3 -3
- package/dist/builtin-memory/internal/plugins.md +59 -2
- package/dist/builtin-memory/internal/storage-tiers.md +3 -1
- package/dist/builtin-memory/spec/roadmap.md +2 -2
- package/dist/builtin-memory/testing.md +1 -1
- package/dist/builtin-memory/wedged-child-on-runaway-bash.md +1 -1
- package/dist/clients/attach/chrome/bash-jobs.js +3 -3
- package/dist/clients/attach/render/chat-view.d.ts +29 -9
- package/dist/clients/attach/render/chat-view.js +64 -35
- package/dist/clients/attach/render/markdown-source.js +0 -6
- package/dist/clients/attach/render/page-block.d.ts +34 -0
- package/dist/clients/attach/render/page-block.js +226 -0
- package/dist/clients/attach/render/tool-calls.js +16 -2
- package/dist/clients/attach/session/whip.d.ts +2 -0
- package/dist/clients/attach/session/whip.js +2 -0
- package/dist/clients/attach/viewer.js +1419 -982
- package/dist/clients/conversation/projection.js +18 -3
- package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +9 -6
- package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +13 -14
- package/dist/clients/inbox/controller.js +10 -16
- package/dist/clients/inbox/page-adapter.d.ts +3 -4
- package/dist/clients/inbox/page-adapter.js +3 -4
- package/dist/clients/inbox/resolve.d.ts +1 -1
- package/dist/clients/inbox/resolve.js +3 -3
- package/dist/clients/inbox/review/document-surface.js +3 -1
- package/dist/clients/inbox/review/state.d.ts +2 -1
- package/dist/clients/inbox/review/state.js +5 -16
- package/dist/clients/inbox/tui/input.js +37 -19
- package/dist/clients/inbox/tui/panel.js +5 -31
- package/dist/clients/inbox/tui/render.d.ts +11 -0
- package/dist/clients/inbox/tui/render.js +101 -36
- package/dist/clients/inbox/tui/slots.d.ts +4 -4
- package/dist/clients/inbox/tui/slots.js +19 -44
- package/dist/clients/inbox/tui/types.d.ts +6 -10
- package/dist/clients/inbox/tui.js +2 -2
- package/dist/commands/__tests__/human.test.js +118 -24
- package/dist/commands/__tests__/node-message.test.js +14 -0
- package/dist/commands/attention.js +4 -4
- package/dist/commands/canvas-browse.js +1 -1
- package/dist/commands/canvas-snapshot.js +1 -1
- package/dist/commands/canvas.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/human/{doc.d.ts → components.d.ts} +1 -1
- package/dist/commands/human/components.js +77 -0
- package/dist/commands/human/feedback.d.ts +2 -0
- package/dist/commands/human/feedback.js +108 -0
- package/dist/commands/human/prompts.d.ts +17 -8
- package/dist/commands/human/prompts.js +217 -226
- package/dist/commands/human/queue.js +57 -88
- package/dist/commands/human/shared.d.ts +4 -15
- package/dist/commands/human/shared.js +19 -61
- package/dist/commands/human.js +32 -19
- package/dist/commands/memory/origin.js +2 -2
- package/dist/commands/node/bash.js +4 -4
- package/dist/commands/node/inspect.js +3 -3
- package/dist/commands/node/lifecycle.js +1 -1
- package/dist/commands/node/message.js +2 -2
- package/dist/commands/node-worktree.js +8 -8
- package/dist/commands/pkg/browse.js +1 -1
- package/dist/commands/pkg/plugin-manage.js +129 -10
- package/dist/commands/profile/delete.js +41 -15
- package/dist/commands/profile.js +1 -1
- package/dist/commands/push.js +1 -1
- package/dist/commands/surface/node/focus.js +29 -14
- package/dist/commands/surface-edit.js +3 -3
- package/dist/commands/surface-inbox.d.ts +2 -0
- package/dist/commands/{human/inbox.js → surface-inbox.js} +14 -18
- package/dist/commands/surface.js +2 -1
- package/dist/commands/sys/doctor.js +60 -3
- package/dist/commands/sys/feedback.js +79 -25
- package/dist/commands/sys/settings.js +1 -1
- package/dist/core/__tests__/cron-broker-capacity.test.js +1 -1
- package/dist/core/__tests__/daemon-boot.test.js +7 -4
- package/dist/core/__tests__/dead-node-policy-table.test.js +20 -36
- package/dist/core/__tests__/extension-abort.test.js +5 -2
- package/dist/core/__tests__/fixtures/fake-engine.d.ts +13 -2
- package/dist/core/__tests__/fixtures/fake-engine.js +47 -3
- package/dist/core/__tests__/human-cancel-guard.test.js +31 -10
- package/dist/core/__tests__/human-deliver.test.js +41 -45
- package/dist/core/__tests__/human-node-not-supervised.test.js +1 -1
- package/dist/core/__tests__/plugin-page-components.test.js +157 -0
- package/dist/core/__tests__/seam/broker-crash-teardown.test.js +13 -11
- package/dist/core/__tests__/seam/dormancy-release.test.js +103 -4
- package/dist/core/__tests__/serial/flagship-lifecycle.test.js +2 -2
- package/dist/core/__tests__/serial/human-deliver-e2e.test.js +12 -16
- package/dist/core/__tests__/serial/live-mutation.test.js +1 -1
- package/dist/core/__tests__/serial/tmux-surface.test.js +4 -2
- package/dist/core/__tests__/serial/worktree.test.js +24 -198
- package/dist/core/__tests__/session-cycles.test.js +22 -0
- package/dist/core/__tests__/stop-guard.test.js +4 -4
- package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +19 -4
- package/dist/core/bash-jobs.d.ts +6 -0
- package/dist/core/bash-jobs.js +10 -0
- package/dist/core/canvas/__tests__/attention.test.js +8 -5
- package/dist/core/canvas/__tests__/render-remote.test.js +5 -5
- package/dist/core/canvas/attention.js +8 -6
- package/dist/core/canvas/browse/app.d.ts +1 -1
- package/dist/core/canvas/browse/app.js +21 -3
- package/dist/core/canvas/browse/model.d.ts +8 -2
- package/dist/core/canvas/browse/model.js +11 -4
- package/dist/core/canvas/browse/render.d.ts +4 -0
- package/dist/core/canvas/browse/render.js +5 -1
- package/dist/core/canvas/canvas.d.ts +33 -0
- package/dist/core/canvas/canvas.js +92 -0
- package/dist/core/canvas/crons.d.ts +9 -0
- package/dist/core/canvas/crons.js +51 -0
- package/dist/core/canvas/node-order.d.ts +22 -1
- package/dist/core/canvas/node-order.js +26 -1
- package/dist/core/canvas/render-source.d.ts +5 -0
- package/dist/core/canvas/render-source.js +1 -0
- package/dist/core/canvas/types.d.ts +9 -3
- package/dist/core/command-plugins/bundle.d.ts +6 -1
- package/dist/core/command-plugins/bundle.js +26 -13
- package/dist/core/command.js +8 -12
- package/dist/core/config.d.ts +47 -1
- package/dist/core/config.js +138 -4
- package/dist/core/help.d.ts +3 -0
- package/dist/core/help.js +1 -1
- package/dist/core/human/__tests__/page-catalog.test.d.ts +1 -0
- package/dist/core/human/__tests__/page-catalog.test.js +20 -0
- package/dist/core/human/__tests__/page-tickets.test.js +68 -34
- package/dist/core/human/__tests__/page.test.js +60 -119
- package/dist/core/human/__tests__/serial/inbox-core.test.js +6 -16
- package/dist/core/human/answer-text.d.ts +3 -0
- package/dist/core/human/answer-text.js +98 -0
- package/dist/core/human/answer.d.ts +53 -0
- package/dist/core/human/answer.js +119 -0
- package/dist/core/human/component-docs.d.ts +18 -0
- package/dist/core/human/component-docs.js +434 -0
- package/dist/core/human/convention.d.ts +17 -3
- package/dist/core/human/convention.js +36 -5
- package/dist/core/human/feedback-companion.d.ts +20 -0
- package/dist/core/human/feedback-companion.js +98 -0
- package/dist/core/human/feedback.d.ts +65 -0
- package/dist/core/human/feedback.js +116 -0
- package/dist/core/human/page-catalog.d.ts +56 -5
- package/dist/core/human/page-catalog.js +151 -22
- package/dist/core/human/page-errors.d.ts +4 -0
- package/dist/core/human/page-errors.js +7 -0
- package/dist/core/human/page-eval.d.ts +20 -0
- package/dist/core/human/page-eval.js +123 -0
- package/dist/core/human/page-schema.d.ts +48 -116
- package/dist/core/human/page-schema.js +100 -72
- package/dist/core/human/page-synth.d.ts +2 -4
- package/dist/core/human/page-synth.js +20 -7
- package/dist/core/human/page.d.ts +13 -21
- package/dist/core/human/page.js +147 -391
- package/dist/core/human/review-schema.d.ts +1 -0
- package/dist/core/human/review-schema.js +1 -1
- package/dist/core/human/root.d.ts +6 -2
- package/dist/core/human/root.js +12 -4
- package/dist/core/human/scan.d.ts +3 -3
- package/dist/core/human/scan.js +21 -21
- package/dist/core/human/summary.d.ts +2 -6
- package/dist/core/human/summary.js +3 -59
- package/dist/core/human/tickets.d.ts +22 -15
- package/dist/core/human/tickets.js +90 -28
- package/dist/core/human/types.d.ts +14 -2
- package/dist/core/keybindings/catalog.d.ts +2 -2
- package/dist/core/keybindings/catalog.js +1 -0
- package/dist/core/preview-registry.js +8 -10
- package/dist/core/profiles/default-binding.d.ts +4 -0
- package/dist/core/profiles/default-binding.js +24 -0
- package/dist/core/profiles/deletion-reservation.d.ts +8 -0
- package/dist/core/profiles/deletion-reservation.js +38 -0
- package/dist/core/profiles/manifest.d.ts +3 -0
- package/dist/core/profiles/manifest.js +12 -0
- package/dist/core/profiles/select.js +2 -2
- package/dist/core/review/realize.js +12 -4
- package/dist/core/review/store.d.ts +6 -0
- package/dist/core/review/store.js +31 -0
- package/dist/core/runtime/bearings.d.ts +0 -4
- package/dist/core/runtime/bearings.js +4 -62
- package/dist/core/runtime/bin-contributions.d.ts +44 -0
- package/dist/core/runtime/bin-contributions.js +186 -0
- package/dist/core/runtime/boot-root.js +6 -1
- package/dist/core/runtime/broker/client-registry.d.ts +4 -0
- package/dist/core/runtime/broker/client-registry.js +13 -1
- package/dist/core/runtime/broker/extension-abort.d.ts +1 -1
- package/dist/core/runtime/broker/extension-abort.js +4 -2
- package/dist/core/runtime/broker/fault-retry.js +4 -0
- package/dist/core/runtime/broker/frame-dispatch.d.ts +3 -1
- package/dist/core/runtime/broker/frame-dispatch.js +5 -6
- package/dist/core/runtime/broker/read-ops.d.ts +1 -1
- package/dist/core/runtime/broker/read-ops.js +2 -2
- package/dist/core/runtime/broker/rebind.d.ts +1 -0
- package/dist/core/runtime/broker/rebind.js +6 -1
- package/dist/core/runtime/broker-extension-render.js +19 -0
- package/dist/core/runtime/broker-protocol.d.ts +14 -0
- package/dist/core/runtime/broker.d.ts +1 -1
- package/dist/core/runtime/broker.js +25 -12
- package/dist/core/runtime/busy.d.ts +4 -3
- package/dist/core/runtime/busy.js +4 -3
- package/dist/core/runtime/front-door-env.d.ts +5 -0
- package/dist/core/runtime/front-door-env.js +16 -0
- package/dist/core/runtime/front-door.d.ts +1 -5
- package/dist/core/runtime/front-door.js +2 -5
- package/dist/core/runtime/host.d.ts +1 -1
- package/dist/core/runtime/invocation.d.ts +6 -0
- package/dist/core/runtime/invocation.js +10 -0
- package/dist/core/runtime/kickoff.js +34 -0
- package/dist/core/runtime/launch.d.ts +2 -6
- package/dist/core/runtime/node-read.js +4 -1
- package/dist/core/runtime/nodes.js +2 -0
- package/dist/core/runtime/promote.d.ts +1 -0
- package/dist/core/runtime/promote.js +3 -1
- package/dist/core/runtime/session-cycles.d.ts +6 -0
- package/dist/core/runtime/session-cycles.js +15 -1
- package/dist/core/runtime/session-visibility.d.ts +17 -8
- package/dist/core/runtime/session-visibility.js +19 -10
- package/dist/core/runtime/spawn-env.d.ts +9 -1
- package/dist/core/runtime/spawn-env.js +62 -2
- package/dist/core/runtime/spawn.js +4 -4
- package/dist/core/runtime/stop-guard.d.ts +2 -2
- package/dist/core/runtime/stop-guard.js +2 -2
- package/dist/core/runtime/stop-signals.d.ts +1 -1
- package/dist/core/runtime/stop-signals.js +2 -2
- package/dist/core/runtime/tmux-bindings.js +1 -1
- package/dist/core/session-model/session-state.d.ts +4 -3
- package/dist/core/session-model/session-state.js +14 -1
- package/dist/core/user-settings.d.ts +36 -2
- package/dist/core/user-settings.js +56 -14
- package/dist/core/worktree.d.ts +15 -45
- package/dist/core/worktree.js +59 -261
- package/dist/daemon/api/__tests__/seam/profile-delete.test.d.ts +1 -0
- package/dist/daemon/api/__tests__/seam/profile-delete.test.js +386 -0
- package/dist/daemon/api/handlers/bash-jobs.js +1 -1
- package/dist/daemon/api/handlers/broker-ops.js +9 -5
- package/dist/daemon/api/handlers/feedback-comments.d.ts +2 -0
- package/dist/daemon/api/handlers/feedback-comments.js +207 -0
- package/dist/daemon/api/handlers/human.js +24 -20
- package/dist/daemon/api/handlers/inbox.d.ts +10 -0
- package/dist/daemon/api/handlers/inbox.js +48 -109
- package/dist/daemon/api/handlers/profiles.js +11 -29
- package/dist/daemon/api/map.d.ts +6 -1
- package/dist/daemon/api/map.js +23 -0
- package/dist/daemon/api/server.js +3 -1
- package/dist/daemon/companion-retire.d.ts +2 -0
- package/dist/daemon/companion-retire.js +33 -0
- package/dist/daemon/cron-run.js +29 -17
- package/dist/daemon/crtrd.js +1 -1
- package/dist/daemon/fleet.d.ts +22 -10
- package/dist/daemon/fleet.js +43 -24
- package/dist/daemon/human/finish.d.ts +12 -6
- package/dist/daemon/human/finish.js +134 -54
- package/dist/daemon/human/sweep.js +9 -3
- package/dist/daemon/profile-delete.d.ts +7 -0
- package/dist/daemon/profile-delete.js +306 -0
- package/dist/daemon/reconcilers/broker-supervision.js +14 -9
- package/dist/daemon/reconcilers/dormant-inbox.js +11 -6
- package/dist/daemon/reconcilers/live-obligation.d.ts +21 -1
- package/dist/daemon/reconcilers/live-obligation.js +30 -2
- package/dist/daemon/reconcilers/storage-maintenance.d.ts +5 -0
- package/dist/daemon/reconcilers/storage-maintenance.js +28 -1
- package/dist/daemon/review/deliver.js +3 -3
- package/dist/daemon/review/finish.d.ts +0 -2
- package/dist/daemon/review/finish.js +1 -29
- package/dist/daemon/review/sweep.js +12 -1
- package/dist/pi-extensions/__tests__/canvas-bash-valve.test.js +56 -1
- package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +4 -4
- package/dist/pi-extensions/canvas-bash-valve.d.ts +9 -1
- package/dist/pi-extensions/canvas-bash-valve.js +31 -13
- package/dist/pi-extensions/canvas-inbox-watcher.js +1 -1
- package/dist/pi-extensions/canvas-review-boundary.d.ts +4 -0
- package/dist/pi-extensions/canvas-review-boundary.js +18 -9
- package/dist/pi-extensions/canvas-stophook.js +4 -4
- package/dist/shared/generated-context.js +2 -2
- package/dist/types.d.ts +61 -5
- package/dist/types.js +8 -2
- package/package.json +4 -4
- package/runtime.lock.json +5 -33
- package/scripts/install-runtime.mjs +19 -24
- package/dist/builtin-memory/init.md +0 -38
- package/dist/builtin-memory/plan.md +0 -19
- package/dist/builtin-memory/spec.md +0 -19
- package/dist/clients/attach/render/html-markdown.d.ts +0 -13
- package/dist/clients/attach/render/html-markdown.js +0 -206
- package/dist/commands/human/doc.js +0 -144
- package/dist/commands/human/inbox.d.ts +0 -2
- package/dist/core/human/__tests__/page-render.test.js +0 -49
- package/dist/core/human/markdown-html.d.ts +0 -8
- package/dist/core/human/markdown-html.js +0 -581
- package/dist/core/human/page-render.d.ts +0 -3
- package/dist/core/human/page-render.js +0 -131
- package/dist/pages/bundle.css +0 -1
- package/dist/pages/bundle.js +0 -1347
- package/dist/pages/comments.d.ts +0 -43
- package/dist/pages/comments.js +0 -125
- package/dist/pages/elements/cards.d.ts +0 -17
- package/dist/pages/elements/cards.js +0 -802
- package/dist/pages/elements/chart.js +0 -648
- package/dist/pages/elements/options.d.ts +0 -21
- package/dist/pages/elements/options.js +0 -629
- package/dist/pages/elements/pages.d.ts +0 -24
- package/dist/pages/elements/pages.js +0 -355
- package/dist/pages/elements/slot.d.ts +0 -21
- package/dist/pages/elements/slot.js +0 -105
- package/dist/pages/elements/table.d.ts +0 -19
- package/dist/pages/elements/table.js +0 -935
- package/dist/pages/elements/text.d.ts +0 -25
- package/dist/pages/elements/text.js +0 -968
- package/dist/pages/entry.d.ts +0 -8
- package/dist/pages/entry.js +0 -8
- package/dist/pages/host.d.ts +0 -158
- package/dist/pages/host.js +0 -182
- package/dist/pages/register.d.ts +0 -2
- package/dist/pages/register.js +0 -3
- package/dist/pages/slot-config.d.ts +0 -36
- package/dist/pages/slot-config.js +0 -50
- package/dist/pages/types.d.ts +0 -98
- package/dist/pages/types.js +0 -23
- /package/dist/{core/human/__tests__/page-render.test.d.ts → commands/__tests__/node-message.test.d.ts} +0 -0
- /package/dist/{pages/elements/chart.d.ts → core/__tests__/plugin-page-components.test.d.ts} +0 -0
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { pageComponentCatalog } from './page-catalog.js';
|
|
2
|
+
/** A product registers a lowercase-hyphen kind; its JSX tag is the PascalCase derivation
|
|
3
|
+
* (`worker-card` → `<WorkerCard>`), which page-catalog carries as the entry `tag`. */
|
|
4
|
+
function productTags(components) {
|
|
5
|
+
return new Map(pageComponentCatalog(components).filter((entry) => !entry.builtin).map((entry) => [entry.kind, entry.tag]));
|
|
6
|
+
}
|
|
7
|
+
const SLOT_RULES = `Component contract
|
|
8
|
+
- Props are the component's config, spread flat as JSX attributes; the component registers itself with the host at mount and enforces its own response shape.
|
|
9
|
+
- \`id\` is required, must match \`^[A-Za-z0-9_-]{1,64}$\`, and must be unique across the page.
|
|
10
|
+
- When a page has two or more response-bearing components, every one needs a nonempty \`label\`.
|
|
11
|
+
- Config objects and every documented nested object reject unknown fields.
|
|
12
|
+
- The page is rendered once at submit to build the ticket manifest, so every prop reaches it whatever its shape — mapped data, a helper component, a value from \`useState\`. Event handlers are dropped, because a manifest is JSON. The user's answer lands in \`response.json\` keyed by this \`id\`.`;
|
|
13
|
+
const COMMENT_SHAPE = `Comments are \`{id:string (nonempty), text:string, anchor:Anchor}\` and qualify the answer itself. \`Anchor\` is \`{kind:"option",optionId:string}\` or \`{kind:"row",rowId:string}\`; each component accepts only the anchors named next. Feedback on rendered page content — including the question text — is given by highlighting it on the page, never authored into a response.`;
|
|
14
|
+
const DISPLAY_RULES = `Display-only: it is not recorded in the page manifest and contributes nothing to the response. Every display component also takes \`className\` for Tailwind utilities, and standard shadcn props otherwise.`;
|
|
15
|
+
export const BUILTIN_COMPONENT_DOCS = [
|
|
16
|
+
{
|
|
17
|
+
names: ['Page'],
|
|
18
|
+
group: 'structural',
|
|
19
|
+
selectionLine: '`Page` — the page root. Every module default-exports a component whose root element is `<Page>`; it carries title and subtitle.',
|
|
20
|
+
contract: `\`<Page>\`
|
|
21
|
+
|
|
22
|
+
Structural root. The \`.tsx\` module's default export returns exactly one \`<Page>\`; it takes no \`id\` and contributes no response.
|
|
23
|
+
|
|
24
|
+
Props
|
|
25
|
+
- \`title\` — required nonempty string. The headline the user sees in their inbox and in the terminal summary.
|
|
26
|
+
- \`subtitle?:string\` — one line under the title.
|
|
27
|
+
|
|
28
|
+
Rules
|
|
29
|
+
- Children are page content: registry components, plain elements with Tailwind classes, or \`<Step>\` elements for a multi-step page.
|
|
30
|
+
- Never render submit or dismiss chrome — the host owns it.
|
|
31
|
+
|
|
32
|
+
Example
|
|
33
|
+
\`\`\`jsx
|
|
34
|
+
export default function ShipGate() {
|
|
35
|
+
return (
|
|
36
|
+
<Page title="Ship 0.4.0?" subtitle="Two gates left.">
|
|
37
|
+
<UserQuestion id="lane" label="Release lane" body="The audit closes tomorrow." mode="single"
|
|
38
|
+
options={[{ id: 'now', label: 'Ship now' }, { id: 'hold', label: 'Hold for the audit' }]} />
|
|
39
|
+
</Page>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
\`\`\``,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
names: ['Step'],
|
|
46
|
+
group: 'structural',
|
|
47
|
+
selectionLine: '`Step` — one step of a multi-step page. Use as a direct child of `<Page>` when the user works through content in sequence.',
|
|
48
|
+
contract: `\`<Step>\`
|
|
49
|
+
|
|
50
|
+
Structural. One step of a multi-step page. It takes no \`id\` and contributes no response.
|
|
51
|
+
|
|
52
|
+
Props
|
|
53
|
+
- \`title?:string\` — the step heading.
|
|
54
|
+
|
|
55
|
+
Rules
|
|
56
|
+
- \`<Step>\` elements are children of \`<Page>\`; step order is source order, and the ticket manifest records the step count.
|
|
57
|
+
- A single-step page needs no \`<Step>\` at all — put the content directly in \`<Page>\`.
|
|
58
|
+
|
|
59
|
+
Example
|
|
60
|
+
\`\`\`jsx
|
|
61
|
+
<Page title="Migration plan">
|
|
62
|
+
<Step title="What changes"><Card>…</Card></Step>
|
|
63
|
+
<Step title="Approve"><UserQuestion id="go" label="Proceed?" body="Applies on the next deploy." mode="single" options={lanes} /></Step>
|
|
64
|
+
</Page>
|
|
65
|
+
\`\`\``,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
names: ['UserQuestion'],
|
|
69
|
+
group: 'slot',
|
|
70
|
+
selectionLine: '`UserQuestion` — known-alternative picker (wire kind `options`). Use when the user picks among known alternatives.',
|
|
71
|
+
contract: `\`<UserQuestion>\` — wire kind \`options\`
|
|
72
|
+
|
|
73
|
+
Response-bearing. The response is \`{selectedOptionIds:string[], comments:Comment[], freetext?:string}\`.
|
|
74
|
+
|
|
75
|
+
${SLOT_RULES}
|
|
76
|
+
|
|
77
|
+
Props
|
|
78
|
+
- \`id\` — required component id.
|
|
79
|
+
- \`options\` — required nonempty array of \`{id:string (nonempty, unique), label:string (nonempty), description?:string}\`.
|
|
80
|
+
- \`mode\` — required: \`single\` or \`multi\`.
|
|
81
|
+
- \`label\` — required nonempty string: the question itself, drawn as the bold first line.
|
|
82
|
+
- \`body\` — required nonempty string: markdown rendered above the choices — the context under the question a label cannot carry. Display-only; contributes nothing to the response.
|
|
83
|
+
- \`allowFreetext?:boolean\`, \`freetextLabel?:string\`, \`freetextPlaceholder?:string\` — adds a final "something else" choice, drawn as one more row with its own control and a field to write in rather than as a note on the question. It is exclusive on a \`single\` question (choosing it clears the selection, and choosing an option clears what was written) and just another box on a \`multi\` one. \`freetextLabel\` names that row, default "Something else".
|
|
84
|
+
|
|
85
|
+
Response rules
|
|
86
|
+
- \`selectedOptionIds\` contains unique known option ids. A \`single\` response has at most one selected id and requires exactly one selection or nonempty allowed \`freetext\`; a \`multi\` response requires a selection, comment, or nonempty allowed \`freetext\`.
|
|
87
|
+
- \`freetext\` is accepted only when \`allowFreetext:true\`.
|
|
88
|
+
- ${COMMENT_SHAPE} UserQuestion accepts only \`option\` anchors; \`optionId\` must name a configured option.
|
|
89
|
+
|
|
90
|
+
Example
|
|
91
|
+
\`\`\`jsx
|
|
92
|
+
<UserQuestion id="lane" label="Release lane" body="The audit closes tomorrow." mode="single" allowFreetext
|
|
93
|
+
freetextLabel="Something else"
|
|
94
|
+
options={[
|
|
95
|
+
{ id: 'now', label: 'Ship now', description: 'Both gates are green.' },
|
|
96
|
+
{ id: 'hold', label: 'Hold for the audit' },
|
|
97
|
+
]} />
|
|
98
|
+
\`\`\``,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
names: ['UserText'],
|
|
102
|
+
group: 'slot',
|
|
103
|
+
selectionLine: '`UserText` — writing surface (wire kind `text`). Use when you need prose back from the user — a value they write, or a draft they hand back revised; use `singleLine` for a subject, name, or URL. Prose the user only reads is a plain element, never this.',
|
|
104
|
+
contract: `\`<UserText>\` — wire kind \`text\`
|
|
105
|
+
|
|
106
|
+
Response-bearing. The response is \`{text:string, edited:boolean}\`: the text the user hands back, and whether they changed what you drafted.
|
|
107
|
+
|
|
108
|
+
It collects prose, it does not present it. Text the user only reads is a plain element with Tailwind classes — \`<p className="whitespace-pre-wrap">\` — which renders the same and asks nothing of them. Reach for it only when the answer you need is the string left in the field. Feedback on the rendered text is given by highlighting it on the page, not authored into the response.
|
|
109
|
+
|
|
110
|
+
${SLOT_RULES}
|
|
111
|
+
|
|
112
|
+
Props
|
|
113
|
+
- \`id\` — required component id.
|
|
114
|
+
- \`initialText\` — required string, including an empty string.
|
|
115
|
+
- \`label?:string\` — the component's label; required by the multi-response page rule.
|
|
116
|
+
- \`placeholder?:string\` — the hint shown while the field is empty.
|
|
117
|
+
- \`singleLine?:boolean\` — swaps the writing surface for one compact form field.
|
|
118
|
+
|
|
119
|
+
The field is always writable; there is no read-only mode, and the config rejects an \`editable\` prop.
|
|
120
|
+
|
|
121
|
+
Example
|
|
122
|
+
\`\`\`jsx
|
|
123
|
+
<UserText id="subject" label="Subject line" singleLine initialText="Following up on the audit" />
|
|
124
|
+
\`\`\``,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
names: ['UserTable'],
|
|
128
|
+
group: 'slot',
|
|
129
|
+
selectionLine: '`UserTable` — selectable rows and columns (wire kind `table`). Use when the user inspects or selects tabular data; for a table the user only reads, `Table` is lighter.',
|
|
130
|
+
contract: `\`<UserTable>\` — wire kind \`table\`
|
|
131
|
+
|
|
132
|
+
Display-only when both selection modes are \`none\`; response-bearing when \`rowSelect\` or \`columnSelect\` is \`single\` or \`multi\`. Its response when selectable is \`{selectedRowIds:string[], selectedColumnIds:string[], comments:Comment[]}\`.
|
|
133
|
+
|
|
134
|
+
${SLOT_RULES}
|
|
135
|
+
|
|
136
|
+
Props
|
|
137
|
+
- \`id\` — required component id.
|
|
138
|
+
- \`columns\` — required nonempty array of \`{id:string (nonempty, unique), label:string (nonempty), align?:"left"|"right", mono?:boolean}\`.
|
|
139
|
+
- \`rows?:\` array of \`{id:string (nonempty, unique), cells:Record<string,string|number|boolean|null>}\`. Rows are inline — map your data into this array in the module.
|
|
140
|
+
- \`rowSelect?:"none"|"single"|"multi"\` — defaults to \`none\`.
|
|
141
|
+
- \`columnSelect?:"none"|"single"|"multi"\` — defaults to \`none\`.
|
|
142
|
+
- \`label?:string\` — the component's label; required by the multi-response page rule when the table is selectable.
|
|
143
|
+
- \`body?:string\` — optional markdown rendered above the table. Display-only; it contributes nothing to the response.
|
|
144
|
+
|
|
145
|
+
Response rules when selectable
|
|
146
|
+
- Selected row and column ids are unique and must name configured ids. \`none\` permits no corresponding selected ids; \`single\` permits at most one.
|
|
147
|
+
- ${COMMENT_SHAPE} UserTable accepts only \`row\` anchors, which must name configured row ids.
|
|
148
|
+
|
|
149
|
+
Example
|
|
150
|
+
\`\`\`jsx
|
|
151
|
+
<UserTable id="picks" label="Which findings do we fix?" rowSelect="multi"
|
|
152
|
+
columns={[{ id: 'finding', label: 'Finding' }, { id: 'severity', label: 'Severity' }]}
|
|
153
|
+
rows={findings.map((f) => ({ id: f.id, cells: { finding: f.title, severity: f.severity } }))} />
|
|
154
|
+
\`\`\``,
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
names: ['UserCards'],
|
|
158
|
+
group: 'slot',
|
|
159
|
+
selectionLine: '`UserCards` — visual item picker (wire kind `cards`). Use when the user chooses among rich, card-shaped alternatives.',
|
|
160
|
+
contract: `\`<UserCards>\` — wire kind \`cards\`
|
|
161
|
+
|
|
162
|
+
Response-bearing. The response is \`{selectedCardIds:string[]}\`. Feedback on a card's content is given by highlighting it on the page, not authored into the response.
|
|
163
|
+
|
|
164
|
+
${SLOT_RULES}
|
|
165
|
+
|
|
166
|
+
Props
|
|
167
|
+
- \`id\` — required component id.
|
|
168
|
+
- \`cards?:\` array of \`{id:string (nonempty, unique), title:string (nonempty), subtitle?:string, body?:string, tag?:string}\`. Cards are inline — map your data into this array in the module.
|
|
169
|
+
- \`mode\` — required: \`single\` or \`multi\`.
|
|
170
|
+
- \`columns?:2|3\` — optional layout column count.
|
|
171
|
+
- \`label?:string\` — the component's label; required by the multi-response page rule.
|
|
172
|
+
- \`body?:string\` — optional markdown rendered above the cards. Display-only; it contributes nothing to the response.
|
|
173
|
+
|
|
174
|
+
Response rules
|
|
175
|
+
- \`selectedCardIds\` contains unique configured card ids. \`single\` permits at most one selected card.
|
|
176
|
+
|
|
177
|
+
Example
|
|
178
|
+
\`\`\`jsx
|
|
179
|
+
<UserCards id="candidate" label="Who do we interview next?" mode="single" columns={3}
|
|
180
|
+
cards={[{ id: 'a', title: 'Dana Whitfield', subtitle: 'Staff eng', tag: 'referral' }]} />
|
|
181
|
+
\`\`\``,
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
names: ['Chart'],
|
|
185
|
+
group: 'slot',
|
|
186
|
+
selectionLine: '`Chart` — data visualization (wire kind `chart`, display-only). Use when a trend or comparison reads better than prose or a table.',
|
|
187
|
+
contract: `\`<Chart>\` — wire kind \`chart\`
|
|
188
|
+
|
|
189
|
+
Display-only; it has no response. It still takes an \`id\` when you want the chart recorded in the ticket manifest.
|
|
190
|
+
|
|
191
|
+
Props
|
|
192
|
+
- \`kind\` — required: \`line\`, \`bar\`, or \`area\`.
|
|
193
|
+
- \`x\` — required nonempty string naming the x field.
|
|
194
|
+
- \`y\` — required nonempty array of \`{field:string (nonempty), label?:string}\` series.
|
|
195
|
+
- \`title?:string\`, \`data?:\` array of JSON row objects. Rows are inline — build them in the module.
|
|
196
|
+
- \`id?:string\` — component id, subject to the same id rules as a response-bearing component when present.
|
|
197
|
+
|
|
198
|
+
Example
|
|
199
|
+
\`\`\`jsx
|
|
200
|
+
<Chart kind="line" title="Build minutes" x="day"
|
|
201
|
+
y={[{ field: 'minutes', label: 'CI minutes' }]}
|
|
202
|
+
data={days.map((d) => ({ day: d.label, minutes: d.minutes }))} />
|
|
203
|
+
\`\`\``,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
names: ['Card', 'CardHeader', 'CardTitle', 'CardDescription', 'CardContent', 'CardFooter'],
|
|
207
|
+
group: 'display',
|
|
208
|
+
selectionLine: '`Card`, `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter` — the standard surface for grouping a block of page content.',
|
|
209
|
+
contract: `\`<Card>\` family — \`Card\`, \`CardHeader\`, \`CardTitle\`, \`CardDescription\`, \`CardContent\`, \`CardFooter\`
|
|
210
|
+
|
|
211
|
+
${DISPLAY_RULES}
|
|
212
|
+
|
|
213
|
+
Props
|
|
214
|
+
- All six take \`className\` and children only; the nesting carries the meaning.
|
|
215
|
+
- Order inside \`<Card>\`: \`<CardHeader>\` (holding \`<CardTitle>\` and optional \`<CardDescription>\`), then \`<CardContent>\`, then optional \`<CardFooter>\`.
|
|
216
|
+
|
|
217
|
+
Example
|
|
218
|
+
\`\`\`jsx
|
|
219
|
+
<Card>
|
|
220
|
+
<CardHeader><CardTitle>Release gates</CardTitle><CardDescription>Two left.</CardDescription></CardHeader>
|
|
221
|
+
<CardContent><Chart kind="bar" x="gate" y={[{ field: 'minutes' }]} data={gates} /></CardContent>
|
|
222
|
+
</Card>
|
|
223
|
+
\`\`\``,
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
names: ['Badge'],
|
|
227
|
+
group: 'display',
|
|
228
|
+
selectionLine: '`Badge` — small status or category chip.',
|
|
229
|
+
contract: `\`<Badge>\`
|
|
230
|
+
|
|
231
|
+
${DISPLAY_RULES}
|
|
232
|
+
|
|
233
|
+
Props
|
|
234
|
+
- \`variant?:"default"|"secondary"|"destructive"|"outline"\`.
|
|
235
|
+
- \`className\`, children.
|
|
236
|
+
|
|
237
|
+
Example
|
|
238
|
+
\`\`\`jsx
|
|
239
|
+
<Badge variant="destructive">blocked</Badge>
|
|
240
|
+
\`\`\``,
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
names: ['Button'],
|
|
244
|
+
group: 'display',
|
|
245
|
+
selectionLine: '`Button` — an in-page control. Never a submit: the host owns submit and dismiss.',
|
|
246
|
+
contract: `\`<Button>\`
|
|
247
|
+
|
|
248
|
+
${DISPLAY_RULES} A page never renders its own submit or dismiss — use it for in-page interaction only (revealing detail, switching a local view).
|
|
249
|
+
|
|
250
|
+
Props — this Button does NOT take shadcn's \`variant\`; a \`variant\` prop is silently ignored.
|
|
251
|
+
- \`tone?:"brand"|"neutral"|"danger"\` — what the action means. Unstated, it resolves to \`brand\` on a solid button and \`neutral\` at every other weight.
|
|
252
|
+
- \`weight?:"solid"|"outline"|"quiet"|"link"\` — how loudly it reads. Default \`solid\`. \`tone="danger"\` has no \`link\` weight — that pair warns and renders untoned.
|
|
253
|
+
- \`size?:"xs"|"sm"|"default"|"lg"\` (icon-only circles: \`"icon-xs"|"icon-sm"|"icon"|"icon-lg"\`). Default \`default\`.
|
|
254
|
+
- \`width?:"auto"|"full"\`. Default \`auto\`.
|
|
255
|
+
- \`loading?:boolean\`, \`onClick?:(event) => void\`, \`disabled?:boolean\`, \`className\`, children.
|
|
256
|
+
|
|
257
|
+
Example
|
|
258
|
+
\`\`\`jsx
|
|
259
|
+
<Button tone="neutral" weight="outline" size="sm" onClick={() => setShowAll(true)}>Show all 12</Button>
|
|
260
|
+
\`\`\``,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
names: ['Table', 'TableHeader', 'TableBody', 'TableRow', 'TableHead', 'TableCell', 'TableCaption'],
|
|
264
|
+
group: 'display',
|
|
265
|
+
selectionLine: '`Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`, `TableCaption` — read-only tabular layout. Use `UserTable` when the user must select rows or columns.',
|
|
266
|
+
contract: `\`<Table>\` family — \`Table\`, \`TableHeader\`, \`TableBody\`, \`TableRow\`, \`TableHead\`, \`TableCell\`, \`TableCaption\`
|
|
267
|
+
|
|
268
|
+
${DISPLAY_RULES} It is layout only: nothing in it is selectable and nothing reaches the response. For selection, use \`UserTable\`.
|
|
269
|
+
|
|
270
|
+
Props
|
|
271
|
+
- All take \`className\` and children. \`TableHead\` and \`TableCell\` additionally take \`colSpan\`.
|
|
272
|
+
- Structure: \`<Table>\` wraps optional \`<TableCaption>\`, \`<TableHeader>\` (rows of \`<TableHead>\`), and \`<TableBody>\` (rows of \`<TableCell>\`).
|
|
273
|
+
|
|
274
|
+
Example
|
|
275
|
+
\`\`\`jsx
|
|
276
|
+
<Table>
|
|
277
|
+
<TableHeader><TableRow><TableHead>Gate</TableHead><TableHead>State</TableHead></TableRow></TableHeader>
|
|
278
|
+
<TableBody>{gates.map((g) => (
|
|
279
|
+
<TableRow key={g.id}><TableCell>{g.name}</TableCell><TableCell>{g.state}</TableCell></TableRow>
|
|
280
|
+
))}</TableBody>
|
|
281
|
+
</Table>
|
|
282
|
+
\`\`\``,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
names: ['Tabs', 'TabsList', 'TabsTrigger', 'TabsContent'],
|
|
286
|
+
group: 'display',
|
|
287
|
+
selectionLine: '`Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` — parallel views of the same subject behind one control.',
|
|
288
|
+
contract: `\`<Tabs>\` family — \`Tabs\`, \`TabsList\`, \`TabsTrigger\`, \`TabsContent\`
|
|
289
|
+
|
|
290
|
+
${DISPLAY_RULES} Which tab the user opened is not part of the response.
|
|
291
|
+
|
|
292
|
+
Props
|
|
293
|
+
- \`Tabs\`: \`defaultValue?:string\`, \`value?:string\`, \`onValueChange?:(value:string) => void\`, \`className\`.
|
|
294
|
+
- \`TabsTrigger\` and \`TabsContent\`: \`value\` — required, and the two match by that value.
|
|
295
|
+
- \`TabsList\`: \`className\` and children.
|
|
296
|
+
|
|
297
|
+
Example
|
|
298
|
+
\`\`\`jsx
|
|
299
|
+
<Tabs defaultValue="diff">
|
|
300
|
+
<TabsList><TabsTrigger value="diff">Diff</TabsTrigger><TabsTrigger value="risk">Risk</TabsTrigger></TabsList>
|
|
301
|
+
<TabsContent value="diff">…</TabsContent>
|
|
302
|
+
<TabsContent value="risk">…</TabsContent>
|
|
303
|
+
</Tabs>
|
|
304
|
+
\`\`\``,
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
names: ['Separator'],
|
|
308
|
+
group: 'display',
|
|
309
|
+
selectionLine: '`Separator` — a rule between sections.',
|
|
310
|
+
contract: `\`<Separator>\`
|
|
311
|
+
|
|
312
|
+
${DISPLAY_RULES}
|
|
313
|
+
|
|
314
|
+
Props
|
|
315
|
+
- \`orientation?:"horizontal"|"vertical"\` — defaults to \`horizontal\`.
|
|
316
|
+
- \`className\`.
|
|
317
|
+
|
|
318
|
+
Example
|
|
319
|
+
\`\`\`jsx
|
|
320
|
+
<Separator className="my-4" />
|
|
321
|
+
\`\`\``,
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
names: ['Alert', 'AlertTitle', 'AlertDescription'],
|
|
325
|
+
group: 'display',
|
|
326
|
+
selectionLine: '`Alert`, `AlertTitle`, `AlertDescription` — a callout for the one thing the user must not miss.',
|
|
327
|
+
contract: `\`<Alert>\` family — \`Alert\`, \`AlertTitle\`, \`AlertDescription\`
|
|
328
|
+
|
|
329
|
+
${DISPLAY_RULES}
|
|
330
|
+
|
|
331
|
+
Props
|
|
332
|
+
- \`Alert\`: \`variant?:"default"|"destructive"\`, \`className\`.
|
|
333
|
+
- \`AlertTitle\` and \`AlertDescription\`: \`className\` and children.
|
|
334
|
+
|
|
335
|
+
Example
|
|
336
|
+
\`\`\`jsx
|
|
337
|
+
<Alert variant="destructive">
|
|
338
|
+
<AlertTitle>Migration is irreversible</AlertTitle>
|
|
339
|
+
<AlertDescription>The old rows are dropped, not archived.</AlertDescription>
|
|
340
|
+
</Alert>
|
|
341
|
+
\`\`\``,
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
names: ['Progress'],
|
|
345
|
+
group: 'display',
|
|
346
|
+
selectionLine: '`Progress` — a completion bar for a known ratio.',
|
|
347
|
+
contract: `\`<Progress>\`
|
|
348
|
+
|
|
349
|
+
${DISPLAY_RULES}
|
|
350
|
+
|
|
351
|
+
Props
|
|
352
|
+
- \`value?:number\` — 0 to 100.
|
|
353
|
+
- \`className\`.
|
|
354
|
+
|
|
355
|
+
Example
|
|
356
|
+
\`\`\`jsx
|
|
357
|
+
<Progress value={Math.round((done / total) * 100)} />
|
|
358
|
+
\`\`\``,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
names: ['Accordion', 'AccordionItem', 'AccordionTrigger', 'AccordionContent'],
|
|
362
|
+
group: 'display',
|
|
363
|
+
selectionLine: '`Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent` — supporting detail the user opens only if they want it.',
|
|
364
|
+
contract: `\`<Accordion>\` family — \`Accordion\`, \`AccordionItem\`, \`AccordionTrigger\`, \`AccordionContent\`
|
|
365
|
+
|
|
366
|
+
${DISPLAY_RULES}
|
|
367
|
+
|
|
368
|
+
Props
|
|
369
|
+
- \`Accordion\`: \`type\` — required: \`single\` or \`multiple\`; \`collapsible?:boolean\`, \`defaultValue?:string|string[]\`, \`className\`.
|
|
370
|
+
- \`AccordionItem\`: \`value\` — required, unique within the accordion.
|
|
371
|
+
- \`AccordionTrigger\` and \`AccordionContent\`: \`className\` and children.
|
|
372
|
+
|
|
373
|
+
Example
|
|
374
|
+
\`\`\`jsx
|
|
375
|
+
<Accordion type="single" collapsible>
|
|
376
|
+
<AccordionItem value="why">
|
|
377
|
+
<AccordionTrigger>Why this is blocked</AccordionTrigger>
|
|
378
|
+
<AccordionContent>The audit job has not run since Tuesday.</AccordionContent>
|
|
379
|
+
</AccordionItem>
|
|
380
|
+
</Accordion>
|
|
381
|
+
\`\`\``,
|
|
382
|
+
},
|
|
383
|
+
];
|
|
384
|
+
const docsByName = new Map(BUILTIN_COMPONENT_DOCS.flatMap((doc) => doc.names.map((name) => [name, doc])));
|
|
385
|
+
/** The structural and response-bearing rows — the ones that decide what a page can ask. `human -h` carries these;
|
|
386
|
+
* the display set is one line there and the full catalog is `human components list`. */
|
|
387
|
+
export const CORE_COMPONENT_SELECTION_LINES = [...BUILTIN_COMPONENT_DOCS.filter((doc) => doc.group !== 'display').map((doc) => doc.selectionLine), '`usePageHost` — reserved host hook. Its product-supplied members are opaque to crouter; consult the owning product documentation.'];
|
|
388
|
+
export function componentDoc(name) {
|
|
389
|
+
return docsByName.get(name);
|
|
390
|
+
}
|
|
391
|
+
export function componentNames(productComponents) {
|
|
392
|
+
return [...BUILTIN_COMPONENT_DOCS.flatMap((doc) => [...doc.names]), ...productTags(productComponents).values()];
|
|
393
|
+
}
|
|
394
|
+
const GROUP_HEADINGS = {
|
|
395
|
+
structural: 'Structural — every page is built from these.',
|
|
396
|
+
slot: 'Response — each is recorded in the page manifest under its wire kind, and their answers are the page response.',
|
|
397
|
+
display: 'Display — shadcn components with their standard props, plus `className` for Tailwind. They contribute nothing to the response.',
|
|
398
|
+
};
|
|
399
|
+
export function renderComponentCatalog(productComponents) {
|
|
400
|
+
const builtinCount = BUILTIN_COMPONENT_DOCS.reduce((total, doc) => total + doc.names.length, 0);
|
|
401
|
+
const lines = [`<components count="${builtinCount + productComponents.length}">`];
|
|
402
|
+
for (const group of ['structural', 'slot', 'display']) {
|
|
403
|
+
lines.push(GROUP_HEADINGS[group], ...BUILTIN_COMPONENT_DOCS.filter((doc) => doc.group === group).map((doc) => doc.selectionLine), '');
|
|
404
|
+
}
|
|
405
|
+
const tags = productTags(productComponents);
|
|
406
|
+
if (productComponents.length > 0)
|
|
407
|
+
lines.push('Product-registered — registry components this product ships. Write each one under the tag shown; a `(kind …)` suffix names its lowercase-hyphen wire kind.');
|
|
408
|
+
else
|
|
409
|
+
lines.pop();
|
|
410
|
+
for (const component of productComponents) {
|
|
411
|
+
const tag = tags.get(component.kind) ?? component.kind;
|
|
412
|
+
const label = tag === component.kind ? `\`${tag}\`` : `\`${tag}\` (kind \`${component.kind}\`)`;
|
|
413
|
+
if (component.description === undefined && component.useWhen === undefined) {
|
|
414
|
+
lines.push(`${label} — ${component.display === true ? 'product-registered; display-only' : 'product-registered'}`);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const details = [component.description, component.useWhen === undefined ? undefined : `${component.useWhen[0].toUpperCase()}${component.useWhen.slice(1)}`, ...(component.display === true ? ['Display-only'] : [])].filter((detail) => detail !== undefined);
|
|
418
|
+
lines.push(`${label} — ${details.join('. ')}`);
|
|
419
|
+
}
|
|
420
|
+
lines.push('</components>');
|
|
421
|
+
return lines.join('\n');
|
|
422
|
+
}
|
|
423
|
+
export function renderComponentContract(name, productComponents) {
|
|
424
|
+
const builtin = componentDoc(name);
|
|
425
|
+
if (builtin !== undefined)
|
|
426
|
+
return builtin.contract;
|
|
427
|
+
const tags = productTags(productComponents);
|
|
428
|
+
const productComponent = productComponents.find((component) => component.kind === name || tags.get(component.kind) === name);
|
|
429
|
+
if (productComponent === undefined)
|
|
430
|
+
return undefined;
|
|
431
|
+
const tag = tags.get(productComponent.kind) ?? productComponent.kind;
|
|
432
|
+
const heading = `\`<${tag}>\` — product-registered, wire kind \`${productComponent.kind}\``;
|
|
433
|
+
return `${heading}\n\n${productComponent.doc ?? `product-registered${productComponent.display === true ? '; display-only' : '; config is passed through unvalidated (unvalidated: true)'} — see the product's own docs`}`;
|
|
434
|
+
}
|
|
@@ -1,17 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/** The authored page source path, derived from the persisted dialect. */
|
|
2
|
+
export declare function pagePath(dir: string, dialect?: 'jsx' | 'html'): string;
|
|
3
|
+
/** The compiled module the Gateway evaluates for JSX pages. */
|
|
4
|
+
export declare function pageModulePath(dir: string): string;
|
|
4
5
|
export declare function pageManifestPath(dir: string): string;
|
|
5
6
|
export declare function reviewPath(dir: string): string;
|
|
6
7
|
export declare function responsePath(dir: string): string;
|
|
7
8
|
export declare function progressPath(dir: string): string;
|
|
8
9
|
export declare function claimPath(dir: string): string;
|
|
10
|
+
export declare function replyRoutePath(dir: string): string;
|
|
11
|
+
export interface TicketReplyRoute {
|
|
12
|
+
bridge_node_id: string;
|
|
13
|
+
}
|
|
14
|
+
/** A reply route exists only when completing the ticket must finish a human
|
|
15
|
+
* bridge and fan its result back to subscribers. Its absence is the complete
|
|
16
|
+
* representation of a standalone page. */
|
|
17
|
+
export declare function readTicketReplyRoute(dir: string): TicketReplyRoute | null;
|
|
18
|
+
export declare function bindTicketReplyRoute(dir: string, bridgeNodeId: string): void;
|
|
9
19
|
export type InteractionState = 'pending' | 'claimed' | 'resolved' | 'missing';
|
|
10
20
|
export declare function interactionState(dir: string): InteractionState;
|
|
11
21
|
export declare function isResolved(dir: string): boolean;
|
|
12
22
|
export declare function isClaimed(dir: string): boolean;
|
|
13
23
|
/** Resolve and verify a ticket directory without a registry. */
|
|
14
24
|
export declare function requireTicket(dir: string): string;
|
|
25
|
+
/** Replace one file's contents in a single rename, so a concurrent reader —
|
|
26
|
+
* the tmux inbox watcher, or crtrd serving a ticket read — sees either the old
|
|
27
|
+
* bytes or the new ones and never a truncated file mid-write. */
|
|
28
|
+
export declare function atomicWriteText(path: string, contents: string): void;
|
|
15
29
|
export declare function atomicWriteJson(path: string, value: unknown): void;
|
|
16
30
|
/** Publish one immutable JSON record without replacing an existing winner. */
|
|
17
31
|
export declare function publishJsonExclusive(path: string, value: unknown): boolean;
|
|
@@ -1,12 +1,37 @@
|
|
|
1
|
-
import { existsSync, linkSync, mkdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
1
|
+
import { existsSync, linkSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
|
-
/** The authored page source,
|
|
4
|
-
export function pagePath(dir, dialect) { return `${dir}/page.${dialect}`; }
|
|
3
|
+
/** The authored page source path, derived from the persisted dialect. */
|
|
4
|
+
export function pagePath(dir, dialect = 'jsx') { return `${dir}/page.${dialect === 'jsx' ? 'tsx' : 'html'}`; }
|
|
5
|
+
/** The compiled module the Gateway evaluates for JSX pages. */
|
|
6
|
+
export function pageModulePath(dir) { return `${dir}/page.js`; }
|
|
5
7
|
export function pageManifestPath(dir) { return `${dir}/page.json`; }
|
|
6
8
|
export function reviewPath(dir) { return `${dir}/review.json`; }
|
|
7
9
|
export function responsePath(dir) { return `${dir}/response.json`; }
|
|
8
10
|
export function progressPath(dir) { return `${dir}/progress.json`; }
|
|
9
11
|
export function claimPath(dir) { return `${dir}/claim.json`; }
|
|
12
|
+
export function replyRoutePath(dir) { return `${dir}/reply-route.json`; }
|
|
13
|
+
/** A reply route exists only when completing the ticket must finish a human
|
|
14
|
+
* bridge and fan its result back to subscribers. Its absence is the complete
|
|
15
|
+
* representation of a standalone page. */
|
|
16
|
+
export function readTicketReplyRoute(dir) {
|
|
17
|
+
const path = replyRoutePath(dir);
|
|
18
|
+
if (!existsSync(path))
|
|
19
|
+
return null;
|
|
20
|
+
let raw;
|
|
21
|
+
try {
|
|
22
|
+
raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new Error(`invalid ticket reply route: ${path}`);
|
|
26
|
+
}
|
|
27
|
+
const bridgeNodeId = raw?.bridge_node_id;
|
|
28
|
+
if (typeof bridgeNodeId !== 'string' || bridgeNodeId === '')
|
|
29
|
+
throw new Error(`invalid ticket reply route: ${path}`);
|
|
30
|
+
return { bridge_node_id: bridgeNodeId };
|
|
31
|
+
}
|
|
32
|
+
export function bindTicketReplyRoute(dir, bridgeNodeId) {
|
|
33
|
+
atomicWriteJson(replyRoutePath(dir), { bridge_node_id: bridgeNodeId });
|
|
34
|
+
}
|
|
10
35
|
export function interactionState(dir) {
|
|
11
36
|
if (!existsSync(pageManifestPath(dir)) && !existsSync(reviewPath(dir)))
|
|
12
37
|
return 'missing';
|
|
@@ -24,12 +49,18 @@ export function requireTicket(dir) {
|
|
|
24
49
|
}
|
|
25
50
|
return canonicalDir;
|
|
26
51
|
}
|
|
27
|
-
|
|
52
|
+
/** Replace one file's contents in a single rename, so a concurrent reader —
|
|
53
|
+
* the tmux inbox watcher, or crtrd serving a ticket read — sees either the old
|
|
54
|
+
* bytes or the new ones and never a truncated file mid-write. */
|
|
55
|
+
export function atomicWriteText(path, contents) {
|
|
28
56
|
const tmp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
29
57
|
mkdirSync(dirname(path), { recursive: true });
|
|
30
|
-
writeFileSync(tmp,
|
|
58
|
+
writeFileSync(tmp, contents, { mode: 0o600 });
|
|
31
59
|
renameSync(tmp, path);
|
|
32
60
|
}
|
|
61
|
+
export function atomicWriteJson(path, value) {
|
|
62
|
+
atomicWriteText(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
63
|
+
}
|
|
33
64
|
/** Publish one immutable JSON record without replacing an existing winner. */
|
|
34
65
|
export function publishJsonExclusive(path, value) {
|
|
35
66
|
const tmp = `${path}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function feedbackCompanionBranchPath(dir: string): string;
|
|
2
|
+
/** Hex sha256 of the ticket's page document, recorded at first comment so the
|
|
3
|
+
* settle-time completion can say whether the page changed during feedback. */
|
|
4
|
+
export declare function pageDocumentSha256(dir: string): string;
|
|
5
|
+
/** A companion node nothing on the ticket points at must not stay resident:
|
|
6
|
+
* the comment that would have bound it was refused, failed to commit, or lost
|
|
7
|
+
* the first-comment race to a concurrently recorded companion. */
|
|
8
|
+
export declare function discardFeedbackCompanion(nodeId: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* Fork the sending node's current state into the ticket-bound companion and
|
|
11
|
+
* wait for its durable boundary bind. Throws on any failure — the caller
|
|
12
|
+
* refuses the comment so no feedback is accepted without a visible
|
|
13
|
+
* conversation able to receive it.
|
|
14
|
+
*/
|
|
15
|
+
export declare function realizeFeedbackCompanion(args: {
|
|
16
|
+
ticketId: string;
|
|
17
|
+
dir: string;
|
|
18
|
+
senderNodeId: string;
|
|
19
|
+
title: string;
|
|
20
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Feedback-companion realization — the one path that creates a page ticket's
|
|
2
|
+
// companion conversation. The first feedback comment forks the page's sending
|
|
3
|
+
// node at its then-current state; the ticket then records that companion and
|
|
4
|
+
// every later comment reaches the same conversation. No fork point is captured
|
|
5
|
+
// at page send and no placeholder exists before feedback.
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
import { resolve } from 'node:path';
|
|
9
|
+
import { getNode } from '../canvas/index.js';
|
|
10
|
+
import { awaitCompanionBind, captureOrigin } from '../review/companion.js';
|
|
11
|
+
import { COMPANION_BIND_DEADLINE_MS } from '../review/types.js';
|
|
12
|
+
import { SessionManager } from '../runtime/broker-sdk.js';
|
|
13
|
+
import { buildLaunchSpec } from '../runtime/launch.js';
|
|
14
|
+
import { transition } from '../runtime/lifecycle.js';
|
|
15
|
+
import { headlessBrokerHost } from '../runtime/host.js';
|
|
16
|
+
import { spawnNode } from '../runtime/nodes.js';
|
|
17
|
+
import { reviveNode } from '../runtime/revive.js';
|
|
18
|
+
import { materializeSessionBranchAt } from '../runtime/session-branch.js';
|
|
19
|
+
import { pagePath } from './convention.js';
|
|
20
|
+
export function feedbackCompanionBranchPath(dir) { return `${dir}/companion-branch.jsonl`; }
|
|
21
|
+
/** Hex sha256 of the ticket's page document, recorded at first comment so the
|
|
22
|
+
* settle-time completion can say whether the page changed during feedback. */
|
|
23
|
+
export function pageDocumentSha256(dir) {
|
|
24
|
+
return createHash('sha256').update(readFileSync(pagePath(dir, 'jsx'))).digest('hex');
|
|
25
|
+
}
|
|
26
|
+
/** Materialize the companion's fork branch, reusing only an already-valid one. */
|
|
27
|
+
function materializeFeedbackBranch(dir, capture) {
|
|
28
|
+
const branchFile = feedbackCompanionBranchPath(dir);
|
|
29
|
+
if (existsSync(branchFile)) {
|
|
30
|
+
const branch = SessionManager.open(branchFile);
|
|
31
|
+
if (branch.getSessionFile() === resolve(branchFile) && branch.getSessionId() !== '')
|
|
32
|
+
return branchFile;
|
|
33
|
+
}
|
|
34
|
+
materializeSessionBranchAt(capture, branchFile);
|
|
35
|
+
return branchFile;
|
|
36
|
+
}
|
|
37
|
+
/** A companion node nothing on the ticket points at must not stay resident:
|
|
38
|
+
* the comment that would have bound it was refused, failed to commit, or lost
|
|
39
|
+
* the first-comment race to a concurrently recorded companion. */
|
|
40
|
+
export function discardFeedbackCompanion(nodeId) {
|
|
41
|
+
try {
|
|
42
|
+
transition(nodeId, 'cancel');
|
|
43
|
+
headlessBrokerHost.teardown(nodeId);
|
|
44
|
+
}
|
|
45
|
+
catch { /* the refusal is authoritative; a straggler node is inert */ }
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Fork the sending node's current state into the ticket-bound companion and
|
|
49
|
+
* wait for its durable boundary bind. Throws on any failure — the caller
|
|
50
|
+
* refuses the comment so no feedback is accepted without a visible
|
|
51
|
+
* conversation able to receive it.
|
|
52
|
+
*/
|
|
53
|
+
export async function realizeFeedbackCompanion(args) {
|
|
54
|
+
const origin = getNode(args.senderNodeId);
|
|
55
|
+
if (origin === null)
|
|
56
|
+
throw new Error(`page sender was not found: ${args.senderNodeId}`);
|
|
57
|
+
const capture = await captureOrigin(args.senderNodeId);
|
|
58
|
+
const branchFile = materializeFeedbackBranch(args.dir, capture);
|
|
59
|
+
const { launch } = buildLaunchSpec(origin.kind, origin.mode, {
|
|
60
|
+
lifecycle: 'resident',
|
|
61
|
+
hasManager: false,
|
|
62
|
+
model: capture.modelSpec,
|
|
63
|
+
modelExact: true,
|
|
64
|
+
cwd: origin.cwd,
|
|
65
|
+
profileId: origin.profile_id ?? null,
|
|
66
|
+
});
|
|
67
|
+
const node = spawnNode({
|
|
68
|
+
kind: origin.kind,
|
|
69
|
+
mode: origin.mode,
|
|
70
|
+
lifecycle: 'resident',
|
|
71
|
+
cwd: origin.cwd,
|
|
72
|
+
profile_id: origin.profile_id ?? null,
|
|
73
|
+
parent: args.senderNodeId,
|
|
74
|
+
subscribe: false,
|
|
75
|
+
forkFrom: branchFile,
|
|
76
|
+
modelOverride: capture.modelSpec,
|
|
77
|
+
reviewBinding: {
|
|
78
|
+
kind: 'page_feedback',
|
|
79
|
+
review_id: args.ticketId,
|
|
80
|
+
origin_node_id: args.senderNodeId,
|
|
81
|
+
branch_file: branchFile,
|
|
82
|
+
target_file: pagePath(args.dir, 'jsx'),
|
|
83
|
+
},
|
|
84
|
+
name: `Feedback · ${args.title}`,
|
|
85
|
+
launch,
|
|
86
|
+
});
|
|
87
|
+
try {
|
|
88
|
+
reviveNode(node.node_id, { resume: false });
|
|
89
|
+
if (!await awaitCompanionBind(node.node_id, COMPANION_BIND_DEADLINE_MS)) {
|
|
90
|
+
throw new Error(`feedback companion did not bind within ${COMPANION_BIND_DEADLINE_MS}ms`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
discardFeedbackCompanion(node.node_id);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
return node.node_id;
|
|
98
|
+
}
|