chore(genui): configure catalog extractor build - #495
Conversation
…atalog
Squashed rewrite of the @lynx-js/a2ui-reactlynx package. The previous
design coupled chat UI, IO transport, and renderer behind a singleton
processor + side-effect catalog registry. This commit splits them into
a small set of orthogonal pieces and shrinks the public surface to what
a protocol-naive developer actually needs.
Public surface
- `<A2UI messageStore catalogs onAction />` — the all-in-one entry
point. Subscribes to a raw-message buffer via useSyncExternalStore,
processes new tail messages on each render, renders the most recent
beginRendering surface. Five render slots (wrapSurface, renderEmpty,
renderFallback, renderError) for theming and lifecycle UI.
- `createMessageStore()` — a pure 4-method buffer:
interface MessageStore {
subscribe(cb): () => void;
getSnapshot(): readonly ServerToClientMessage[];
push(message | message[]): void;
clear(): void;
}
No SendIO, no SendContext, no resources, no surfaces, no processor —
the buffer knows nothing about the protocol. The developer's IO
module pushes raw messages in; <A2UI> processes them.
- Catalog API: `defineCatalog([Text, [Button, buttonManifest], MyCustom])`,
`mergeCatalogs`, `serializeCatalog`, `resolveCatalog`. CatalogInput is
either a bare component (name from `displayName ?? component.name`) or
a `[component, manifest]` tuple where the manifest is the JSON the
extractor emits at `dist/catalog/<Name>/catalog.json`.
- Built-in components individually re-exported (`Text`, `Button`,
`Card`, `CheckBox`, `Column`, `Divider`, `Image`, `List`,
`RadioGroup`, `Row`) so apps pick exactly what they need. There is
intentionally NO all-in-one aggregate — see
`src/catalog/README.md` for the paste-able recipe.
- Custom-component-author API: `useAction`, `useDataBinding`,
`useResolvedProps`, `NodeRenderer`. That is everything a third-party
catalog component needs to be interactive, data-bound, and
recursively render children.
Internal architecture
- src/store/: MessageStore (buffer), MessageProcessor (protocol state
machine), Resource, SignalStore, payloadNormalizer, types.
MessageProcessor is exposed for protocol-aware consumers who want to
build their own renderer instead of using <A2UI>.
- src/react/: <A2UI> owns a per-instance MessageProcessor, a
Map<id, Resource>, an activeMessageId, and a processedCount cursor.
useEffect on the buffer's snapshot calls
processor.processMessages(slice). processor.onUpdate updates
resources + activeMessageId; processor.onEvent forwards user actions
to props.onAction (fire-and-forget).
- A2UIProvider, A2UIContext, A2UIRenderer, useA2UIContext, useCatalog
are intentionally NOT exported. They're internal details of how
<A2UI> wires its catalog-component subtree.
Catalog composition
- Per-component subpath exports (`./catalog/<Name>` -> the component;
`./catalog/<Name>/catalog.json` -> the extractor manifest).
- `"sideEffects": ["**/*.css"]` in package.json so bundlers can drop
unused per-component imports while preserving CSS side effects.
- Catalog component CSS imports updated to origin/main's centralized
`styles/catalog/<Name>.css` location.
Examples (in a2ui-playground/examples/)
- io-mock/mockAgent.ts: createMockAgent(store, opts) returns
{ start, onAction, stop } — streams raw messages into the buffer
with a configurable delay.
- io-sse/sseAgent.ts: createSseAgent(store, { url }) returns
{ send, onAction, stop } — opens an EventSource, parses
delta/complete events, pushes into the buffer.
- chat-shell-lynx/: per-turn-store pattern. Each agent response gets
its own MessageStore + its own <A2UI>. The shell only knows about
turns; no provider/renderer ceremony.
Playground (lynx-src/App.tsx)
- Rewritten on top of <A2UI> + createMockAgent. Forwards user actions
back to the mock agent so canned responses appear in the same
panel. Preserves origin/main's useGlobalProps + effectiveData
integration for native preview.
Tests (vitest -> rstest)
- store/MessageStore.test.ts: subscribe/push/clear/getSnapshot
stability, snapshot freezing.
- store/Resource.test.ts: status transitions, subscribe/getSnapshot,
no throw-suspense regression.
- store/MessageProcessor.test.ts: surface lifecycle, multi-subscriber
listeners, dispatch.
- store/payloadNormalizer.test.ts: text-fallback + Card/Text wrappers.
- catalog/defineCatalog.test.ts: bare/tuple/resolved inputs,
mergeCatalogs last-write-wins, serializeCatalog manifest shape.
- 43/43 passing.
Extractor (a2ui-catalog-extractor)
- bin/cli.ts: replaced the require-main entry guard with a portable
`pathToFileURL`-based check so the bin shim invokes runCli.
Verification
- tsc --build packages/genui clean
- eslint clean across a2ui src + tests + extractor + playground/examples
(only pre-existing playground/index.tsx errors remain, unchanged)
- pnpm --filter @lynx-js/a2ui-reactlynx test -> 43/43
- pnpm --filter @lynx-js/a2ui-catalog-extractor test -> 6/6
- pnpm --filter @lynx-js/a2ui-reactlynx build -> 10 schemas emitted
There was a problem hiding this comment.
Code Review
This pull request introduces a turbo.json configuration file for the a2ui-catalog-extractor package to define build task parameters. The review feedback suggests including the build configuration file in the task inputs to ensure proper cache invalidation and updating the task dependencies to support workspace-level build ordering.
| "build": { | ||
| "dependsOn": [], | ||
| "inputs": [ | ||
| "src/**", | ||
| "tsconfig.json", | ||
| "package.json" | ||
| ], | ||
| "outputs": [ | ||
| "dist/**" | ||
| ] | ||
| } |
There was a problem hiding this comment.
The build task configuration is missing the build tool's configuration file (rslib.config.ts) in the inputs list. Including it ensures that changes to the build configuration correctly invalidate the cache. Additionally, using dependsOn: ["^build"] instead of an empty array ensures that any future workspace dependencies are built in the correct order, maintaining consistency with the root configuration.
"build": {
"dependsOn": ["^build"],
"inputs": [
"src/**",
"rslib.config.ts",
"tsconfig.json",
"package.json"
],
"outputs": [
"dist/**"
]
}
@coderabbitai summary
Checklist