[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CCGET8QJVZ9B7QMC78TY6Y":3,"skill-same:01M6CCGET8QJVZ9B7QMC78TY6Y":86},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":47,"linkOnly":48,"markdownContent":49,"markdownBody":50,"frontMatter":51,"refName":57,"commitSha":58,"skillDirectory":59,"forksCount":60,"repositoryHtmlUrl":61,"repositoryHomepageUrl":62,"ownerHtmlUrl":63,"ownerBlogUrl":62,"ownerDescription":64,"updatedAt":65,"install":66,"resources":79},"01M6CCGET8QJVZ9B7QMC78TY6Y","sdk-console-architecture",null,"The architecture decisions behind the TypeScript SDK packages (@stigmer\u002Freact-sdk, @stigmer\u002Fdesign-tokens, @stigmer\u002Fclient) and the client apps that are thin shells over them, each with its reason, plus the theme-token reference. Use when adding or changing a hook, a component, a token, a streaming view, or any wiring between a client app and an SDK component.","stigmer\u002Fstigmer","stigmer","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F255479904?v=4","01M6CCFBFXXRZC4ZVNS3JSDV4W","Stigmer","01M6CCFBKY7M8QGR4GH9TT35MY",9,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fsdk-console-architecture\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",[27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46],"sdk console architecture","sdk","console","architecture","with","wiring","apps","tokens","theme","app","desktop","web","component","packages","streaming","react","decisions","use","design","view","2026-11-10 10:12:30",false,"---\nname: sdk-console-architecture\ndescription:\n  The architecture decisions behind the TypeScript SDK packages\n  (@stigmer\u002Freact-sdk, @stigmer\u002Fdesign-tokens, @stigmer\u002Fclient) and the client\n  apps that are thin shells over them, each with its reason, plus the\n  theme-token reference. Use when adding or changing a hook, a component, a\n  token, a streaming view, or any wiring between a client app and an SDK\n  component.\npaths:\n  - sdk\u002Freact\u002F**\n  - sdk\u002Ftheme\u002F**\n  - client-apps\u002Fweb\u002Fsrc\u002F**\n  - client-apps\u002Fdesktop\u002Fsrc\u002F**\n---\n\n# SDK and console architecture\n\nStigmer is a platform for platforms. Every line of UI code answers one question:\nwould a platform builder embedding Stigmer in their product need this? If yes,\nit belongs in the SDK packages. The client apps (`client-apps\u002Fweb`,\n`client-apps\u002Fdesktop`) are reference implementations; the SDK packages are the\nproduct. The package guides (`sdk\u002FAGENTS.md`, `client-apps\u002FAGENTS.md`) carry the\none-line laws; this skill carries the decisions behind them, with the reason\neach was taken, so that a change which bends one knows what it is bending.\n\n## The layered architecture\n\n| Package                  | Role                                                   | Allowed dependencies                                 |\n| ------------------------ | ------------------------------------------------------ | ---------------------------------------------------- |\n| `@stigmer\u002Fclient`        | Typed API clients generated from the protobuf contract | `@stigmer\u002Fapis`                                      |\n| `@stigmer\u002Freact-sdk`     | Data hooks, behaviour hooks, styled components         | `@stigmer\u002Fclient`, `@stigmer\u002Fdesign-tokens`, `react` |\n| `@stigmer\u002Fdesign-tokens` | `--stgm-*` tokens, presets, the `cn()` utility         | none beyond `clsx` and `tailwind-merge`              |\n| `client-apps\u002Fweb`        | The Next.js console: routing, app shell, page layout   | the SDK packages, `next\u002F*`                           |\n| `client-apps\u002Fdesktop`    | The Tauri app: Vite, React Router, native integrations | the SDK packages, `@tauri-apps\u002F*`                    |\n\nDependencies flow downward only. The SDK never imports from a client app; client\napps never import from each other. `sdk-import-boundaries` in\n`tools\u002Feslint-plugin-stigmer\u002Frules\u002F` enforces the direction.\n\n## The decisions\n\nEach decision is named; cite it by name in code comments and reviews.\n\n**SDK first.** Build every feature in `@stigmer\u002Freact-sdk` first (data hook,\nthen behaviour hook, then styled component) and consume it from a client app\nsecond. When placement is uncertain, default to the SDK: extraction later is\nharder than inclusion now.\n\n**Thin shells.** Page files are routes and layout only, with no domain logic. A\npage file that grows past a screen of layout and imports is carrying logic that\nbelongs in the SDK.\n\n**Headless first.** Three layers, each independently importable: data hooks (API\ndata through `@stigmer\u002Fclient`), behaviour hooks (interaction logic and state\nmachines), styled components (hooks composed with `@stigmer\u002Fdesign-tokens`\ntokens). A platform builder adopts at the layer that fits: full control with\nhooks only, partial control with hooks and their own rendering, zero effort with\nthe styled components. `useSession()` without `\u003CSessionViewer \u002F>` must be a\nvalid import, and the hooks layer is an entry of its own,\n`@stigmer\u002Freact-sdk\u002Fheadless` (`sdk\u002Freact\u002Fsrc\u002Fheadless.ts`), which the root\nre-exports and nothing that draws may enter.\n\n**No framework dependencies in the SDK.** `@stigmer\u002Freact-sdk` has no `next\u002F*`,\nno `@tauri-apps\u002F*`, no routing, no app-shell auth, no framework assumption; it\nruns in any React environment. Navigation and image optimisation are the\nconsumer's responsibility, reached through callback props. The `\"use client\"`\ndirective is not a framework import: it is part of the React Server Components\nspecification and a no-op string for bundlers that do not know it.\n\n**Theme tokens only.** Every visual property flows through `--stgm-*` tokens,\nscoped to the `.stgm` container and `@layer stgm`, using the token family the\nrendering context calls for. The reference is\n[references\u002Ftheme-tokens.md](references\u002Ftheme-tokens.md).\n\n**Error messages are the interface.** Every error states what happened, why, and\nwhat to do. A hook used outside its provider throws a message naming the\nprovider to add; a data hook returns a typed error state; a styled component\nrenders meaningful error UI, never a blank screen.\n\n**Generated types are the source of truth.** `@stigmer\u002Fapis` feeds\n`@stigmer\u002Fclient`, which feeds `@stigmer\u002Freact-sdk`. Never hand-write a type\nthat duplicates a generated one; never raw-fetch beside a generated client.\nDerived types (`Pick`, `Omit`, UI extensions) are fine.\n\n**One provider.** `StigmerProvider` and `useStigmer()` are the only injection\npoint. The five-minute integration is: install, create a client, wrap in the\nprovider, import a component. SDK hooks use `useStigmer()` and nothing else; a\nsecond provider needs a written reason.\n\n**The streaming pipeline.** Real-time views ride one pipeline: a stream source,\nthe StreamController state machine (`idle`, `connecting`, `streaming`,\n`reconnecting`, `complete`, `error`;\n`sdk\u002Freact-core\u002Fsrc\u002Finternal\u002Fstream-controller.ts`), request-animation-frame\ncoalescing (buffer the latest, commit at most once per frame), structural\nsharing (walk the tree by natural keys and keep unchanged references),\n`startTransition`, `useSyncExternalStore`, and `React.memo` on the leaves. Never\n`useState(snapshot)` for a high-frequency stream: it re-renders the whole tree\non every frame. Completion is phase-driven (a terminal run phase or an explicit\nsignal), never inferred from the stream ending.\n\n**Reference stability is architectural.** A hook that returns an object literal\nwraps it in `useMemo`; a callback's dependencies name the specific method used\n(`conv.sendFollowUp`, not `conv`). These are not micro-optimisations; they are\nwhat makes `React.memo` downstream work at all.\n\n**Opt-in behaviour changes.** A new behaviour that changes DOM structure, scroll\nbehaviour, rendering strategy or bundle dependencies is opt-in through a prop\nwith a backward-compatible default (`virtualized?: boolean` defaults to\n`false`). Automatic threshold switching mid-session is forbidden: it remounts,\nloses state and resets scroll.\n\nThe one exception is a redesign the maintainer rules for every host, shipped as\na breaking (`!`) change whose body says so. The session viewer's plain-line\nsteps (stigmer\u002Fstigmer#3046) are one: a working turn's quiet stretch of steps\nfolds into one line once every step in it returned (never one still running or\none that failed), and a finished turn leads with its answer and folds its\nworking under \"How it got there\", for every reader. A host gets the new\npresentation with the release; there is no prop for the old one.\n\n**Dependency licence policy.** The SDK packages use only MIT- or\nApache-2.0-compatible dependencies. A commercial-licence dependency is isolated\nto a client app; a dependency that imposes obligations on SDK consumers is a\nblocker.\n\n**Lazy loading for heavy dependencies.** A library most screens never use is\nimported through `React.lazy` or `import()`, so it is downloaded only on use:\n`react-virtuoso` (`sdk\u002Freact\u002Fsrc\u002Finternal\u002FVirtualizedThread.tsx`) and CodeMirror\n(`YamlEditor` in `sdk\u002Freact\u002Fsrc\u002Fmanifest\u002FYamlEditor.tsx` is the loading\nboundary, `CodeMirrorYamlEditor.tsx` the implementation). Lazy is not optional:\na bundler resolves every import it can reach, `import()` included, so a package\nthe kit names in any import is a dependency, never an optional peer. A library\ntoo large to install for every host (`mermaid`) is not named by the kit at all:\nthe host passes its import (`StigmerProvider`'s `loadMermaid`). An app with only\nthe required peers builds the kit with Vite and webpack\n(`test\u002Finstall\u002Fsmoke-libs.mjs`).\n\n**Cache across mounts, reset by key.** `key={id}` remounts are the correct React\npattern for a clean state reset; `useFetch` consumers pass a `cacheKey` for\ncross-mount persistence (instant render of previously fetched data, refetch in\nthe background). The two coexist: the cache gives `useFetch` memory, the key\ngives hooks clean resets.\n\n**Animation accessibility.** All animation respects `prefers-reduced-motion`\n(`sdk\u002Freact\u002Fsrc\u002Finternal\u002Fmotion-preference.ts`), using a `0.01ms` duration\nrather than `0ms` so `animationEnd` and `transitionEnd` callbacks still fire.\nClass-based animation, not `@starting-style`, wherever virtualization recycles\nDOM nodes, because `@starting-style` fires on every insertion including a\nrecycled mount. Animation CSS lives in `@layer stgm` with `--stgm-motion-*`\nproperties.\n\n**Client-app parity.** Every client app consuming the same SDK component wires\nits props identically, allowing only platform differences such as routing or\nnative APIs. When a fix or a feature changes how one client wires a component,\nevery other consumer of that component is reviewed and updated in the same\nchangeset. Unexplained divergence between clients is a defect. The check applies\nto every shared component and matters most for `SessionComposer`,\n`MessageThread`, `ModelSelector` and the flow hooks (`useSessionPageFlow`,\n`useNewSessionFlow`).\n\n**Resilient streaming.** Long-lived streaming hooks recover from transient drops\nwith exponential backoff and full jitter before showing any error. A\nnon-terminal interruption enters `reconnecting`, keeps the last-known-good state\nvisible (`error` stays `null`, `isReconnecting` is true), and surfaces a\nterminal error only when retries are exhausted. `isTransientStreamError` in\n`@stigmer\u002Fclient` (`sdk\u002Ftypescript\u002Fsrc\u002Ferrors.ts`) classifies transient against\ndeterministic; not-found, validation and auth errors never retry. Reconnect is\non by default and tunable through `autoReconnect` and `reconnectOptions`;\n`reconnect()` is the manual fallback. Every consumer of `streamState.stage`\ntreats `reconnecting` as live.\n\n**Node-resolvable ESM output.** The SDK packages are consumed by bundlers and by\nplain Node (the `stigmer` CLI, `npx @stigmer\u002Fmcp-server`). Node ESM needs an\nexplicit extension on every relative specifier, so the packages write the `.js`\nsuffix on every relative import, export and dynamic import under\n`moduleResolution: \"bundler\"`, the convention the CLI, the Ink package, the MCP\nserver and the generated protos already follow. An extension-less specifier\nresolves under a bundler and crashes every Node consumer with\n`ERR_MODULE_NOT_FOUND`, and development never catches it because tests run under\n`tsx` (stigmer\u002Fstigmer#209). The `.js` is a no-op for Vite and is stripped for\nTurbopack by `client-apps\u002Fweb\u002Fturbopack-js-to-ts-loader.js`, which must cover\n`from`, dynamic `import(` and side-effect forms. `scripts\u002Fverify-esm-node.mjs`\nenforces the invariant before publish.\n\n**The scoping container's layout contract.** `StigmerProvider`'s in-tree\ncontainer ships with no sizing and must never gain a default. A default\n`height: 100%` looks harmless under auto-height parents and balloons under a\nflex-item parent, because flexbox treats a flexed size as definite for\npercentage resolution; a `flex-1` prose column would grow the container to the\nfull column height even on a content-sized page (measured on the docs site).\nFixed-height embedding (stigmer\u002Fstigmer#260) is an explicit opt-in the host\napplies from its own CSS:\n`.stgm[data-stgm-root] { height: 100%; min-height: 0 }`. Two marker attributes\nare stable public selectors: `data-stgm-root` on the in-tree container only and\n`data-stgm-portal` on the portal container only, never both on one element; a\npopup opened inside a modal `DialogShell` portals into that dialog's own\nunmarked target, since the top layer would cover the body container. The\n`className` prop is a theming channel mirrored onto both scope containers and\nmust never become a layout channel.\n`sdk\u002Freact\u002Fsrc\u002F__tests__\u002Fprovider-theme-scope.test.tsx` pins the markers and\n`sdk\u002Freact\u002Fsrc\u002F__tests__\u002Fprovider-container.layout.browser.test.tsx` pins the\nlayout in a real browser.\n\n**Console chrome is SDK-owned.** `WorkspaceSidebar` and `SettingsSidebar` are\npublic `@stigmer\u002Freact-sdk\u002Fconsole` surfaces rendered by the web console and the\ndesktop app, so the hosts' chrome cannot drift apart (stigmer\u002Fstigmer#317).\nPer-host differences enter only through named seams: `renderLink` (the host's\nrouter link, or an inert row), the `footer` slot (each host's user menu),\n`renderEntryAccessory` (the desktop's background-run indicator), and data as\nprops (`useRecentActivity` output or frozen fixtures, plus a `now` instant for\ndeterministic hosts). The app-shell frame (column width, collapse state, zone\nswitching, responsive wrapping) stays client-app-owned under Thin shells. Never\nre-transcribe sidebar markup into a client app or a demo scene; extend the\ncomponent's seams. The same holds for every depicted surface: a demo scene\nrenders the real SDK organism with fixture data and hand-draws only documented\npage framing.\n\n## Dont-dos\n\n1. No client-app imports in the SDK: nothing from `client-apps\u002Fweb`,\n   `client-apps\u002Fdesktop` or an `@\u002F` path.\n2. No framework dependencies in the SDK: no `next\u002F*`, `next-themes`,\n   `@tauri-apps\u002F*`.\n3. No hard-coded colours or sizes: no hex values, no `bg-white`, no\n   `text-[14px]` (a type-scale step, `text-2xs` the smallest; the\n   `text-size-from-scale` lint rule refuses the rest).\n4. No opacity modifiers on tokens: a dedicated variant\n   (`text-sidebar-muted-foreground`), never a `\u002F60` suffix; a missing variant is\n   a new token.\n5. No technical-role folders in client apps: group by domain, as\n   `client-apps\u002Fweb\u002Fsrc\u002Fdomain\u002F` and `client-apps\u002Fdesktop\u002Fsrc\u002Fpages\u002F` do, never\n   by role (a hooks folder, a services folder).\n6. No `useState(snapshot)` for streaming data.\n7. No whole-object dependencies in `useCallback` or `useMemo`.\n8. No `@starting-style` in virtualized contexts.\n9. No single-client fix for shared SDK wiring: find every consumer across the\n   client apps and fix them all.\n\n## Integration ergonomics\n\nSDK APIs are user interfaces. Method names, hook return types, component props,\nerror messages and TypeScript intellisense are the surfaces a platform builder\ntouches every day.\n\n- Clean props, sensible defaults, minimal required configuration.\n- If a developer cannot get a component running in five minutes, the developer\n  experience has failed.\n- Hooks are exported beside styled components.\n- Naming is critical: a name becomes part of the platform builder's codebase and\n  is expensive to change.\n- Every exported hook, component and type is a public API contract.\n- A reader can name every type a public signature takes or returns. A type from\n  a peer dependency (`@stigmer\u002Fapis`, `@stigmer\u002Fclient`, `react`) is imported\n  from the peer; any other (the kit's own, or one from a plain dependency such\n  as `@stigmer\u002Freact-core` or `@stigmer\u002Fplugin-package`) is exported by the kit,\n  as a type, from the entry the function is in. A reader installs only the kit\n  and its peers. The docs snippet gate compiles every reference signature and\n  names one that breaks this at its page and line.\n- An `@example` is read in two places: its reference page and the editor's hover\n  over the `.d.ts`, where nothing is hidden. So it holds no hidden setup (the\n  generators refuse a `---cut---` line). It takes what it needs as a literal or\n  as a small component's typed props, imports what its reader imports, and\n  compiles on its page (`docs\u002FSTYLE.md`, \"Code examples compile\").\n\n## Before writing any component\n\n1. Does it belong in the SDK or a client app? Default to the SDK.\n2. Can it be themed through `--stgm-*` tokens without leaking styles?\n3. Does it work identically embedded in a third-party dashboard and in the\n   console?\n4. Are its hooks exported independently of its styled component?\n5. Does it use generated types from `@stigmer\u002Fclient`, not hand-written ones?\n6. If it changes how a client app wires an SDK component, has every client app\n   been updated?\n7. Does it offer an act that changes something? Then it appears only on the\n   server's yes, never while the check is in flight: ask with\n   `useCheckPermissions` (`sdk\u002Freact\u002Fsrc\u002Fgrant\u002FuseCheckPermissions.ts`) failing\n   closed, or declare `requires` on a `DetailAction`; reading stays fail-open,\n   and no disabled button stands in for an act the server refuses.\n","# SDK and console architecture\n\nStigmer is a platform for platforms. Every line of UI code answers one question:\nwould a platform builder embedding Stigmer in their product need this? If yes,\nit belongs in the SDK packages. The client apps (`client-apps\u002Fweb`,\n`client-apps\u002Fdesktop`) are reference implementations; the SDK packages are the\nproduct. The package guides (`sdk\u002FAGENTS.md`, `client-apps\u002FAGENTS.md`) carry the\none-line laws; this skill carries the decisions behind them, with the reason\neach was taken, so that a change which bends one knows what it is bending.\n\n## The layered architecture\n\n| Package                  | Role                                                   | Allowed dependencies                                 |\n| ------------------------ | ------------------------------------------------------ | ---------------------------------------------------- |\n| `@stigmer\u002Fclient`        | Typed API clients generated from the protobuf contract | `@stigmer\u002Fapis`                                      |\n| `@stigmer\u002Freact-sdk`     | Data hooks, behaviour hooks, styled components         | `@stigmer\u002Fclient`, `@stigmer\u002Fdesign-tokens`, `react` |\n| `@stigmer\u002Fdesign-tokens` | `--stgm-*` tokens, presets, the `cn()` utility         | none beyond `clsx` and `tailwind-merge`              |\n| `client-apps\u002Fweb`        | The Next.js console: routing, app shell, page layout   | the SDK packages, `next\u002F*`                           |\n| `client-apps\u002Fdesktop`    | The Tauri app: Vite, React Router, native integrations | the SDK packages, `@tauri-apps\u002F*`                    |\n\nDependencies flow downward only. The SDK never imports from a client app; client\napps never import from each other. `sdk-import-boundaries` in\n`tools\u002Feslint-plugin-stigmer\u002Frules\u002F` enforces the direction.\n\n## The decisions\n\nEach decision is named; cite it by name in code comments and reviews.\n\n**SDK first.** Build every feature in `@stigmer\u002Freact-sdk` first (data hook,\nthen behaviour hook, then styled component) and consume it from a client app\nsecond. When placement is uncertain, default to the SDK: extraction later is\nharder than inclusion now.\n\n**Thin shells.** Page files are routes and layout only, with no domain logic. A\npage file that grows past a screen of layout and imports is carrying logic that\nbelongs in the SDK.\n\n**Headless first.** Three layers, each independently importable: data hooks (API\ndata through `@stigmer\u002Fclient`), behaviour hooks (interaction logic and state\nmachines), styled components (hooks composed with `@stigmer\u002Fdesign-tokens`\ntokens). A platform builder adopts at the layer that fits: full control with\nhooks only, partial control with hooks and their own rendering, zero effort with\nthe styled components. `useSession()` without `\u003CSessionViewer \u002F>` must be a\nvalid import, and the hooks layer is an entry of its own,\n`@stigmer\u002Freact-sdk\u002Fheadless` (`sdk\u002Freact\u002Fsrc\u002Fheadless.ts`), which the root\nre-exports and nothing that draws may enter.\n\n**No framework dependencies in the SDK.** `@stigmer\u002Freact-sdk` has no `next\u002F*`,\nno `@tauri-apps\u002F*`, no routing, no app-shell auth, no framework assumption; it\nruns in any React environment. Navigation and image optimisation are the\nconsumer's responsibility, reached through callback props. The `\"use client\"`\ndirective is not a framework import: it is part of the React Server Components\nspecification and a no-op string for bundlers that do not know it.\n\n**Theme tokens only.** Every visual property flows through `--stgm-*` tokens,\nscoped to the `.stgm` container and `@layer stgm`, using the token family the\nrendering context calls for. The reference is\n[references\u002Ftheme-tokens.md](references\u002Ftheme-tokens.md).\n\n**Error messages are the interface.** Every error states what happened, why, and\nwhat to do. A hook used outside its provider throws a message naming the\nprovider to add; a data hook returns a typed error state; a styled component\nrenders meaningful error UI, never a blank screen.\n\n**Generated types are the source of truth.** `@stigmer\u002Fapis` feeds\n`@stigmer\u002Fclient`, which feeds `@stigmer\u002Freact-sdk`. Never hand-write a type\nthat duplicates a generated one; never raw-fetch beside a generated client.\nDerived types (`Pick`, `Omit`, UI extensions) are fine.\n\n**One provider.** `StigmerProvider` and `useStigmer()` are the only injection\npoint. The five-minute integration is: install, create a client, wrap in the\nprovider, import a component. SDK hooks use `useStigmer()` and nothing else; a\nsecond provider needs a written reason.\n\n**The streaming pipeline.** Real-time views ride one pipeline: a stream source,\nthe StreamController state machine (`idle`, `connecting`, `streaming`,\n`reconnecting`, `complete`, `error`;\n`sdk\u002Freact-core\u002Fsrc\u002Finternal\u002Fstream-controller.ts`), request-animation-frame\ncoalescing (buffer the latest, commit at most once per frame), structural\nsharing (walk the tree by natural keys and keep unchanged references),\n`startTransition`, `useSyncExternalStore`, and `React.memo` on the leaves. Never\n`useState(snapshot)` for a high-frequency stream: it re-renders the whole tree\non every frame. Completion is phase-driven (a terminal run phase or an explicit\nsignal), never inferred from the stream ending.\n\n**Reference stability is architectural.** A hook that returns an object literal\nwraps it in `useMemo`; a callback's dependencies name the specific method used\n(`conv.sendFollowUp`, not `conv`). These are not micro-optimisations; they are\nwhat makes `React.memo` downstream work at all.\n\n**Opt-in behaviour changes.** A new behaviour that changes DOM structure, scroll\nbehaviour, rendering strategy or bundle dependencies is opt-in through a prop\nwith a backward-compatible default (`virtualized?: boolean` defaults to\n`false`). Automatic threshold switching mid-session is forbidden: it remounts,\nloses state and resets scroll.\n\nThe one exception is a redesign the maintainer rules for every host, shipped as\na breaking (`!`) change whose body says so. The session viewer's plain-line\nsteps (stigmer\u002Fstigmer#3046) are one: a working turn's quiet stretch of steps\nfolds into one line once every step in it returned (never one still running or\none that failed), and a finished turn leads with its answer and folds its\nworking under \"How it got there\", for every reader. A host gets the new\npresentation with the release; there is no prop for the old one.\n\n**Dependency licence policy.** The SDK packages use only MIT- or\nApache-2.0-compatible dependencies. A commercial-licence dependency is isolated\nto a client app; a dependency that imposes obligations on SDK consumers is a\nblocker.\n\n**Lazy loading for heavy dependencies.** A library most screens never use is\nimported through `React.lazy` or `import()`, so it is downloaded only on use:\n`react-virtuoso` (`sdk\u002Freact\u002Fsrc\u002Finternal\u002FVirtualizedThread.tsx`) and CodeMirror\n(`YamlEditor` in `sdk\u002Freact\u002Fsrc\u002Fmanifest\u002FYamlEditor.tsx` is the loading\nboundary, `CodeMirrorYamlEditor.tsx` the implementation). Lazy is not optional:\na bundler resolves every import it can reach, `import()` included, so a package\nthe kit names in any import is a dependency, never an optional peer. A library\ntoo large to install for every host (`mermaid`) is not named by the kit at all:\nthe host passes its import (`StigmerProvider`'s `loadMermaid`). An app with only\nthe required peers builds the kit with Vite and webpack\n(`test\u002Finstall\u002Fsmoke-libs.mjs`).\n\n**Cache across mounts, reset by key.** `key={id}` remounts are the correct React\npattern for a clean state reset; `useFetch` consumers pass a `cacheKey` for\ncross-mount persistence (instant render of previously fetched data, refetch in\nthe background). The two coexist: the cache gives `useFetch` memory, the key\ngives hooks clean resets.\n\n**Animation accessibility.** All animation respects `prefers-reduced-motion`\n(`sdk\u002Freact\u002Fsrc\u002Finternal\u002Fmotion-preference.ts`), using a `0.01ms` duration\nrather than `0ms` so `animationEnd` and `transitionEnd` callbacks still fire.\nClass-based animation, not `@starting-style`, wherever virtualization recycles\nDOM nodes, because `@starting-style` fires on every insertion including a\nrecycled mount. Animation CSS lives in `@layer stgm` with `--stgm-motion-*`\nproperties.\n\n**Client-app parity.** Every client app consuming the same SDK component wires\nits props identically, allowing only platform differences such as routing or\nnative APIs. When a fix or a feature changes how one client wires a component,\nevery other consumer of that component is reviewed and updated in the same\nchangeset. Unexplained divergence between clients is a defect. The check applies\nto every shared component and matters most for `SessionComposer`,\n`MessageThread`, `ModelSelector` and the flow hooks (`useSessionPageFlow`,\n`useNewSessionFlow`).\n\n**Resilient streaming.** Long-lived streaming hooks recover from transient drops\nwith exponential backoff and full jitter before showing any error. A\nnon-terminal interruption enters `reconnecting`, keeps the last-known-good state\nvisible (`error` stays `null`, `isReconnecting` is true), and surfaces a\nterminal error only when retries are exhausted. `isTransientStreamError` in\n`@stigmer\u002Fclient` (`sdk\u002Ftypescript\u002Fsrc\u002Ferrors.ts`) classifies transient against\ndeterministic; not-found, validation and auth errors never retry. Reconnect is\non by default and tunable through `autoReconnect` and `reconnectOptions`;\n`reconnect()` is the manual fallback. Every consumer of `streamState.stage`\ntreats `reconnecting` as live.\n\n**Node-resolvable ESM output.** The SDK packages are consumed by bundlers and by\nplain Node (the `stigmer` CLI, `npx @stigmer\u002Fmcp-server`). Node ESM needs an\nexplicit extension on every relative specifier, so the packages write the `.js`\nsuffix on every relative import, export and dynamic import under\n`moduleResolution: \"bundler\"`, the convention the CLI, the Ink package, the MCP\nserver and the generated protos already follow. An extension-less specifier\nresolves under a bundler and crashes every Node consumer with\n`ERR_MODULE_NOT_FOUND`, and development never catches it because tests run under\n`tsx` (stigmer\u002Fstigmer#209). The `.js` is a no-op for Vite and is stripped for\nTurbopack by `client-apps\u002Fweb\u002Fturbopack-js-to-ts-loader.js`, which must cover\n`from`, dynamic `import(` and side-effect forms. `scripts\u002Fverify-esm-node.mjs`\nenforces the invariant before publish.\n\n**The scoping container's layout contract.** `StigmerProvider`'s in-tree\ncontainer ships with no sizing and must never gain a default. A default\n`height: 100%` looks harmless under auto-height parents and balloons under a\nflex-item parent, because flexbox treats a flexed size as definite for\npercentage resolution; a `flex-1` prose column would grow the container to the\nfull column height even on a content-sized page (measured on the docs site).\nFixed-height embedding (stigmer\u002Fstigmer#260) is an explicit opt-in the host\napplies from its own CSS:\n`.stgm[data-stgm-root] { height: 100%; min-height: 0 }`. Two marker attributes\nare stable public selectors: `data-stgm-root` on the in-tree container only and\n`data-stgm-portal` on the portal container only, never both on one element; a\npopup opened inside a modal `DialogShell` portals into that dialog's own\nunmarked target, since the top layer would cover the body container. The\n`className` prop is a theming channel mirrored onto both scope containers and\nmust never become a layout channel.\n`sdk\u002Freact\u002Fsrc\u002F__tests__\u002Fprovider-theme-scope.test.tsx` pins the markers and\n`sdk\u002Freact\u002Fsrc\u002F__tests__\u002Fprovider-container.layout.browser.test.tsx` pins the\nlayout in a real browser.\n\n**Console chrome is SDK-owned.** `WorkspaceSidebar` and `SettingsSidebar` are\npublic `@stigmer\u002Freact-sdk\u002Fconsole` surfaces rendered by the web console and the\ndesktop app, so the hosts' chrome cannot drift apart (stigmer\u002Fstigmer#317).\nPer-host differences enter only through named seams: `renderLink` (the host's\nrouter link, or an inert row), the `footer` slot (each host's user menu),\n`renderEntryAccessory` (the desktop's background-run indicator), and data as\nprops (`useRecentActivity` output or frozen fixtures, plus a `now` instant for\ndeterministic hosts). The app-shell frame (column width, collapse state, zone\nswitching, responsive wrapping) stays client-app-owned under Thin shells. Never\nre-transcribe sidebar markup into a client app or a demo scene; extend the\ncomponent's seams. The same holds for every depicted surface: a demo scene\nrenders the real SDK organism with fixture data and hand-draws only documented\npage framing.\n\n## Dont-dos\n\n1. No client-app imports in the SDK: nothing from `client-apps\u002Fweb`,\n   `client-apps\u002Fdesktop` or an `@\u002F` path.\n2. No framework dependencies in the SDK: no `next\u002F*`, `next-themes`,\n   `@tauri-apps\u002F*`.\n3. No hard-coded colours or sizes: no hex values, no `bg-white`, no\n   `text-[14px]` (a type-scale step, `text-2xs` the smallest; the\n   `text-size-from-scale` lint rule refuses the rest).\n4. No opacity modifiers on tokens: a dedicated variant\n   (`text-sidebar-muted-foreground`), never a `\u002F60` suffix; a missing variant is\n   a new token.\n5. No technical-role folders in client apps: group by domain, as\n   `client-apps\u002Fweb\u002Fsrc\u002Fdomain\u002F` and `client-apps\u002Fdesktop\u002Fsrc\u002Fpages\u002F` do, never\n   by role (a hooks folder, a services folder).\n6. No `useState(snapshot)` for streaming data.\n7. No whole-object dependencies in `useCallback` or `useMemo`.\n8. No `@starting-style` in virtualized contexts.\n9. No single-client fix for shared SDK wiring: find every consumer across the\n   client apps and fix them all.\n\n## Integration ergonomics\n\nSDK APIs are user interfaces. Method names, hook return types, component props,\nerror messages and TypeScript intellisense are the surfaces a platform builder\ntouches every day.\n\n- Clean props, sensible defaults, minimal required configuration.\n- If a developer cannot get a component running in five minutes, the developer\n  experience has failed.\n- Hooks are exported beside styled components.\n- Naming is critical: a name becomes part of the platform builder's codebase and\n  is expensive to change.\n- Every exported hook, component and type is a public API contract.\n- A reader can name every type a public signature takes or returns. A type from\n  a peer dependency (`@stigmer\u002Fapis`, `@stigmer\u002Fclient`, `react`) is imported\n  from the peer; any other (the kit's own, or one from a plain dependency such\n  as `@stigmer\u002Freact-core` or `@stigmer\u002Fplugin-package`) is exported by the kit,\n  as a type, from the entry the function is in. A reader installs only the kit\n  and its peers. The docs snippet gate compiles every reference signature and\n  names one that breaks this at its page and line.\n- An `@example` is read in two places: its reference page and the editor's hover\n  over the `.d.ts`, where nothing is hidden. So it holds no hidden setup (the\n  generators refuse a `---cut---` line). It takes what it needs as a literal or\n  as a small component's typed props, imports what its reader imports, and\n  compiles on its page (`docs\u002FSTYLE.md`, \"Code examples compile\").\n\n## Before writing any component\n\n1. Does it belong in the SDK or a client app? Default to the SDK.\n2. Can it be themed through `--stgm-*` tokens without leaking styles?\n3. Does it work identically embedded in a third-party dashboard and in the\n   console?\n4. Are its hooks exported independently of its styled component?\n5. Does it use generated types from `@stigmer\u002Fclient`, not hand-written ones?\n6. If it changes how a client app wires an SDK component, has every client app\n   been updated?\n7. Does it offer an act that changes something? Then it appears only on the\n   server's yes, never while the check is in flight: ask with\n   `useCheckPermissions` (`sdk\u002Freact\u002Fsrc\u002Fgrant\u002FuseCheckPermissions.ts`) failing\n   closed, or declare `requires` on a `DetailAction`; reading stays fail-open,\n   and no disabled button stands in for an act the server refuses.\n",{"name":5,"paths":52,"description":7},[53,54,55,56],"sdk\u002Freact\u002F**","sdk\u002Ftheme\u002F**","client-apps\u002Fweb\u002Fsrc\u002F**","client-apps\u002Fdesktop\u002Fsrc\u002F**","main","542abf3e227d5b3e3921e09715f4122ece6466aa",".agents\u002Fskills\u002Fsdk-console-architecture",6,"https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer","https:\u002F\u002Fstigmer.ai","https:\u002F\u002Fgithub.com\u002Fstigmer","The open-source backend for the agents in your product.","2026-11-14 02:41:36",{"source":8,"skillName":5,"commands":67,"prompt":77,"zipAvailable":78},[68,71,74],{"tool":69,"command":70},"npx","npx skills add stigmer\u002Fstigmer --skill sdk-console-architecture",{"tool":72,"command":73},"bunx","bunx skills add stigmer\u002Fstigmer --skill sdk-console-architecture",{"tool":75,"command":76},"pnpm","pnpm dlx skills add stigmer\u002Fstigmer --skill sdk-console-architecture","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fsdk-console-architecture\u002FSKILL.md",true,[80],{"path":81,"resourceType":82,"resourceTypeDesc":83,"sizeBytes":84,"sourceHtmlUrl":85},"references\u002Ftheme-tokens.md","REFERENCE","参考文档",5134,"https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fsdk-console-architecture\u002Freferences\u002Ftheme-tokens.md",[87,115,135,153,167,183],{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":104,"publishedAt":47},"01M6CCSERKR4YRSZJ2TB6B0X60","writing-hookify-rules","This skill should be used when the user asks to \"create a hookify rule\", \"write a hook rule\", \"configure hookify\", \"add a hookify rule\", or needs guidance on hookify rule syntax and patterns.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fsupport\u002Ffixtures\u002Fclaude-plugins\u002Fhookify\u002Fskills\u002Fwriting-rules\u002FSKILL.md",[93,96,99],{"categoryNo":94,"categoryName":95,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":97,"categoryName":98,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":100,"categoryName":101,"taxonomyType":102,"taxonomyTypeDesc":103},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[105,106,107,108,109,110,111,112,113,114],"writing","Writing Hookify Rules","Rules","hookify rules","create","guidance","user","write","configure","syntax",{"skillNo":116,"name":117,"nameZh":6,"description":118,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":119,"categories":120,"keywords":127,"publishedAt":47},"01M6CCSC63RKCGC2S1EZDEMXZE","sql-migrations","Write safe database schema migrations - reversible steps, online index builds, and backfills in batches.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Fsql-migrations\u002FSKILL.md",[121,124],{"categoryNo":122,"categoryName":123,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":125,"categoryName":126,"taxonomyType":102,"taxonomyTypeDesc":103},"01JBSEEDCAT000000000000033","数据分析师",[128,129,130,131,112,132,133,134],"sql","sql migrations","migrations","schema","index","database","safe",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":141,"publishedAt":47},"01M6CCS9Q8E0R3TYHPZHCPN0JZ","repo-conventions","How to change Go code in this repository - error wrapping, table-driven tests, doc comments, no new dependencies, and running go test before finishing.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Frepo-conventions\u002FSKILL.md",[],[142,143,144,145,146,147,148,149,150,151,152],"repo","repo conventions","table","go","test","change","doc comments","code","repository","doc","how",{"skillNo":154,"name":155,"nameZh":6,"description":156,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":157,"categories":158,"keywords":159,"publishedAt":47},"01M6CCS7F2D1YZ9H3N63XTEV09","incident-postmortem","Write a blameless incident postmortem - timeline, impact, root cause, contributing factors and follow-up actions with owners.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Fincident-postmortem\u002FSKILL.md",[],[160,161,112,162,163,164,165,166],"incident postmortem","postmortem","contributing","actions","timeline","owners","up",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":177,"publishedAt":47},"01M6CCS4W1D2K257KHC40HS8JG","data-analysis","Summarise a data export or table - lead with the key finding and give every number its baseline or comparison.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Fdata-analysis\u002FSKILL.md",[173,176],{"categoryNo":174,"categoryName":175,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000013","分析与可视化",{"categoryNo":125,"categoryName":126,"taxonomyType":102,"taxonomyTypeDesc":103},[178,179,180,181,144,182],"analysis","data analysis","data","baseline","export",{"skillNo":184,"name":185,"nameZh":6,"description":186,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":187,"categories":188,"keywords":189,"publishedAt":47},"01M6CCS2MNQYK9S24AYHJAVPWF","customer-support","Answer customer questions about orders, returns and refunds from the written policies and the order system, ending with a clear next step.","https:\u002F\u002Fgithub.com\u002Fstigmer\u002Fstigmer\u002Fblob\u002Fmain\u002Ftest\u002Fconformance\u002Ffixtures\u002Fworking-agent\u002Fskills\u002Fcustomer-support\u002FSKILL.md",[],[190,191,192,193,194,195],"support","customer","customer support","system","refunds","returns"]