Skip to content

feat(ui): group createChatHook options and pass layout slots as components - #1282

Merged
AlemTuzlak merged 9 commits into
feat/create-chat-hookfrom
feat/ui-layout-component-slots
Sep 1, 2026
Merged

feat(ui): group createChatHook options and pass layout slots as components#1282
AlemTuzlak merged 9 commits into
feat/create-chat-hookfrom
feat/ui-layout-component-slots

Conversation

@jherr

@jherr jherr commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Registering chat UI components now reads as named groups instead of one chatComponents bag, and layout receives Messages, Interrupts, and Input as components rather than renderX() thunks. message receives Parts the same way. Five new recipe pages take a reader from a plain chat box up to per-request context.

Stacked on #1275. Review that one first.

🎯 Changes

Option groups. createChatHook and createChatUI take context, components, toolsComponents, interruptsComponents, and partsComponents. chatComponents is gone.

Components, not thunks. layout gets Messages / Interrupts / Input; message gets Parts. renderMessages, renderInterrupts, renderInput, and renderParts are gone.

Parts is the one that needed care. It closes over the message, so building it per render makes React remount every part on each stream chunk, which throws away local state in tool and part widgets. It now reads a message-scoped context, so the kit hands out one stable component.

Typed Input. Input is on the layout props only when the config registers an input, so rendering one you never registered fails to compile. When that inference cannot run, the runtime warns once instead of crashing on an undefined element.

Docs. Five recipes under docs/ui/recipes/. Migration guidance for createChatHook is removed, because that API has never shipped: the published @tanstack/ai-react-ui@0.8.22 exports only the older Chat / ChatMessages components. The page keeps the real migration, which is the @tanstack/ai-*-ui to /ui package swap.

All four adapters move together: React, Solid, Vue, Svelte.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested code changes locally with pnpm run test:pr, or these tests do not apply to this pull request.
  • I fully understand the code in this pull request, including any code generated with AI assistance.
  • Docs: I updated docs/ for this change, or this change is not user-facing.
  • Changeset: I added a changeset (pnpm changeset), or this PR does not change a published package.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Existing changesets are rewritten rather than added to. createChatHook has not released, so they describe the shipping API instead of a change to it.

Testing

Commands run.

  • pnpm test:pr passes.
  • pnpm test:ci and pnpm test:dts pass across all 86 projects.
  • pnpm --filter @tanstack/ai-e2e test:e2e: 638 passed, 10 failed. Both failing specs fail the same way on this PR's base with none of these changes applied, checked in a scratch worktree. They run against examples/ts-react-chat, which never imports /ui. multi-turn-structured is a fixture problem: turn 3 receives turn 2's recipe. durable-takeover passes on re-run.

Manual test.

  1. pnpm install && pnpm build:all
  2. cd examples/ts-react-ui-chatbot && pnpm dev
  3. Send a message. The chat renders through layout with component props.
  4. Trigger the booking tool and approve it. The approval renders in the list.
  5. Open docs/ui/recipes/basic-chat.md and copy the client snippet into a new app. It runs as written.

How this PR makes testing easy.

  • testing/e2e/tests/headless-ui.spec.ts asserts the Parts subtree is not remounted mid-stream, using a data-mount-seq stamp on the tool widget. Reverting Parts to a per-render closure takes that to 7 mounts and the test fails.
  • Type-level tests in create-ui-types.test.tsx pin the conditional Input, including the ordering case that used to break inference silently.
  • Four example apps and the e2e route are converted, so the new shape is exercised at real call sites.
  • All 1183 doc snippets type-check under kiira.

Risk / rollback

Low. Nothing here has been published, so no installed version changes behaviour. The blast radius is the /ui surface plus the docs.

To roll back, revert this PR. #1275 stands on its own.

Public API change

Before

const { useAppChat } = createChatHook({
  options: chatOptions,
  chatComponents: {
    chatContext,
    partContext,
    interruptContext,
    layout: ({ renderMessages, renderInput }) => (
      <main>
        {renderMessages()}
        {renderInput()}
      </main>
    ),
    message: ({ renderParts }) => <article>{renderParts()}</article>,
    parts: { text: TextPart, fallback: FallbackPart },
    tools: { getWeather: WeatherTool },
    interrupts: { generic: { choosePlan: ChoosePlan } },
  },
})

After

const { useAppChat } = createChatHook({
  options: chatOptions,
  context: { chatContext, partContext, interruptContext },
  components: {
    input: ChatInput,
    layout: ({ Messages, Input }) => (
      <main>
        <Messages />
        <Input />
      </main>
    ),
    message: ({ Parts }) => (
      <article>
        <Parts />
      </article>
    ),
  },
  toolsComponents: { getWeather: WeatherTool },
  interruptsComponents: { generic: { choosePlan: ChoosePlan } },
  partsComponents: { text: TextPart, fallback: FallbackPart },
})

jherr and others added 6 commits August 31, 2026 11:47
`layout` now receives `Messages`, `Interrupts`, and `Input`; `message`
receives `Parts`. `renderMessages`, `renderInterrupts`, `renderInput`,
and `renderParts` are gone — this API ships first in this PR, so there
is nothing released to migrate.

React and Solid only. Vue and Svelte compose through slots and snippets
and already had empty `LayoutProps`.

`Messages` and `Interrupts` were already factory-closure declarations, so
passing them directly is strictly more stable than the `useCallback`
thunks they replace. `Parts` was the hard part: it closes over the
message, so the naive version builds a new component per render and
React remounts every part on each stream chunk. It now reads a
message-scoped context that `MessageView` provides, which makes it one
component for the factory's lifetime. `create-ui-stability.test.tsx`
pins that: the same `Parts` identity must survive a re-render with a new
message object.

`Input` is on the layout props only when the config registers an
`input`, so rendering one you never registered is a compile error rather
than a silent no-op. This is inferred from a `TInput` parameter on the
`input` property; a self-referential config constraint was tried first
and silently collapses to `false`.

Two inference caveats, both found by making the examples compile:

- Referencing the factory's own result inside its config
  (`UI.useChatContext()`) is circular and blocks inference. The examples
  and the e2e route now use `createChatHookContexts()`, already the
  documented cycle-breaker.
- An inline `function` expression for `input` also blocks it. Arrows and
  named references infer fine, so the examples hoist the component and
  both UI docs call the trap out.

The default falls back to a union rather than `undefined` so that when
inference does degrade, `input` stays assignable and only `Input` goes
missing — a false negative instead of a rejected config.

Two doc snippets rendered an input they never registered; the new types
caught them.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The conditional `Input` type hides the prop when no `input` is
registered, but that inference degrades on some config shapes, and the
runtime had no component to render in that case — React threw
"Element type is invalid" on an undefined element. Always supply a
component and warn once instead, matching the existing
"Missing tools.<name> component" warning. The floor is now consistent:
you find out at build time where inference works, and on the first dev
render where it doesn't.

The inference trigger turned out to be narrower than the previous commit
claimed. It is source order, not the `function` keyword: an inline
`function` expression for `input` infers fine as long as it is listed
before `layout`. Hoisting the component out of the config bought nothing
— the config literal is evaluated once at module scope, so an inline
component is already a stable reference — so the examples and the e2e
route go back to inline named components, ordered `input` first. Both UI
docs now describe the ordering rule rather than telling people to hoist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`chatComponents` is gone. Registration is now flat on the factory call,
one group per kind:

  createChatHook({
    options,
    context: { chatContext, partContext, interruptContext },
    components: { input, layout, message },
    toolsComponents: { ... },
    interruptsComponents: { tools: { ... }, generic: { ... } },
    partsComponents: { ... },
  })

`createChatUI` takes the same shape as its second argument, so both
entry points register widgets identically. All four adapters (React,
Solid, Vue, Svelte) move together. Vue and Svelte keep their flat
internal runtime shape; the new config is mapped onto it at the factory
boundary, so nothing downstream of that changes.

`partsComponents` is plural, unlike the original sketch — the registries
are `parts`/`tools`/`interrupts` and the maps hold many entries;
`PartProps` and `ChatUIPartKey` stay singular because they describe one
part.

This also removes the `Input` inference fragility rather than
documenting around it. `layout` and `input` are now siblings inside
`components`, so TypeScript resolves that inner literal as a unit and
fixes `TInput` before contextually typing `layout`. Declaration order no
longer matters and an inline `function` expression works, both of which
broke inference when these keys sat at the config's top level. The
ordering caveat is out of both UI docs, and a type test pins the case
that used to fail so a future flattening cannot regress it silently.

Per instruction, no migration guidance was added anywhere; the existing
`docs/migration/create-ui.md` page covers the older package move only
and has been updated to the new shape rather than extended.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`createChatHook` and `createChatUI` have never been published, so
teaching them as something to migrate to was wrong. I checked before
cutting: `@tanstack/ai-react-ui@0.8.22` (and Solid 0.7.21, Vue 0.2.40)
export only the older `Chat` / `ChatMessages` / `ChatInput` compound
components, with no `createChatHook` anywhere.

So the page keeps the one migration that is real — swapping
`@tanstack/ai-*-ui` for the framework `/ui` subpath, which those
published packages do need — and loses the Before/After, Steps, and
Gotchas sections that walked through adopting the new factory. The new
API is documented in the UI guides as a new API.

The `Chat` component's `@deprecated` notices pointed at that page for
`createChatHook` guidance, so they now point at the matching UI guide.
The `docs/api/*` pages no longer advertise the migration page as reading
for the typed chat UI. The `*-ui` shim packages still link there, which
is still exactly what that page covers.

Also brings this branch in line with repo rules I had been working
without, having only picked up the worktree's CLAUDE.md late:

- Removed the em dashes the docs skill forbids from text added here.
- Bumped `updatedAt` in `docs/config.json` for the five pages with real
  content changes. The four `docs/api/*` pages only lost a stale link,
  which the rule classes as a factual fix that must not bump.
- Rewrote the `create-chat-hook` changeset to describe the shipping
  option groups instead of the old `chatComponents` shape, and aligned
  `typed-headless-chat-ui` with the new names. Since none of this has
  released, these describe the API rather than a change to it.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`CLAUDE.md` requires E2E coverage for a behavior change and this stack
had none. The behavior worth guarding is the reason `Parts` reads a
message-scoped context instead of closing over the message: a `Parts`
rebuilt per render makes React remount every part on each stream chunk,
throwing away local state in tool and part widgets.

The purchase tool widget on `/headless-ui` now stamps a per-mount
sequence into `data-mount-seq`, and the spec asserts it stays at 1 across
the tool-call chunks and again across the resume stream after approval.

Verified the test actually catches the regression rather than just
passing: reverting `Parts` to a per-render closure and rebuilding takes
the widget to 7 mounts, and the assertion fails on the first check.

Full suite: 638 passed, 10 failed. Both failing specs
(`multi-turn-structured` across providers, and the flaky
`durable-takeover`) fail identically on the PR base `ad5c6c28d` with none
of this stack's changes present, verified in a scratch worktree. They run
against `examples/ts-react-chat`, which uses `useChat` and never touches
the `/ui` surface. `multi-turn-structured` is a fixture-playback problem:
turn 3 receives turn 2's recipe.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The framework guides open with one example that registers tools,
interrupts, structured output, input and parts at once. A reader wiring
their first chat box has to skip past all of it. These five pages each do
one thing, in order:

1. `basic-chat` — layout, message, text part. No tools.
2. `format-a-tool` — one tool, branching on `part.state`.
3. `tool-approval` — `needsApproval`, inline or in the list.
4. `custom-interrupt` — `defineInterrupt` with your own schemas.
5. `request-context` — `forwardedProps` from screen to route.

Two gaps these close: there was no starting point smaller than the full
example, and nothing documented sending per-request context from the
chat UI. `docs/advanced/runtime-context.md` covers only the server half,
so the new page shows both and says plainly that `forwardedProps` is
client-controlled and must not be trusted for identity.

React only. The Solid, Vue and Svelte guides already point at the React
page for the full map, and each now links here too.

Named `recipes`, not `examples`: `scripts/verify-links.ts` treats any
path containing `/examples/` as a directory link for the site's app
examples, so `docs/ui/examples/*.md` reported every internal link
broken.

While writing these I checked the API instead of trusting the existing
prose, and corrected two things in my own drafts: the terminal tool state
is `complete`, not `output-available`, and `ToolCallPart` has no
`errorMessage` field. I also confirmed the grouped `ToolProps` snippet is
really type-checked by breaking it on purpose and watching kiira fail.

All 1183 doc snippets typecheck. `pnpm test:ci` and `pnpm test:dts` pass
across all 86 projects.

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

nx-cloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 3310d68

Command Status Duration Result
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 2s View ↗

☁️ Nx Cloud last updated this comment at 2026-09-01 08:48:56 UTC

@pkg-pr-new

pkg-pr-new Bot commented Sep 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

@tanstack/ai

npm i https://pkg.pr.new/@tanstack/ai@1282

@tanstack/ai-acp

npm i https://pkg.pr.new/@tanstack/ai-acp@1282

@tanstack/ai-angular

npm i https://pkg.pr.new/@tanstack/ai-angular@1282

@tanstack/ai-anthropic

npm i https://pkg.pr.new/@tanstack/ai-anthropic@1282

@tanstack/ai-bedrock

npm i https://pkg.pr.new/@tanstack/ai-bedrock@1282

@tanstack/ai-byteplus

npm i https://pkg.pr.new/@tanstack/ai-byteplus@1282

@tanstack/ai-claude-code

npm i https://pkg.pr.new/@tanstack/ai-claude-code@1282

@tanstack/ai-client

npm i https://pkg.pr.new/@tanstack/ai-client@1282

@tanstack/ai-code-mode

npm i https://pkg.pr.new/@tanstack/ai-code-mode@1282

@tanstack/ai-code-mode-snippets

npm i https://pkg.pr.new/@tanstack/ai-code-mode-snippets@1282

@tanstack/ai-codex

npm i https://pkg.pr.new/@tanstack/ai-codex@1282

@tanstack/ai-cohere

npm i https://pkg.pr.new/@tanstack/ai-cohere@1282

@tanstack/ai-compaction

npm i https://pkg.pr.new/@tanstack/ai-compaction@1282

@tanstack/ai-devtools-core

npm i https://pkg.pr.new/@tanstack/ai-devtools-core@1282

@tanstack/ai-durable-stream

npm i https://pkg.pr.new/@tanstack/ai-durable-stream@1282

@tanstack/ai-elevenlabs

npm i https://pkg.pr.new/@tanstack/ai-elevenlabs@1282

@tanstack/ai-event-client

npm i https://pkg.pr.new/@tanstack/ai-event-client@1282

@tanstack/ai-fal

npm i https://pkg.pr.new/@tanstack/ai-fal@1282

@tanstack/ai-gemini

npm i https://pkg.pr.new/@tanstack/ai-gemini@1282

@tanstack/ai-grok

npm i https://pkg.pr.new/@tanstack/ai-grok@1282

@tanstack/ai-grok-build

npm i https://pkg.pr.new/@tanstack/ai-grok-build@1282

@tanstack/ai-groq

npm i https://pkg.pr.new/@tanstack/ai-groq@1282

@tanstack/ai-isolate-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-isolate-cloudflare@1282

@tanstack/ai-isolate-daytona

npm i https://pkg.pr.new/@tanstack/ai-isolate-daytona@1282

@tanstack/ai-isolate-node

npm i https://pkg.pr.new/@tanstack/ai-isolate-node@1282

@tanstack/ai-isolate-quickjs

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs@1282

@tanstack/ai-isolate-quickjs-bun

npm i https://pkg.pr.new/@tanstack/ai-isolate-quickjs-bun@1282

@tanstack/ai-llmgateway

npm i https://pkg.pr.new/@tanstack/ai-llmgateway@1282

@tanstack/ai-lovable

npm i https://pkg.pr.new/@tanstack/ai-lovable@1282

@tanstack/ai-mcp

npm i https://pkg.pr.new/@tanstack/ai-mcp@1282

@tanstack/ai-memory

npm i https://pkg.pr.new/@tanstack/ai-memory@1282

@tanstack/ai-mistral

npm i https://pkg.pr.new/@tanstack/ai-mistral@1282

@tanstack/ai-octane

npm i https://pkg.pr.new/@tanstack/ai-octane@1282

@tanstack/ai-ollama

npm i https://pkg.pr.new/@tanstack/ai-ollama@1282

@tanstack/ai-openai

npm i https://pkg.pr.new/@tanstack/ai-openai@1282

@tanstack/ai-opencode

npm i https://pkg.pr.new/@tanstack/ai-opencode@1282

@tanstack/ai-openrouter

npm i https://pkg.pr.new/@tanstack/ai-openrouter@1282

@tanstack/ai-perplexity

npm i https://pkg.pr.new/@tanstack/ai-perplexity@1282

@tanstack/ai-persistence

npm i https://pkg.pr.new/@tanstack/ai-persistence@1282

@tanstack/ai-preact

npm i https://pkg.pr.new/@tanstack/ai-preact@1282

@tanstack/ai-react

npm i https://pkg.pr.new/@tanstack/ai-react@1282

@tanstack/ai-react-ui

npm i https://pkg.pr.new/@tanstack/ai-react-ui@1282

@tanstack/ai-sandbox

npm i https://pkg.pr.new/@tanstack/ai-sandbox@1282

@tanstack/ai-sandbox-cloudflare

npm i https://pkg.pr.new/@tanstack/ai-sandbox-cloudflare@1282

@tanstack/ai-sandbox-daytona

npm i https://pkg.pr.new/@tanstack/ai-sandbox-daytona@1282

@tanstack/ai-sandbox-docker

npm i https://pkg.pr.new/@tanstack/ai-sandbox-docker@1282

@tanstack/ai-sandbox-local-process

npm i https://pkg.pr.new/@tanstack/ai-sandbox-local-process@1282

@tanstack/ai-sandbox-sprites

npm i https://pkg.pr.new/@tanstack/ai-sandbox-sprites@1282

@tanstack/ai-sandbox-upstash-box

npm i https://pkg.pr.new/@tanstack/ai-sandbox-upstash-box@1282

@tanstack/ai-sandbox-vercel

npm i https://pkg.pr.new/@tanstack/ai-sandbox-vercel@1282

@tanstack/ai-skills

npm i https://pkg.pr.new/@tanstack/ai-skills@1282

@tanstack/ai-solid

npm i https://pkg.pr.new/@tanstack/ai-solid@1282

@tanstack/ai-solid-ui

npm i https://pkg.pr.new/@tanstack/ai-solid-ui@1282

@tanstack/ai-svelte

npm i https://pkg.pr.new/@tanstack/ai-svelte@1282

@tanstack/ai-utils

npm i https://pkg.pr.new/@tanstack/ai-utils@1282

@tanstack/ai-vercel-gateway

npm i https://pkg.pr.new/@tanstack/ai-vercel-gateway@1282

@tanstack/ai-vertex

npm i https://pkg.pr.new/@tanstack/ai-vertex@1282

@tanstack/ai-vue

npm i https://pkg.pr.new/@tanstack/ai-vue@1282

@tanstack/ai-vue-ui

npm i https://pkg.pr.new/@tanstack/ai-vue-ui@1282

@tanstack/openai-base

npm i https://pkg.pr.new/@tanstack/openai-base@1282

@tanstack/preact-ai-devtools

npm i https://pkg.pr.new/@tanstack/preact-ai-devtools@1282

@tanstack/react-ai-devtools

npm i https://pkg.pr.new/@tanstack/react-ai-devtools@1282

@tanstack/solid-ai-devtools

npm i https://pkg.pr.new/@tanstack/solid-ai-devtools@1282

@tanstack/svelte-ai-devtools

npm i https://pkg.pr.new/@tanstack/svelte-ai-devtools@1282

commit: 3310d68

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: a9c631b2-0851-4fee-87cc-eab9f44c0f26

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the waiting-on: maintainer The ball is in the maintainers’ court label Sep 1, 2026
AlemTuzlak and others added 2 commits September 1, 2026 10:45
Layout receives a stable Queue component that maps pending sends. Each item has cancelQueued bound. Register the row with components.queue and QueueProps.
@AlemTuzlak
AlemTuzlak merged commit fcc999c into feat/create-chat-hook Sep 1, 2026
9 checks passed
@AlemTuzlak
AlemTuzlak deleted the feat/ui-layout-component-slots branch September 1, 2026 09:08
AlemTuzlak added a commit that referenced this pull request Sep 1, 2026
* feat(ui): add createChatHook and move Chat UI onto /ui (#1263)

* feat(client): add createChatHook bound useChat factory

* feat(ui): align createChatUI with Form and Table factories

* feat(ui): move chat UI onto framework /ui subpaths

Fold the *-ui packages into @tanstack/ai-react/ui, @tanstack/ai-solid/ui, @tanstack/ai-vue/ui, and @tanstack/ai-svelte/ui.

createChatHook({ options, chatComponents }) returns useAppChat (Svelte: createAppChat). useAppChat mixes AppChat onto the instance so screens render <chat.AppChat />.

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* feat(ui): deprecate ai-*-ui packages in favor of /ui

Restore @tanstack/ai-react-ui, ai-solid-ui, ai-vue-ui, and ai-svelte-ui as
thin re-exports of the framework /ui subpath until 1.0.0.

Document createChatHook, chat.AppChat, and the import path change.

* ci: apply automated fixes

* chore(ui): drop unpublished @tanstack/ai-svelte-ui

That package never shipped on npm. Svelte chat UI lives on
@tanstack/ai-svelte/ui. Keep deprecated shims only for the
published react, solid, and vue UI packages.

* ci: apply automated fixes

* fix(ui): green up the CI suite for createChatHook

Nine tasks were failing on this branch. Fixes, in order of substance:

- `ai-vue`/`ai-solid` ship their `/ui` subpath as source (Vue SFCs and
  Solid JSX must be compiled by the consumer), so `create-ui` is
  type-checked against the *consumer's* tsconfig. Both referenced a bare
  `process`, which broke any consumer without `@types/node` (this is what
  failed `ts-vue-chat`). Declare `process` locally, matching the
  `src/env.d.ts` shape the devtools packages already use, so the literal
  `process.env.NODE_ENV` bundlers substitute stays intact and the branch
  is still constant-folded in production.
- `ts-react-ui-chatbot` imported `useChatContext` from `@/chat/ui-context`,
  which only exports the raw contexts. It comes off the factory in
  `./ui-components`, the way `layout.tsx` already imports it.
- Docs: `docs/ui/react.md`'s widget-picking snippet referenced `UI` and
  `message` that no fence defined, and `p.getWeather` for a widget the
  example factory never registered. Grouped it with the factory fence and
  registered `getWeather`/`text`, so the snippet now type-checks against
  the real API instead of only looking right.
- Docs: `docs/ui/vue.md`'s `StatusLine` and `ui` formed an inference cycle.
  Declare `StatusLine` after `ui` and annotate it; `h(StatusLine)` only
  runs at render time, so the runtime order is unchanged.
- Split the mixed value/type imports in `ai-react`/`ai-solid` `create-ui`
  and drop the inline `import()` type in the Solid test (oxlint).
- Drop devDeps the deprecation commit orphaned in the `ai-*-ui` packages,
  and alphabetize `ai-vue`'s (knip, sherif).

`pnpm test:ci` and `pnpm test:dts` both pass across all 86 projects.

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

* feat(ui): group createChatHook options and pass layout slots as components (#1282)

* feat(ui)!: hand layout and message components, not render thunks

`layout` now receives `Messages`, `Interrupts`, and `Input`; `message`
receives `Parts`. `renderMessages`, `renderInterrupts`, `renderInput`,
and `renderParts` are gone — this API ships first in this PR, so there
is nothing released to migrate.

React and Solid only. Vue and Svelte compose through slots and snippets
and already had empty `LayoutProps`.

`Messages` and `Interrupts` were already factory-closure declarations, so
passing them directly is strictly more stable than the `useCallback`
thunks they replace. `Parts` was the hard part: it closes over the
message, so the naive version builds a new component per render and
React remounts every part on each stream chunk. It now reads a
message-scoped context that `MessageView` provides, which makes it one
component for the factory's lifetime. `create-ui-stability.test.tsx`
pins that: the same `Parts` identity must survive a re-render with a new
message object.

`Input` is on the layout props only when the config registers an
`input`, so rendering one you never registered is a compile error rather
than a silent no-op. This is inferred from a `TInput` parameter on the
`input` property; a self-referential config constraint was tried first
and silently collapses to `false`.

Two inference caveats, both found by making the examples compile:

- Referencing the factory's own result inside its config
  (`UI.useChatContext()`) is circular and blocks inference. The examples
  and the e2e route now use `createChatHookContexts()`, already the
  documented cycle-breaker.
- An inline `function` expression for `input` also blocks it. Arrows and
  named references infer fine, so the examples hoist the component and
  both UI docs call the trap out.

The default falls back to a union rather than `undefined` so that when
inference does degrade, `input` stays assignable and only `Input` goes
missing — a false negative instead of a rejected config.

Two doc snippets rendered an input they never registered; the new types
caught them.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

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

* fix(ui): warn instead of crashing on an unregistered Input

The conditional `Input` type hides the prop when no `input` is
registered, but that inference degrades on some config shapes, and the
runtime had no component to render in that case — React threw
"Element type is invalid" on an undefined element. Always supply a
component and warn once instead, matching the existing
"Missing tools.<name> component" warning. The floor is now consistent:
you find out at build time where inference works, and on the first dev
render where it doesn't.

The inference trigger turned out to be narrower than the previous commit
claimed. It is source order, not the `function` keyword: an inline
`function` expression for `input` infers fine as long as it is listed
before `layout`. Hoisting the component out of the config bought nothing
— the config literal is evaluated once at module scope, so an inline
component is already a stable reference — so the examples and the e2e
route go back to inline named components, ordered `input` first. Both UI
docs now describe the ordering rule rather than telling people to hoist.

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

* feat(ui)!: split createChatHook options into named component groups

`chatComponents` is gone. Registration is now flat on the factory call,
one group per kind:

  createChatHook({
    options,
    context: { chatContext, partContext, interruptContext },
    components: { input, layout, message },
    toolsComponents: { ... },
    interruptsComponents: { tools: { ... }, generic: { ... } },
    partsComponents: { ... },
  })

`createChatUI` takes the same shape as its second argument, so both
entry points register widgets identically. All four adapters (React,
Solid, Vue, Svelte) move together. Vue and Svelte keep their flat
internal runtime shape; the new config is mapped onto it at the factory
boundary, so nothing downstream of that changes.

`partsComponents` is plural, unlike the original sketch — the registries
are `parts`/`tools`/`interrupts` and the maps hold many entries;
`PartProps` and `ChatUIPartKey` stay singular because they describe one
part.

This also removes the `Input` inference fragility rather than
documenting around it. `layout` and `input` are now siblings inside
`components`, so TypeScript resolves that inner literal as a unit and
fixes `TInput` before contextually typing `layout`. Declaration order no
longer matters and an inline `function` expression works, both of which
broke inference when these keys sat at the config's top level. The
ordering caveat is out of both UI docs, and a type test pins the case
that used to fail so a future flattening cannot regress it silently.

Per instruction, no migration guidance was added anywhere; the existing
`docs/migration/create-ui.md` page covers the older package move only
and has been updated to the new shape rather than extended.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

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

* docs: drop migration guidance for the unreleased chat UI API

`createChatHook` and `createChatUI` have never been published, so
teaching them as something to migrate to was wrong. I checked before
cutting: `@tanstack/ai-react-ui@0.8.22` (and Solid 0.7.21, Vue 0.2.40)
export only the older `Chat` / `ChatMessages` / `ChatInput` compound
components, with no `createChatHook` anywhere.

So the page keeps the one migration that is real — swapping
`@tanstack/ai-*-ui` for the framework `/ui` subpath, which those
published packages do need — and loses the Before/After, Steps, and
Gotchas sections that walked through adopting the new factory. The new
API is documented in the UI guides as a new API.

The `Chat` component's `@deprecated` notices pointed at that page for
`createChatHook` guidance, so they now point at the matching UI guide.
The `docs/api/*` pages no longer advertise the migration page as reading
for the typed chat UI. The `*-ui` shim packages still link there, which
is still exactly what that page covers.

Also brings this branch in line with repo rules I had been working
without, having only picked up the worktree's CLAUDE.md late:

- Removed the em dashes the docs skill forbids from text added here.
- Bumped `updatedAt` in `docs/config.json` for the five pages with real
  content changes. The four `docs/api/*` pages only lost a stale link,
  which the rule classes as a factual fix that must not bump.
- Rewrote the `create-chat-hook` changeset to describe the shipping
  option groups instead of the old `chatComponents` shape, and aligned
  `typed-headless-chat-ui` with the new names. Since none of this has
  released, these describe the API rather than a change to it.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

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

* test(e2e): pin the Parts subtree against remounting mid-stream

`CLAUDE.md` requires E2E coverage for a behavior change and this stack
had none. The behavior worth guarding is the reason `Parts` reads a
message-scoped context instead of closing over the message: a `Parts`
rebuilt per render makes React remount every part on each stream chunk,
throwing away local state in tool and part widgets.

The purchase tool widget on `/headless-ui` now stamps a per-mount
sequence into `data-mount-seq`, and the spec asserts it stays at 1 across
the tool-call chunks and again across the resume stream after approval.

Verified the test actually catches the regression rather than just
passing: reverting `Parts` to a per-render closure and rebuilding takes
the widget to 7 mounts, and the assertion fails on the first check.

Full suite: 638 passed, 10 failed. Both failing specs
(`multi-turn-structured` across providers, and the flaky
`durable-takeover`) fail identically on the PR base `ad5c6c28d` with none
of this stack's changes present, verified in a scratch worktree. They run
against `examples/ts-react-chat`, which uses `useChat` and never touches
the `/ui` surface. `multi-turn-structured` is a fixture-playback problem:
turn 3 receives turn 2's recipe.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

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

* docs(ui): add five chat UI recipes

The framework guides open with one example that registers tools,
interrupts, structured output, input and parts at once. A reader wiring
their first chat box has to skip past all of it. These five pages each do
one thing, in order:

1. `basic-chat` — layout, message, text part. No tools.
2. `format-a-tool` — one tool, branching on `part.state`.
3. `tool-approval` — `needsApproval`, inline or in the list.
4. `custom-interrupt` — `defineInterrupt` with your own schemas.
5. `request-context` — `forwardedProps` from screen to route.

Two gaps these close: there was no starting point smaller than the full
example, and nothing documented sending per-request context from the
chat UI. `docs/advanced/runtime-context.md` covers only the server half,
so the new page shows both and says plainly that `forwardedProps` is
client-controlled and must not be trusted for identity.

React only. The Solid, Vue and Svelte guides already point at the React
page for the full map, and each now links here too.

Named `recipes`, not `examples`: `scripts/verify-links.ts` treats any
path containing `/examples/` as a directory link for the site's app
examples, so `docs/ui/examples/*.md` reported every internal link
broken.

While writing these I checked the API instead of trusting the existing
prose, and corrected two things in my own drafts: the terminal tool state
is `complete`, not `output-available`, and `ToolCallPart` has no
`errorMessage` field. I also confirmed the grouped `ToolProps` snippet is
really type-checked by breaking it on purpose and watching kiira fail.

All 1183 doc snippets typecheck. `pnpm test:ci` and `pnpm test:dts` pass
across all 86 projects.

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

* ci: apply automated fixes

* feat(ui): add Queue layout slot and QueueProps

Layout receives a stable Queue component that maps pending sends. Each item has cancelQueued bound. Register the row with components.queue and QueueProps.

* ci: apply automated fixes

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Alem Tuzlak <t.zlak@hotmail.com>

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jack Herrington <jack.herrington@netlify.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Jack Herrington <jherr@pobox.com>
AlemTuzlak added a commit that referenced this pull request Sep 1, 2026
* feat(ui): add typed headless createUI adapters

* docs(ui): document typed props, context, and interrupt placement

Teach ToolProps, RegisteredInterruptProps, and UI.useChat. Show tool approvals inline in the tool or in the interrupt list. Put registered generic interrupts under interrupts.generic next to fallback. Unbound interrupts use fallback. Check interrupt.kind when the copy must differ.

* feat(ui): pin InterruptProps and add the typed chatbot example

InterruptProps takes a tool name or a registered interrupt id. A tool approval renders in the tool when interrupts.tools omits that name. examples/ts-react-ui-chatbot shows parts, list vs in-tool approval, BYOK, and transcription.

* fix(ui): export createChatUI and type-check Vue and Svelte examples

Rename the factory to createChatUI. Register every guitar tool in the Vue and Svelte examples so defineComponents type-checks. Point the UI docs at gpt-5.6 and @tanstack/ai-svelte-ui 0.2.0.

* feat(ui): move createChatUI onto framework /ui subpaths

Chat UI now lives at @tanstack/ai-react/ui, @tanstack/ai-solid/ui,
@tanstack/ai-vue/ui, and @tanstack/ai-svelte/ui.

The published @tanstack/ai-react-ui, @tanstack/ai-solid-ui, and
@tanstack/ai-vue-ui packages re-export those subpaths until 1.0.0.
@tanstack/ai-svelte-ui was never published, so that package is gone.

* feat(ui): add createChatHook and move Chat UI onto /ui (#1275)

* feat(ui): add createChatHook and move Chat UI onto /ui (#1263)

* feat(client): add createChatHook bound useChat factory

* feat(ui): align createChatUI with Form and Table factories

* feat(ui): move chat UI onto framework /ui subpaths

Fold the *-ui packages into @tanstack/ai-react/ui, @tanstack/ai-solid/ui, @tanstack/ai-vue/ui, and @tanstack/ai-svelte/ui.

createChatHook({ options, chatComponents }) returns useAppChat (Svelte: createAppChat). useAppChat mixes AppChat onto the instance so screens render <chat.AppChat />.

* ci: apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* feat(ui): deprecate ai-*-ui packages in favor of /ui

Restore @tanstack/ai-react-ui, ai-solid-ui, ai-vue-ui, and ai-svelte-ui as
thin re-exports of the framework /ui subpath until 1.0.0.

Document createChatHook, chat.AppChat, and the import path change.

* ci: apply automated fixes

* chore(ui): drop unpublished @tanstack/ai-svelte-ui

That package never shipped on npm. Svelte chat UI lives on
@tanstack/ai-svelte/ui. Keep deprecated shims only for the
published react, solid, and vue UI packages.

* ci: apply automated fixes

* fix(ui): green up the CI suite for createChatHook

Nine tasks were failing on this branch. Fixes, in order of substance:

- `ai-vue`/`ai-solid` ship their `/ui` subpath as source (Vue SFCs and
  Solid JSX must be compiled by the consumer), so `create-ui` is
  type-checked against the *consumer's* tsconfig. Both referenced a bare
  `process`, which broke any consumer without `@types/node` (this is what
  failed `ts-vue-chat`). Declare `process` locally, matching the
  `src/env.d.ts` shape the devtools packages already use, so the literal
  `process.env.NODE_ENV` bundlers substitute stays intact and the branch
  is still constant-folded in production.
- `ts-react-ui-chatbot` imported `useChatContext` from `@/chat/ui-context`,
  which only exports the raw contexts. It comes off the factory in
  `./ui-components`, the way `layout.tsx` already imports it.
- Docs: `docs/ui/react.md`'s widget-picking snippet referenced `UI` and
  `message` that no fence defined, and `p.getWeather` for a widget the
  example factory never registered. Grouped it with the factory fence and
  registered `getWeather`/`text`, so the snippet now type-checks against
  the real API instead of only looking right.
- Docs: `docs/ui/vue.md`'s `StatusLine` and `ui` formed an inference cycle.
  Declare `StatusLine` after `ui` and annotate it; `h(StatusLine)` only
  runs at render time, so the runtime order is unchanged.
- Split the mixed value/type imports in `ai-react`/`ai-solid` `create-ui`
  and drop the inline `import()` type in the Solid test (oxlint).
- Drop devDeps the deprecation commit orphaned in the `ai-*-ui` packages,
  and alphabetize `ai-vue`'s (knip, sherif).

`pnpm test:ci` and `pnpm test:dts` both pass across all 86 projects.

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

* feat(ui): group createChatHook options and pass layout slots as components (#1282)

* feat(ui)!: hand layout and message components, not render thunks

`layout` now receives `Messages`, `Interrupts`, and `Input`; `message`
receives `Parts`. `renderMessages`, `renderInterrupts`, `renderInput`,
and `renderParts` are gone — this API ships first in this PR, so there
is nothing released to migrate.

React and Solid only. Vue and Svelte compose through slots and snippets
and already had empty `LayoutProps`.

`Messages` and `Interrupts` were already factory-closure declarations, so
passing them directly is strictly more stable than the `useCallback`
thunks they replace. `Parts` was the hard part: it closes over the
message, so the naive version builds a new component per render and
React remounts every part on each stream chunk. It now reads a
message-scoped context that `MessageView` provides, which makes it one
component for the factory's lifetime. `create-ui-stability.test.tsx`
pins that: the same `Parts` identity must survive a re-render with a new
message object.

`Input` is on the layout props only when the config registers an
`input`, so rendering one you never registered is a compile error rather
than a silent no-op. This is inferred from a `TInput` parameter on the
`input` property; a self-referential config constraint was tried first
and silently collapses to `false`.

Two inference caveats, both found by making the examples compile:

- Referencing the factory's own result inside its config
  (`UI.useChatContext()`) is circular and blocks inference. The examples
  and the e2e route now use `createChatHookContexts()`, already the
  documented cycle-breaker.
- An inline `function` expression for `input` also blocks it. Arrows and
  named references infer fine, so the examples hoist the component and
  both UI docs call the trap out.

The default falls back to a union rather than `undefined` so that when
inference does degrade, `input` stays assignable and only `Input` goes
missing — a false negative instead of a rejected config.

Two doc snippets rendered an input they never registered; the new types
caught them.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

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

* fix(ui): warn instead of crashing on an unregistered Input

The conditional `Input` type hides the prop when no `input` is
registered, but that inference degrades on some config shapes, and the
runtime had no component to render in that case — React threw
"Element type is invalid" on an undefined element. Always supply a
component and warn once instead, matching the existing
"Missing tools.<name> component" warning. The floor is now consistent:
you find out at build time where inference works, and on the first dev
render where it doesn't.

The inference trigger turned out to be narrower than the previous commit
claimed. It is source order, not the `function` keyword: an inline
`function` expression for `input` infers fine as long as it is listed
before `layout`. Hoisting the component out of the config bought nothing
— the config literal is evaluated once at module scope, so an inline
component is already a stable reference — so the examples and the e2e
route go back to inline named components, ordered `input` first. Both UI
docs now describe the ordering rule rather than telling people to hoist.

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

* feat(ui)!: split createChatHook options into named component groups

`chatComponents` is gone. Registration is now flat on the factory call,
one group per kind:

  createChatHook({
    options,
    context: { chatContext, partContext, interruptContext },
    components: { input, layout, message },
    toolsComponents: { ... },
    interruptsComponents: { tools: { ... }, generic: { ... } },
    partsComponents: { ... },
  })

`createChatUI` takes the same shape as its second argument, so both
entry points register widgets identically. All four adapters (React,
Solid, Vue, Svelte) move together. Vue and Svelte keep their flat
internal runtime shape; the new config is mapped onto it at the factory
boundary, so nothing downstream of that changes.

`partsComponents` is plural, unlike the original sketch — the registries
are `parts`/`tools`/`interrupts` and the maps hold many entries;
`PartProps` and `ChatUIPartKey` stay singular because they describe one
part.

This also removes the `Input` inference fragility rather than
documenting around it. `layout` and `input` are now siblings inside
`components`, so TypeScript resolves that inner literal as a unit and
fixes `TInput` before contextually typing `layout`. Declaration order no
longer matters and an inline `function` expression works, both of which
broke inference when these keys sat at the config's top level. The
ordering caveat is out of both UI docs, and a type test pins the case
that used to fail so a future flattening cannot regress it silently.

Per instruction, no migration guidance was added anywhere; the existing
`docs/migration/create-ui.md` page covers the older package move only
and has been updated to the new shape rather than extended.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

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

* docs: drop migration guidance for the unreleased chat UI API

`createChatHook` and `createChatUI` have never been published, so
teaching them as something to migrate to was wrong. I checked before
cutting: `@tanstack/ai-react-ui@0.8.22` (and Solid 0.7.21, Vue 0.2.40)
export only the older `Chat` / `ChatMessages` / `ChatInput` compound
components, with no `createChatHook` anywhere.

So the page keeps the one migration that is real — swapping
`@tanstack/ai-*-ui` for the framework `/ui` subpath, which those
published packages do need — and loses the Before/After, Steps, and
Gotchas sections that walked through adopting the new factory. The new
API is documented in the UI guides as a new API.

The `Chat` component's `@deprecated` notices pointed at that page for
`createChatHook` guidance, so they now point at the matching UI guide.
The `docs/api/*` pages no longer advertise the migration page as reading
for the typed chat UI. The `*-ui` shim packages still link there, which
is still exactly what that page covers.

Also brings this branch in line with repo rules I had been working
without, having only picked up the worktree's CLAUDE.md late:

- Removed the em dashes the docs skill forbids from text added here.
- Bumped `updatedAt` in `docs/config.json` for the five pages with real
  content changes. The four `docs/api/*` pages only lost a stale link,
  which the rule classes as a factual fix that must not bump.
- Rewrote the `create-chat-hook` changeset to describe the shipping
  option groups instead of the old `chatComponents` shape, and aligned
  `typed-headless-chat-ui` with the new names. Since none of this has
  released, these describe the API rather than a change to it.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

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

* test(e2e): pin the Parts subtree against remounting mid-stream

`CLAUDE.md` requires E2E coverage for a behavior change and this stack
had none. The behavior worth guarding is the reason `Parts` reads a
message-scoped context instead of closing over the message: a `Parts`
rebuilt per render makes React remount every part on each stream chunk,
throwing away local state in tool and part widgets.

The purchase tool widget on `/headless-ui` now stamps a per-mount
sequence into `data-mount-seq`, and the spec asserts it stays at 1 across
the tool-call chunks and again across the resume stream after approval.

Verified the test actually catches the regression rather than just
passing: reverting `Parts` to a per-render closure and rebuilding takes
the widget to 7 mounts, and the assertion fails on the first check.

Full suite: 638 passed, 10 failed. Both failing specs
(`multi-turn-structured` across providers, and the flaky
`durable-takeover`) fail identically on the PR base `ad5c6c28d` with none
of this stack's changes present, verified in a scratch worktree. They run
against `examples/ts-react-chat`, which uses `useChat` and never touches
the `/ui` surface. `multi-turn-structured` is a fixture-playback problem:
turn 3 receives turn 2's recipe.

`pnpm test:ci` and `pnpm test:dts` pass across all 86 projects.

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

* docs(ui): add five chat UI recipes

The framework guides open with one example that registers tools,
interrupts, structured output, input and parts at once. A reader wiring
their first chat box has to skip past all of it. These five pages each do
one thing, in order:

1. `basic-chat` — layout, message, text part. No tools.
2. `format-a-tool` — one tool, branching on `part.state`.
3. `tool-approval` — `needsApproval`, inline or in the list.
4. `custom-interrupt` — `defineInterrupt` with your own schemas.
5. `request-context` — `forwardedProps` from screen to route.

Two gaps these close: there was no starting point smaller than the full
example, and nothing documented sending per-request context from the
chat UI. `docs/advanced/runtime-context.md` covers only the server half,
so the new page shows both and says plainly that `forwardedProps` is
client-controlled and must not be trusted for identity.

React only. The Solid, Vue and Svelte guides already point at the React
page for the full map, and each now links here too.

Named `recipes`, not `examples`: `scripts/verify-links.ts` treats any
path containing `/examples/` as a directory link for the site's app
examples, so `docs/ui/examples/*.md` reported every internal link
broken.

While writing these I checked the API instead of trusting the existing
prose, and corrected two things in my own drafts: the terminal tool state
is `complete`, not `output-available`, and `ToolCallPart` has no
`errorMessage` field. I also confirmed the grouped `ToolProps` snippet is
really type-checked by breaking it on purpose and watching kiira fail.

All 1183 doc snippets typecheck. `pnpm test:ci` and `pnpm test:dts` pass
across all 86 projects.

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

* ci: apply automated fixes

* feat(ui): add Queue layout slot and QueueProps

Layout receives a stable Queue component that maps pending sends. Each item has cancelQueued bound. Register the row with components.queue and QueueProps.

* ci: apply automated fixes

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Alem Tuzlak <t.zlak@hotmail.com>

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jack Herrington <jack.herrington@netlify.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Jack Herrington <jherr@pobox.com>

* fix(sandbox): do not leave completed runs marked detached

* feat(ui): add createChatHook on Preact, Octane, and Angular /ui

* ci: apply automated fixes

* fix(angular): compile Chat UI as an ng-packagr secondary entry

ng-packagr compiled src/ui twice and crashed with referencedFiles. Move the /ui sources to ui/ next to src. Import the primary package by name. Point export types at the generated d.ts file. Type the Preact and Octane doc snippets so kiira can check them.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jack Herrington <jack.herrington@netlify.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Jack Herrington <jherr@pobox.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-on: maintainer The ball is in the maintainers’ court

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants