Skip to content

docs: gate the custom chat layout's empty state on an empty thread - #3579

Merged
kojiwakayama merged 3 commits into
mainfrom
fix/dx-20260811-b2-11
Aug 11, 2026
Merged

kojiwakayama merged 3 commits into
mainfrom
fix/dx-20260811-b2-11

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Symptom

Found on a DX dogfood walk of https://veryfront.com/docs/code.

The Chat UI guide's "Compose a custom layout" sample was pasted verbatim into
app/custom-chat/page.tsx. After sending one message and getting a tool-calling
reply, the page renders: header, the full conversation, the composer — and then,
below the composer, the empty-state hero What can I help with? with its
Explain React hooks / Write a regex suggestion buttons still mounted.

agent-browser find placeholder "Ask me anything..." fill "What is 128 divided by 8?"
agent-browser find role button click --name "Send"
agent-browser snapshot -i
# -> still lists: heading "What can I help with?" [level=2],
#    button "Explain React hooks", button "Write a regex"

The guide's own custom-layout sample renders a visibly broken UI on first use,
and "Verify it worked" only said "custom layouts keep the message list and
composer wired to the same AG-UI stream", so nothing told the reader this was
wrong.

Root cause

ChatEmpty (src/react/components/chat/chat/composition/chat-empty.tsx) is a
prop-driven wrapper around ChatEmptyState.*. It never reads the chat context
and has no self-hiding behavior — it renders whatever it is given, always.

The <Chat> preset gates its own hero externally:

// src/react/components/chat/chat/controlled-chat.tsx:143,178
const isEmpty = messages.length === 0;
… : isEmpty && emptyState ? <ChatEmpty … /> : <ChatMessageList … />

<Chat.Root> already publishes isEmpty on the context
(chat-root.tsx:157), and <Chat.If> exists precisely to read it — but the
documented custom layout used neither. So the composition path had everything it
needed to self-hide and the sample simply never asked for it.

Fix

Wrap the hero in <Chat.If condition={(ctx) => ctx.isEmpty}> and move it above
the transcript, and state in prose that <Chat.Empty> does not hide itself and
that a custom layout owns that decision. Add a "Verify it worked" bullet that
actually catches the bug.

The same ungated pattern shipped in the veryfront/chat module example that
generates the API reference's "Custom layout (composition)" block
(src/chat/index.ts), so that gets the gate too, and ChatEmpty's own JSDoc —
which is the description rendered in docs/api-reference/veryfront/chat.md —
now says it never hides itself. docs/api-reference/veryfront/chat.md is the
regenerated deno task docs output (Deno 2.7.7, matching
.github/actions/setup-deno), not hand-edited, as is
src/server/handlers/dev/framework-candidates.generated.ts, which is derived
from source text and picks up the new comment tokens.

No runtime behavior changes: this is documentation plus JSDoc.

Regression test

tests/docs/guide-code-examples.test.ts, inside the existing
describe("Guide: chat-ui.md") suite — that file is where documented guide
examples are already exercised against the real public API, and the guide source
of truth is docs/guides/chat-ui.md in this repo (veryfront-docs
docs/code/guides/** is a synced copy of it).

The test does both halves:

  1. reads docs/guides/chat-ui.md and asserts the custom-layout sample wraps
    <Chat.Empty> in <Chat.If condition={(ctx) => ctx.isEmpty}>;
  2. renders the documented composition through renderToString and asserts the
    hero and its suggestions are absent with two messages, the transcript is
    still present, and the hero is present on an empty thread.

Confirmed red before the fix, for the right reason:

Guide: chat-ui.md ... gates the custom layout's empty state ... FAILED
error: AssertionError: custom layout sample gates the empty state with <Chat.If>

Green after, together with tests/docs/ (51 passed) and
src/react/components/chat/ (144 passed). scripts/docs/generate-api-reference.test.ts
passes under the CI-pinned Deno 2.7.7.

Follow-up (not in this PR)

The published copy at veryfront-docs/docs/code/guides/chat-ui.md carries the
same sample and is refreshed from this repo by update-reference.yml; a matching
PR is opened there so the live page is corrected before the next sync.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@kojiwakayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 4 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a1811780-4ff2-4249-863c-da4b4da16c4d

📥 Commits

Reviewing files that changed from the base of the PR and between 1114e69 and 6e49962.

⛔ Files ignored due to path filters (1)
  • src/server/handlers/dev/framework-candidates.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (6)
  • docs/api-reference/veryfront/chat.md
  • docs/guides/chat-ui.md
  • src/chat/index.ts
  • src/react/components/chat/chat/composition/chat-empty.tsx
  • src/react/components/chat/chat/index.tsx
  • tests/docs/guide-code-examples.test.ts

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

@kojiwakayama
kojiwakayama force-pushed the fix/dx-20260811-b2-11 branch from 78071bd to eb7d657 Compare August 11, 2026 10:16
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kojiwakayama

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

The Chat UI guide's "Compose a custom layout" sample drops `<Chat.Empty>`
straight into `<Chat.Root>` as the last child. `<Chat.Empty>` is a prop-driven
wrapper that renders whatever it is given and never reads the chat context, so
it never hides: after the first turn the page shows the header, the full
conversation, the composer, and then the empty-state hero with its suggestion
buttons still mounted below. The `<Chat>` preset gates its own hero on
`messages.length === 0` (controlled-chat.tsx), so only the copied sample is
broken - the docs' own custom layout renders a visibly wrong UI on first use.

Wrap the hero in `<Chat.If condition={(ctx) => ctx.isEmpty}>`, which is what
`ChatIf` and `ChatContextValue.isEmpty` exist for, and move it above the
transcript. Say in prose that `<Chat.Empty>` does not hide itself, and add the
same gate to the two `veryfront/chat` module examples that feed the API
reference.

Regression test in tests/docs/guide-code-examples.test.ts, next to the existing
"Guide: chat-ui.md" suite: it asserts the guide's custom-layout sample carries
the `<Chat.If>` gate, then renders the documented composition and asserts the
hero is absent with messages and present on a fresh thread. It fails on the
unfixed guide with "custom layout sample gates the empty state with <Chat.If>".

Found on a DX dogfood walk of veryfront.com/docs/code.
…ates

CI review of the previous commit.

`ban-chat-antipatterns` caps src/react/components/chat/chat/index.tsx at 278
LOC and the added `<Chat.If>` lines pushed it to 280. That module doc is a
duplicate of the `veryfront/chat` example in src/chat/index.ts, which is the one
the API reference is generated from - the barrel's copy produced no doc output
at all - so drop the edit there rather than raise the ceiling.

The remaining JSDoc additions feed
src/server/handlers/dev/framework-candidates.generated.ts, which is derived from
source text; regenerate it with the CI-pinned Deno 2.7.7 so
`generate:manifests:check` is clean.
The barrel module doc in src/react/components/chat/chat/index.tsx still
showed `<Chat.Empty>` dropped straight into `<Chat.Root>` - the exact
pattern the rest of this change fixes. It was left alone earlier only
because `ban-chat-antipatterns` caps that file at 278 LOC and the
multi-line gate pushed it to 280.

Write the gate on one line instead, so the example teaches the correct
pattern and the file stays at 278 LOC. The barrel's copy still produces
no API-reference output (the reference renders the `src/chat/index.ts`
example), so docs/api-reference is unchanged; only the derived
framework-candidates manifest picks up the new comment tokens,
regenerated with the CI-pinned Deno 2.7.7.
@kojiwakayama
kojiwakayama force-pushed the fix/dx-20260811-b2-11 branch from 3b9f69d to 6e49962 Compare August 11, 2026 10:40
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (through #3564, which reworked the same
docs/guides/chat-ui.md chat page samples) and force-pushed. The rebase was
clean and the custom-layout delta is unchanged; tests/docs/guide-code-examples.test.ts
and #3564's new tests/docs/guide-content.test.ts assertions pass together
(36 passed).

One extra commit on top of the original two:

docs: gate the chat barrel's custom-layout example too — the module doc in
src/react/components/chat/chat/index.tsx still showed <Chat.Empty> dropped
straight into <Chat.Root>, i.e. the exact pattern the rest of this PR fixes.
The earlier commit dropped that edit only because ban-chat-antipatterns caps
that file at 278 LOC and the multi-line gate pushed it to 280. Writing the gate
on one line keeps the file at 278 and still teaches the correct pattern.

That barrel copy still produces no API-reference output (the reference renders
the src/chat/index.ts example), so docs/api-reference/veryfront/chat.md is
unchanged; only the derived framework-candidates.generated.ts picks up the two
new comment tokens, regenerated with the CI-pinned Deno 2.7.7.

CI note: coverage shard 2/8 went red once on
src/transforms/esm/http-cache.test.ts:803 — "returns a signal-less cache
follower after its bounded wait" — which is the known flake from #3553. Because
tests (unit) and coverage gate depend on the shards, that one flake showed as
three red checks. Re-ran the failed jobs on the identical commit and all three
passed; the test itself was not touched. All 27 checks are green.

@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 46fe6da Aug 11, 2026
57 of 60 checks passed
@kojiwakayama
kojiwakayama deleted the fix/dx-20260811-b2-11 branch August 11, 2026 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant