Skip to content

docs(rfc): replace RFC 29's blanket banner with a linted per-symbol status ledger - #3630

Merged
kojiwakayama merged 9 commits into
mainfrom
rfc-status-pass
Aug 12, 2026
Merged

docs(rfc): replace RFC 29's blanket banner with a linted per-symbol status ledger#3630
kojiwakayama merged 9 commits into
mainfrom
rfc-status-pass

Conversation

@kojiwakayama

@kojiwakayama kojiwakayama commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

The drift

RFC 29 is a pending spec, not archaeology: the chat UI library shipped first, and RFC 29 proposes to reset it. The corpus is accurate and live — which is exactly why its status claim was dangerous.

All 59 sub-pages carried one identical, page-level banner:

Status: proposed (RFC). This page documents the proposed API shape - not yet implemented.

That is a claim about a whole page, so no machine can check it, and an RFC that lands piecemeal silently falsifies it. It was already false: 57 of 59 page titles resolve to live exports, and #3277 shipped the prop-getter surface, made mergeProps public, and landed the IME guard without a single banner moving.

1. The status pass

The blanket banner is gone. Every page now carries a status block that names, symbol by symbol, what veryfront/chat exports today and what it does not:

> **Status: RFC 29 - partly landed.** Per-symbol truth, verified against `src/` by `deno task lint:rfc-status`:
>
> - **Exported from `veryfront/chat` today:** `useChatScroll`, `UseChatScrollOptions`, `UseChatScrollResult`, `useStickToBottom`
> - **Not exported today:** none
> - **Not in `src/` today:** `isAutoScrolling`, `currentAnchorId`, `visibleMessageIds`, `turnAnchor`, …

The third list carries props and hook members, which are not exports — the submitMode / getDropTargetProps class of claim.

Deltas that have actually landed are badged on their own heading, with the source they landed in:

### `ChatInput.Field` - `changed` - `partly shipped` (src/react/components/chat/chat/composition/chat-composer.types.ts:18)

README.md gains a Reading the status block section and the roll-up of the complete landed set. An exported symbol is explicitly not a landed delta — ChatInput.Submit ships; the RFC's reshape of it does not — and the docs now say so instead of leaving it implied.

What was actually found to have landed

I did not assume the two named in the brief. Checking the RFC's kept/changed/new/removed ledgers against the real exports turned up seven:

Delta Status Evidence
mergeProps made public shipped src/react/components/chat/chat/hooks/use-chat-input.ts:85
useMessageBranches shipped src/react/components/chat/chat/contexts/message-context.tsx:87
useChatInputContext / …Optional / ChatInputContextProvider naming shipped src/react/components/chat/chat/contexts/composer-context.tsx:83
ChatInput flat sub-part exports (ChatInputField, … + Props types) shipped src/chat/index.ts:253
useChatInput + five prop getters partly shipped src/react/components/chat/chat/hooks/use-chat-input.ts:155
useChatScroll partly shipped src/react/components/chat/chat/hooks/use-stick-to-bottom.ts:177
ChatInput.Field IME guard + full native textarea surface partly shipped src/react/primitives/input-box.tsx:37

Each partly shipped section spells out what landed and what did not — e.g. useChatInput ships the getters but still takes no options argument, still exposes input/isLoading rather than value/status, and has no submit/stop/clear/attach or getDropTargetProps.

All 14 new badges were re-verified and are still honest: Message.File, Message.Image, AttachmentsPanel.Item.Name/.Size, ChatSidebar.Item.Title, ChatSidebar.Item.Menu.Trigger genuinely do not exist.

2. Making it self-policing

scripts/lint/audit-rfc-status.ts is a sibling of audit-chat-composability.ts and reuses its collectCompoundParts — same class of mechanical doc-truth check, aimed at the RFC pages instead of the Storybook trees.

The rule the drift needed, and five more that keep the fix from rotting:

  1. No blanket claims — the un-checkable page-wide phrasings are banned.
  2. Every page has a status block, in the documented grammar.
  3. Every symbol claimed exported must really be on the public surface.
  4. Every symbol claimed absent must really be absent — a page may not say "not yet implemented" about something that ships.
  5. A shipped badge must cite a path:line that resolves (file exists, line in range) — the reverse direction, so a fake "shipped" is caught too.
  6. The banner's landed-ness word must agree with the badges below it — so the page-level summary can never drift from its deltas again. This is the original bug, made structurally impossible.

Resolution is real, not textual: barrel exports plus Object.assign compounds, plain object-literal compounds (ChatEmptyState), …Compound aliases (ChatSidebar.Item.Menu), and flat declarations.

Confirmed failing first

Against the corpus as it stood, 124 violations. And with the ledger written but mergeProps still on the not-implemented side — the known-wrong page — the check produced exactly the finding it exists for:

docs/rfcs/29-chat-api-shape/helpers.md:5: "mergeProps" is documented as not yet
  implemented, but it ships from `veryfront/chat` today.
  Move it to "**Exported from `veryfront/chat` today:**".

17 unit tests in audit-rfc-status.test.ts cover each rule in both directions, including that regression by name.

Wiring

lint:rfc-status joins lint:ci, verify, and verify:quick. While wiring it I found that lint:chat-composability was in verify but never in lint:ci — CI has not been running the existing doc-truth audit at all. Both now run in the lint shard, and both test files join test:scripts (neither was listed).

3. Attribution

ban-chat-antipatterns.ts and audit-chat-composability.ts enforce RFC 29's principles but never cited it. Both now name the rules they mechanise (ban-chat-antipatterns → hard rules 1 and 7, the icon-slot ban, ref-as-prop; audit-chat-composability → the node contract), so the corpus is discoverable from the code that enforces it.

Incidental

collectCompoundParts now recognises shorthand properties, so Object.assign(ChatBase, { Message, ErrorBanner }) stops reading as missing anatomy. Covered by a new test; lint:chat-composability stays green.

Verification

  • deno task lint:rfc-status — 63 pages, green
  • deno task lint:chat-composability — 22 compounds, green
  • deno task lint:chat-ratchets — green
  • deno task docs:check-links — 1305 links OK
  • deno fmt --check (Deno 2.7.7, CI's pin) — 5007 files, clean
  • audit-rfc-status.test.ts + audit-chat-composability.test.ts — 23 steps, green
  • full pre-push suite — green

Nothing in docs/rfcs was deleted.

Summary by CodeRabbit

  • Documentation

    • Updated chat API RFC documentation with verified export status, shipped functionality, remaining proposals, and source references.
    • Clarified RFC status indicators and per-symbol implementation state.
  • Quality Improvements

    • Added automated validation for RFC status accuracy, exports, badges, and source anchors.
    • Improved chat composability checks and expanded test coverage.
  • Chores

    • Integrated RFC status checks into verification and testing workflows.

…tatus ledger

RFC 29 proposes a reset of a chat library that already shipped, and it is
landing piecemeal. All 59 sub-pages carried the same page-level banner - "this
page documents the proposed API shape - not yet implemented" - which is a claim
no machine can check, so it rotted the moment #3277 shipped the prop-getter
surface, made `mergeProps` public, and landed the IME guard. 57 of 59 page
titles already resolved to live exports while the banner said otherwise.

Status pass. Every page now carries a status block that names, symbol by
symbol, what `veryfront/chat` exports today and what it does not, plus a third
list for props and hook members that are not exports. Deltas that have actually
landed are badged on their own heading - `shipped` or `partly shipped` - with
the source file and line they landed in. `docs/rfcs/29-chat-api-shape/README.md`
explains how to read a status block and rolls up the complete landed set.

Seven deltas were found to have landed, not the two already known:
`mergeProps` public, `useMessageBranches`, the `ChatInputContext*` naming, the
`ChatInput` flat sub-part exports (all whole); `useChatInput` + its five prop
getters, `useChatScroll`, and `ChatInput.Field`'s IME guard + native textarea
surface (all partial, with the remainder spelled out).

Self-policing. `scripts/lint/audit-rfc-status.ts` is a sibling of
`audit-chat-composability.ts` and does the same class of mechanical doc-truth
check: it bans the blanket phrasings, requires a status block on every page,
and asserts that every symbol a page claims is *not* implemented is genuinely
absent from the real public surface (barrel exports plus `Object.assign` and
object-literal compound parts) - the rule the drift needed. It also verifies
the reverse direction, that every `shipped` badge cites a `path:line` that
resolves, and that the banner's landed-ness word agrees with the badges below
it, so the page-level summary can never drift from its deltas again.

Confirmed failing before the pass: 124 violations on the corpus as it stood,
including the exact known-wrong claim -
  helpers.md:5: "mergeProps" is documented as not yet implemented, but it
  ships from `veryfront/chat` today.

Wired into `lint:ci`, `verify`, and `verify:quick`. `lint:chat-composability`
was in `verify` but not in `lint:ci`, so CI never ran it; both audits now run
in the lint shard, and both their test files join `test:scripts`.

Also: `collectCompoundParts` now recognises shorthand properties, so
`Object.assign(ChatBase, { Message, ErrorBanner })` stops reading as a lie; and
`ban-chat-antipatterns.ts` and `audit-chat-composability.ts` now cite the RFC
rules they mechanise, which is what makes the corpus discoverable from the code.
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 2 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: 440a613c-bff2-4842-b659-2cfcbb8ed94e

📥 Commits

Reviewing files that changed from the base of the PR and between 94692bf and ffe70a4.

📒 Files selected for processing (2)
  • scripts/lint/audit-rfc-status.test.ts
  • scripts/lint/audit-rfc-status.ts
📝 Walkthrough

Walkthrough

RFC 29 documentation now records verified export and implementation status. A new Deno lint audits those records against the chat public surface and source anchors. Verification tasks run the audit and its tests. Chat composability linting masks nested structures and recognizes uppercase shorthand parts.

Changes

RFC 29 status verification

Layer / File(s) Summary
RFC status audit engine
scripts/lint/audit-rfc-status.ts, scripts/lint/audit-rfc-status.test.ts
Adds public-surface collection, ledger parsing, shipped-anchor validation, page auditing, roll-up checks, CLI execution, and comprehensive tests.
Core RFC status ledgers
docs/rfcs/29-chat-api-shape.md, docs/rfcs/29-chat-api-shape/README.md, docs/rfcs/29-chat-api-shape/helpers.md, docs/rfcs/29-chat-api-shape/components/chat-input.md, docs/rfcs/29-chat-api-shape/hooks/*
Documents partly landed APIs, shipped symbols, unexported symbols, source anchors, and remaining proposed behavior.
Component and hook status blocks
docs/rfcs/29-chat-api-shape/components/*, docs/rfcs/29-chat-api-shape/hooks/*, docs/rfcs/29-chat-api-shape/providers.md
Replaces generic proposal notices with per-symbol export and implementation status records.
Verification task wiring and lint coverage
deno.json, scripts/lint/audit-chat-composability.ts, scripts/lint/audit-chat-composability.test.ts, scripts/lint/ban-chat-antipatterns.ts
Adds RFC-status tasks and tests, masks nested compound structures, recognizes uppercase shorthand parts, and links lint rules to RFC 29.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: kwakayama

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes replacing RFC 29's blanket status banner with a linted per-symbol status ledger.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch rfc-status-pass

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (2)
deno.json (1)

544-544: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Run both audit tests in CI.

lint:ci runs in the CI lint job, but it does not run scripts/lint/audit-rfc-status.test.ts or scripts/lint/audit-cross-runtime-jsr.test.ts. Add both tests to a CI-executed command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@deno.json` at line 544, The test:scripts command omits
audit-rfc-status.test.ts and audit-cross-runtime-jsr.test.ts from CI execution.
Add both test files to the CI-executed test command, preserving the existing
test configuration and command flow.

Source: Learnings

docs/rfcs/29-chat-api-shape/hooks/use-upload.md (1)

5-10: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a "Not in src/ today" list for the proposed members.

useUpload is exported, so the ledger reports the page as fully covered. The UseUploadResult shape at Lines 33-41 documents getDropTargetProps, getAttachInputProps, and retry, which the README at Line 20 says belong in the third list. Without that list, no rule pins those member claims, and the page can drift the same way the old banner did.

The README explicitly names getDropTargetProps as an example for this list.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/rfcs/29-chat-api-shape/hooks/use-upload.md` around lines 5 - 10, Add a
“Not in src/ today” status list to the RFC status block, documenting the
proposed UseUploadResult members getDropTargetProps, getAttachInputProps, and
retry as not currently present. Keep useUpload under the existing
exported-symbol list so the status ledger distinguishes the hook export from its
proposed result-shape members.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/rfcs/29-chat-api-shape.md`:
- Line 12: Change the “Already landed - shipped” heading in the RFC to level 2
(`##`) instead of level 3, preserving the heading text so the existing
SHIPPED_RE audit continues to match and markdownlint MD001 passes.
- Line 14: Update the status sentence in the RFC introduction to remove the
uncheckable document-wide claim that nothing else is implemented. Direct readers
to the per-piece ledgers as the authority, using wording that describes
remaining deltas as proposals while preserving the existing lint-authority
statement.

In `@docs/rfcs/29-chat-api-shape/components/agent-picker.md`:
- Around line 5-10: The status ledgers omit symbols documented as public
exports. In docs/rfcs/29-chat-api-shape/components/agent-picker.md lines 5-10,
add AgentPickerItem to the exported-symbol ledger or remove its flat-export
documentation; in docs/rfcs/29-chat-api-shape/hooks/use-sources.md lines 5-10,
add extractSourcesFromParts to the ledger or stop documenting it as a
veryfront/chat export, keeping each page’s ledger aligned with its API and
import sections.

In `@docs/rfcs/29-chat-api-shape/components/app-shell.md`:
- Around line 5-10: Correct the AppShell status block to use the actual package
boundary identified by the page, `veryfront/ui`, instead of `veryfront/chat`.
Update the related status/audit references consistently so the listed AppShell
symbols and their import guidance reflect the UI package.

In `@docs/rfcs/29-chat-api-shape/components/attachment-pill.md`:
- Around line 5-10: Align the status blocks with the documented public imports:
in docs/rfcs/29-chat-api-shape/components/attachment-pill.md:5-10, classify
AttachmentPillRetry and AttachmentPillRetryProps as not exported or mark their
import as proposed; in
docs/rfcs/29-chat-api-shape/components/attachments-panel.md:5-10, list
AttachmentsPanelItemName separately from AttachmentsPanel.Item.Name and
AttachmentsPanel.Item.Size; in
docs/rfcs/29-chat-api-shape/components/chat-agent-picker.md:5-10, add
agentsToPickerOptions and ChatAgentPickerProps to exported symbols; and in
docs/rfcs/29-chat-api-shape/hooks/use-chat-context.md:5-10, add
useChatContextOptional to exported symbols.

In `@docs/rfcs/29-chat-api-shape/components/chat-input.md`:
- Around line 180-184: Align the landing-site references for the ChatInput.Field
delta: update the badge anchor on the “changed” row or the README roll-up to use
the same primary file, and explicitly identify the other file as the secondary
implementation location if both remain relevant. Preserve the existing
descriptions of ChatInputFieldProps and handleInputBoxKeyDown.

In `@docs/rfcs/29-chat-api-shape/helpers.md`:
- Line 41: Update the sentence beginning “What has not landed with it” in
helpers.md to point readers to the reference table above rather than saying
“below,” while preserving the distinction between the proposed public helpers
and the current barrel.

In `@docs/rfcs/29-chat-api-shape/hooks/use-conversations-context.md`:
- Around line 5-10: Complete the status ledgers by adding
useConversationsContextOptional in
docs/rfcs/29-chat-api-shape/hooks/use-conversations-context.md:5-10,
useMessageContextOptional in
docs/rfcs/29-chat-api-shape/hooks/use-message-context.md:5-10, and
groupPartsInOrder in docs/rfcs/29-chat-api-shape/hooks/use-message-parts.md:5-10
to each page’s “Exported from veryfront/chat today” list.

In `@docs/rfcs/29-chat-api-shape/README.md`:
- Line 10: Update the RFC introduction around the sentence beginning “Those
five” to match the seven-row complete landed table and avoid treating exported
symbols as shipped deltas. Refer readers to the status block for shipped-delta
interpretation, while preserving the rule that exported symbols alone do not
constitute landed changes.

In `@scripts/lint/audit-chat-composability.test.ts`:
- Around line 60-75: Update the imports used by the test containing “collects
shorthand properties as sub-parts” to use describe/it from
`#veryfront/testing/bdd.ts` and assertions from `#veryfront/testing/assert.ts`,
removing the `#std/`... test-helper imports while preserving the test behavior.

In `@scripts/lint/audit-chat-composability.ts`:
- Around line 74-78: The shorthand discovery loop using shorthandRe must collect
only properties at the direct level of the Object.assign compound object,
excluding identifiers inside nested object literals such as Config: { Message }.
Track brace/object depth while scanning or replace the regex approach with an
AST-based property scan, and add a regression test covering nested shorthand
properties; preserve the existing direct shorthand detection used by
findCompositionLies and loadSurface.

In `@scripts/lint/audit-rfc-status.test.ts`:
- Around line 1-2: Update the imports in the audit RFC status test to use
describe and it from `#veryfront/testing/bdd.ts` and assertEquals from
`#veryfront/testing/assert.ts`, replacing the direct `#std/testing/bdd` and
`#std/assert` entry points.

In `@scripts/lint/audit-rfc-status.ts`:
- Around line 213-240: Update scripts/lint/audit-rfc-status.ts:213-240 in
parseLedger to detect more than one status banner, preserve the duplicate-block
violation through its return value or error state, and have auditPage report it
instead of silently using the last banner. Update
scripts/lint/audit-rfc-status.test.ts:70-259 with a case containing both
LANDED_BANNER and UNLANDED_BANNER that asserts the duplicate-block violation.

---

Nitpick comments:
In `@deno.json`:
- Line 544: The test:scripts command omits audit-rfc-status.test.ts and
audit-cross-runtime-jsr.test.ts from CI execution. Add both test files to the
CI-executed test command, preserving the existing test configuration and command
flow.

In `@docs/rfcs/29-chat-api-shape/hooks/use-upload.md`:
- Around line 5-10: Add a “Not in src/ today” status list to the RFC status
block, documenting the proposed UseUploadResult members getDropTargetProps,
getAttachInputProps, and retry as not currently present. Keep useUpload under
the existing exported-symbol list so the status ledger distinguishes the hook
export from its proposed result-shape members.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2dc21666-0332-47be-9d8a-182e9669aad6

📥 Commits

Reviewing files that changed from the base of the PR and between 2b3a0e0 and 2f9e57d.

📒 Files selected for processing (69)
  • deno.json
  • docs/rfcs/29-chat-api-shape.md
  • docs/rfcs/29-chat-api-shape/README.md
  • docs/rfcs/29-chat-api-shape/components/agent-card.md
  • docs/rfcs/29-chat-api-shape/components/agent-picker.md
  • docs/rfcs/29-chat-api-shape/components/app-shell.md
  • docs/rfcs/29-chat-api-shape/components/attachment-pill.md
  • docs/rfcs/29-chat-api-shape/components/attachments-panel.md
  • docs/rfcs/29-chat-api-shape/components/branch-picker.md
  • docs/rfcs/29-chat-api-shape/components/chat-actions.md
  • docs/rfcs/29-chat-api-shape/components/chat-agent-picker.md
  • docs/rfcs/29-chat-api-shape/components/chat-empty-state.md
  • docs/rfcs/29-chat-api-shape/components/chat-error-boundary.md
  • docs/rfcs/29-chat-api-shape/components/chat-input.md
  • docs/rfcs/29-chat-api-shape/components/chat-message-list.md
  • docs/rfcs/29-chat-api-shape/components/chat-root.md
  • docs/rfcs/29-chat-api-shape/components/chat-sidebar.md
  • docs/rfcs/29-chat-api-shape/components/chat-theme-scope.md
  • docs/rfcs/29-chat-api-shape/components/chat.md
  • docs/rfcs/29-chat-api-shape/components/inline-citation.md
  • docs/rfcs/29-chat-api-shape/components/markdown.md
  • docs/rfcs/29-chat-api-shape/components/message-action-bar.md
  • docs/rfcs/29-chat-api-shape/components/message.md
  • docs/rfcs/29-chat-api-shape/components/model-selector.md
  • docs/rfcs/29-chat-api-shape/components/reasoning.md
  • docs/rfcs/29-chat-api-shape/components/sources.md
  • docs/rfcs/29-chat-api-shape/components/step-indicator.md
  • docs/rfcs/29-chat-api-shape/components/tool-call.md
  • docs/rfcs/29-chat-api-shape/helpers.md
  • docs/rfcs/29-chat-api-shape/hooks/consumed-from-ui.md
  • docs/rfcs/29-chat-api-shape/hooks/use-agent-card.md
  • docs/rfcs/29-chat-api-shape/hooks/use-agent-metadata.md
  • docs/rfcs/29-chat-api-shape/hooks/use-agent-picker.md
  • docs/rfcs/29-chat-api-shape/hooks/use-agent.md
  • docs/rfcs/29-chat-api-shape/hooks/use-agents.md
  • docs/rfcs/29-chat-api-shape/hooks/use-attachment-pill.md
  • docs/rfcs/29-chat-api-shape/hooks/use-attachments-panel.md
  • docs/rfcs/29-chat-api-shape/hooks/use-attachments.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-actions.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-context.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-error-handler.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-input-context.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-input.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-scroll.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-sidebar-item.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat.md
  • docs/rfcs/29-chat-api-shape/hooks/use-clipboard.md
  • docs/rfcs/29-chat-api-shape/hooks/use-completion.md
  • docs/rfcs/29-chat-api-shape/hooks/use-conversation-chat.md
  • docs/rfcs/29-chat-api-shape/hooks/use-conversation.md
  • docs/rfcs/29-chat-api-shape/hooks/use-conversations-context.md
  • docs/rfcs/29-chat-api-shape/hooks/use-conversations.md
  • docs/rfcs/29-chat-api-shape/hooks/use-message-branches.md
  • docs/rfcs/29-chat-api-shape/hooks/use-message-context.md
  • docs/rfcs/29-chat-api-shape/hooks/use-message-parts.md
  • docs/rfcs/29-chat-api-shape/hooks/use-model-selector.md
  • docs/rfcs/29-chat-api-shape/hooks/use-reasoning.md
  • docs/rfcs/29-chat-api-shape/hooks/use-sources.md
  • docs/rfcs/29-chat-api-shape/hooks/use-step-indicator.md
  • docs/rfcs/29-chat-api-shape/hooks/use-streaming.md
  • docs/rfcs/29-chat-api-shape/hooks/use-tool-call.md
  • docs/rfcs/29-chat-api-shape/hooks/use-upload.md
  • docs/rfcs/29-chat-api-shape/hooks/use-voice-input.md
  • docs/rfcs/29-chat-api-shape/providers.md
  • scripts/lint/audit-chat-composability.test.ts
  • scripts/lint/audit-chat-composability.ts
  • scripts/lint/audit-rfc-status.test.ts
  • scripts/lint/audit-rfc-status.ts
  • scripts/lint/ban-chat-antipatterns.ts

Comment thread docs/rfcs/29-chat-api-shape.md Outdated
Comment thread docs/rfcs/29-chat-api-shape.md Outdated
Comment thread docs/rfcs/29-chat-api-shape/components/agent-picker.md
Comment thread docs/rfcs/29-chat-api-shape/components/app-shell.md
Comment thread docs/rfcs/29-chat-api-shape/components/attachment-pill.md
Comment thread docs/rfcs/29-chat-api-shape/README.md Outdated
Comment thread scripts/lint/audit-chat-composability.test.ts
Comment thread scripts/lint/audit-chat-composability.ts
Comment thread scripts/lint/audit-rfc-status.test.ts Outdated
Comment thread scripts/lint/audit-rfc-status.ts

@kwakayama kwakayama left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found three medium-severity documentation-lint correctness gaps.

  • [Medium] False “exported” compound parts pass validation. resolvesOnSurface accepts a dotted member whenever a same-named flat declaration exists, even when the member is not attached to the public compound (scripts/lint/audit-rfc-status.ts:188). Thus a private ChatInputPreview declaration makes a ledger claim that ChatInput.Preview is exported pass despite no ChatInput.Preview runtime property. Require an actual compound attachment/alias chain, and add the missing negative test.

  • [Medium] The promised “exactly one status ledger” rule is not implemented. The parser silently overwrites each matched banner/list while scanning (scripts/lint/audit-rfc-status.ts:221), and auditPage validates only that final ledger (scripts/lint/audit-rfc-status.ts:315). A page can retain an incorrect first ledger and append a correct second ledger; the lint passes. Count and reject duplicate banners and each required list, with a regression test.

  • [Medium] shipped anchors prove only that an arbitrary file and line exist, not that the cited change landed there. The validator performs only existence and line-range checks (scripts/lint/audit-rfc-status.ts:375), while the docs present the anchor as the source where the delta landed (docs/rfcs/29-chat-api-shape/README.md:24). An unrelated in-range src/ line passes, allowing stale or fabricated shipping evidence. Validate the cited symbol/change at the anchor (or use a stronger structured reference) and test an unrelated-but-in-range anchor.

Rubric Score
Correctness 27/40
Tests 11/20
Reliability/security 15/15
Maintainability 11/15
Scope/docs 7/10
Total 71/100

Review-Gate:
Reviewer: Codex
Reviewed-SHA: 2f9e57d
Score: 71/100
Actionable-Findings: 3
Verdict: REQUEST_CHANGES

- RFC root: h1 -> h2 for the landed section (MD001), and drop the
  un-checkable document-wide "nothing else has been implemented"
  negative in favour of pointing at the per-piece ledgers.
- audit-rfc-status: ban that phrasing class by pattern, not substring,
  so the next paraphrase cannot slip through; enforce rule 2 (exactly
  one status block per page) instead of only documenting it.
- audit-chat-composability: collect only the compound's OWN properties.
  `Object.assign(Base, { Config: { Message } })` was registering
  `Message` as a direct `Base.Message` part, which let an invalid
  composition token pass.
- Complete six status ledgers with exports the pages already document
  (extractSourcesFromParts, agentsToPickerOptions, ChatAgentPickerProps,
  useChatContextOptional, useConversationsContextOptional,
  useMessageContextOptional, groupPartsInOrder).
- ChatInput.Field landed in two files; say so on the page and in the
  roll-up instead of citing one anchor in each.
- README: 'those five' no longer reads as 'five deltas shipped'; note
  that Import blocks show the proposed shape.
- app-shell: AppShell's home is veryfront/ui AND it is re-exported from
  veryfront/chat - say both rather than implying it is not on the chat
  surface.
- helpers: the reference table is above, not below.
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 12, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 12, 2026

@kwakayama kwakayama left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • [P1] Restore the build cache ignore marker. src/build/production-build/build/build-orchestrator.ts:87 now initializes the build immediately after logging, removing the prior ensureCacheDirIgnored() call. Outside production, the default cache is <cwd>/.cache (src/utils/cache-dir.ts:85), and its own implementation documents that adopted projects otherwise commit generated bundles via git add -A (src/utils/cache-dir.ts:152). The targeted regression test was also deleted from tests/integration/server/build/build.test.ts. Restore the best-effort marker before build cache use and retain that test.

  • [P1] Do not present a bare root curl as deployment verification. docs/getting-started/deploy-project.md:97101 says curl -sSf <environment-url> confirms success, but protected environments deliberately return 302 without a session (src/proxy/proxy-access-control.ts:268), which curl -f treats as success. It also incorrectly rejects valid API-only/dynamic-only deployments: deployment readiness intentionally has no route when no static page exists (cli/shared/deployment/deploy-project.ts:1293). Restore a route-specific status/redirect check and the protected-environment guidance.

  • [P2] Enforce one complete ledger, not merely one banner. scripts/lint/audit-rfc-status.ts:251256 overwrites each symbol list every time it encounters a matching label, while :349 rejects only duplicate banners. A page can therefore put a false Not exported line first and a clean line later; auditPage reports zero violations. Count and reject duplicate Exported/Not-exported/Not-in-src lines within the status block, with a regression test.

  • [P2] The RFC still contains the blanket claims this lint is meant to prohibit. docs/rfcs/29-chat-api-shape.md:14 says “Every other delta is still a proposal,” and docs/rfcs/29-chat-api-shape/README.md:34 says “Everything else … is still a proposal.” These are uncheckable corpus-wide negatives, yet BLANKET_CLAIMS only matches “nothing else” (scripts/lint/audit-rfc-status.ts:64). Remove these claims or make the complete delta set machine-readable and validate it.

Rubric: correctness 28/40 · tests 10/20 · reliability/security 13/15 · maintainability 9/15 · scope/docs 4/10.

Review-Gate:
Reviewer: Codex
Reviewed-SHA: 399428a
Score: 64/100
Actionable-Findings: 4
Verdict: REQUEST_CHANGES

…e blanket claim

Two gaps in the RFC 29 status lint, both the same shape as bugs it had
already fixed one level up.

`parseLedger` overwrote each symbol list every time it met a matching
label while `auditPage` rejected only duplicate *banners*, so a page
could put a false `Not exported today:` line first and a clean one below
it and audit clean - the drift the ledger exists to catch, hidden inside
a single well-formed status block. Each list now carries its own line
numbers and every line past the first is rejected, exactly as extra
banners already were.

`BLANKET_CLAIMS` matched only the negative voice ("nothing else ...
implemented"), so the same un-checkable corpus-wide negative survived
inverted into the positive voice: "Every other delta is still a
proposal", "Everything else in this corpus is still a proposal". A
fourth pattern keys on a residual quantifier followed by "proposal",
which cannot reach the per-page banners - they say "proposed" of one
named page and carry no residual quantifier.

Deleting those two sentences alone would only hide the claim, because
the reference index's table still asserts it is "the complete set". So
the set is now validated (rule 7): every page badging a delta `shipped`
must have a row, and every row must point at a page that carries one.
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed pass. Two of the four hold up and are now fixed; the two P1s do not apply to this branch, and I want to lay out the evidence rather than just assert it.

The two P1s are stale-base artifacts

The gate reviewed 399428ab, which is the first parent of the current head 2089445. That merge commit pulled 9 commits from origin/main into the branch, and both P1 findings are about code those commits changed.

The cleanest way to see it: this PR does not touch either file. Its entire diff against main is deno.json, docs/rfcs/29-chat-api-shape**, and scripts/lint/** — no src/, no tests/, no cli/:

$ git diff --name-only origin/main...HEAD | grep -E '^(src|tests|cli)/'
(no output)

P1 — "Restore the build cache ignore marker"

At the current head the marker is present, and the file is byte-identical to main:

  • src/build/production-build/build/build-orchestrator.ts:5import { ensureCacheDirIgnored } from "#veryfront/utils/cache-dir.ts";
  • src/build/production-build/build/build-orchestrator.ts:95await ensureCacheDirIgnored();
  • git diff origin/main...HEAD -- src/build/production-build/build/build-orchestrator.ts → empty

The regression test is also present, not deleted: tests/integration/server/build/build.test.ts:76, it("marks the local cache root as ignored so a build cannot dirty the project's git history", …).

What happened is the reverse of the finding: the call and its test were added on main by #3606 (2b3a0e096), which landed after 399428ab. The reviewed tree genuinely did not contain ensureCacheDirIgnored — because the branch's base predated #3606, not because this branch removed anything. The merge at 2089445 brought it in.

P1 — "Do not present a bare root curl as deployment verification"

docs/getting-started/deploy-project.md is likewise identical to main (git diff origin/main...HEAD -- docs/getting-started/deploy-project.md → empty), and at the current head it already says the opposite of what the finding describes — it warns against the bare curl:

  • :156 — the route-specific check: curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' <environment-url>/<route>
  • :160:166 — the API-only / dynamic-only caveat: "A deployment whose routes are all API routes, or whose only pages are dynamic like /blog/[slug], has no root page to answer with … a successful deploy does not imply a 200 anywhere."
  • :168:174 — the protected-environment guidance: "A protected environment answers 302 to the sign-in page … or 403 for a signed-in non-member. … Do not check with a bare curl -sSf <environment-url>: curl does not treat a 302 as a failure, so that command exits 0 with an empty body whether or not the deployment works."

At 399428ab that section did read curl -sSf <environment-url> at :97:101, exactly as quoted — so the observation was accurate about the tree that was read. It was corrected on main by #3616 (7c9ea564a) and #3627 (82ae2f73c), both after the reviewed SHA.

So both findings describe real past states of main, already fixed there, and now present on this branch via the merge. I've left both files untouched — changing working code to satisfy them would reintroduce the bugs #3606 and #3627 fixed.

Both P2s were real and are fixed

P2 — "Enforce one complete ledger, not merely one banner"

Correct, and it was the duplicate-banner bug one level down. Reproduced before fixing: three tests covering a repeated Not exported today: / Exported from …: / Not in src/ today: line inside a single status block all reported zero violations.

Fix in scripts/lint/audit-rfc-status.ts: parseLedger now records exportedLines / absentLines / unbuiltLines alongside bannerLines, and auditPage rejects every line past the first for each list, the same way it already rejected extra banners. The pre-existing "second status block" test now asserts [8, 10, 11] — a duplicated block duplicates its lists too, so all three are reported.

P2 — "The RFC still contains the blanket claims this lint is meant to prohibit"

Also correct. BLANKET_CLAIMS matched only the negative voice ("nothing else …"), and both surviving claims were the same corpus-wide negative inverted into the positive voice.

Two changes:

  1. A fourth pattern, keyed on a residual quantifier followed by "proposal":
    /\b(?:everything|anything|every|all)\s+(?:else|other)\b[^.]*\bproposals?\b/i
    Before the doc edit it failed on exactly the two lines you named and nothing else across all 63 pages:

    2 RFC status violation(s):
      docs/rfcs/29-chat-api-shape.md:14: blanket status claim "everything else / every other … is still a proposal"
      docs/rfcs/29-chat-api-shape/README.md:34: blanket status claim "everything else / every other … is still a proposal"
    

    On your explicit question — it does not swallow UNLANDED_BANNER. I checked that directly: the banner says "proposed" (not "proposal") of a single named page and carries no residual quantifier, so it fails both halves of the pattern. Verified against UNLANDED_BANNER, LANDED_BANNER, the "no status badge - still proposed" legend, and the "every proposed component and hook" line — all non-matching; the corpus run above confirms it at scale.

  2. The complete delta set is now machine-readable and validated (rule 7, auditRollup). Deleting the sentence alone would only have hidden the claim: the index table still asserts completeness. It is now checked against the corpus — every page carrying a shipped badge must have a row, and every row must point at a page that carries one. Mutation-tested against the real corpus (repointing one row at an unbadged page) to confirm it fires in both directions rather than passing vacuously:

    README.md:38: "…/hooks/use-chat-scroll.md" badges a delta `shipped` but has no row in this table…
    README.md:42: this row says a delta on "…/hooks/use-chat.md" has landed, but that page badges nothing `shipped`.
    

Both sentences are now checkable statements about the rule rather than corpus-wide negatives.

Gates

deno task lint:rfc-status clean over 63 pages; scripts/lint/audit-rfc-status.test.ts 26 steps green (19 before, +7); audit-chat-composability.test.ts green alongside it; deno check and deno task fmt:check clean; deno task lint:ci green.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/rfcs/29-chat-api-shape/README.md (1)

32-43: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a neutral heading for the mixed-status roll-up.

The table contains both shipped and partly shipped rows, but the heading says What has landed - shipped. This makes partly shipped deltas appear fully shipped. Rename the heading to What has landed, and update the Line 10 fragment link if needed.

Proposed fix
-> Those five symbols resolve on the public surface today. That is **not** the same as a landed delta - see [reading the status block](`#reading-the-status-block`). For the deltas that have actually landed, see [what has landed](`#what-has-landed---shipped-srcreactcomponentschatchathooksuse-chat-inputts85`).
+> Those five symbols resolve on the public surface today. That is **not** the same as a landed delta - see [reading the status block](`#reading-the-status-block`). For the deltas that have actually landed, see [what has landed](`#what-has-landed`).

-### What has landed - `shipped` (src/react/components/chat/chat/hooks/use-chat-input.ts:85)
+### What has landed
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/rfcs/29-chat-api-shape/README.md` around lines 32 - 43, Rename the
mixed-status roll-up heading from “What has landed - shipped” to “What has
landed” so it accurately covers both shipped and partly shipped entries. Update
the corresponding fragment link in the mergeProps row if the heading change
alters its generated anchor.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/rfcs/29-chat-api-shape/README.md`:
- Around line 22-23: Update both references to the ChatInput export block in the
RFC to use the consistent src/chat/index.ts:250 anchor, and rename the “What has
landed - shipped” status heading so it accurately covers both shipped and partly
shipped entries.

In `@scripts/lint/audit-rfc-status.ts`:
- Around line 389-424: Update auditRollup and parseRollupRows to preserve and
report duplicate page rows instead of collapsing them through the listed Map,
while retaining each row’s line for violations. Broaden ROLLUP_ROW_RE parsing to
accept bare and ../ links, normalizing targets against RFC_DIR, or emit a
violation for shipped rows whose targets cannot be parsed. Add coverage in
audit-rfc-status.test.ts for duplicate rows and both non-./ link forms.

---

Outside diff comments:
In `@docs/rfcs/29-chat-api-shape/README.md`:
- Around line 32-43: Rename the mixed-status roll-up heading from “What has
landed - shipped” to “What has landed” so it accurately covers both shipped and
partly shipped entries. Update the corresponding fragment link in the mergeProps
row if the heading change alters its generated anchor.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: befb433f-fb7b-4e6c-972f-d729668592b4

📥 Commits

Reviewing files that changed from the base of the PR and between 2f9e57d and b66554a.

📒 Files selected for processing (16)
  • deno.json
  • docs/rfcs/29-chat-api-shape.md
  • docs/rfcs/29-chat-api-shape/README.md
  • docs/rfcs/29-chat-api-shape/components/app-shell.md
  • docs/rfcs/29-chat-api-shape/components/chat-agent-picker.md
  • docs/rfcs/29-chat-api-shape/components/chat-input.md
  • docs/rfcs/29-chat-api-shape/helpers.md
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-context.md
  • docs/rfcs/29-chat-api-shape/hooks/use-conversations-context.md
  • docs/rfcs/29-chat-api-shape/hooks/use-message-context.md
  • docs/rfcs/29-chat-api-shape/hooks/use-message-parts.md
  • docs/rfcs/29-chat-api-shape/hooks/use-sources.md
  • scripts/lint/audit-chat-composability.test.ts
  • scripts/lint/audit-chat-composability.ts
  • scripts/lint/audit-rfc-status.test.ts
  • scripts/lint/audit-rfc-status.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • docs/rfcs/29-chat-api-shape/hooks/use-conversations-context.md
  • docs/rfcs/29-chat-api-shape/hooks/use-message-parts.md
  • scripts/lint/audit-chat-composability.ts
  • docs/rfcs/29-chat-api-shape/hooks/use-message-context.md
  • docs/rfcs/29-chat-api-shape/components/app-shell.md
  • deno.json
  • docs/rfcs/29-chat-api-shape/hooks/use-chat-context.md
  • docs/rfcs/29-chat-api-shape/helpers.md
  • docs/rfcs/29-chat-api-shape.md
  • docs/rfcs/29-chat-api-shape/components/chat-agent-picker.md
  • docs/rfcs/29-chat-api-shape/components/chat-input.md

Comment thread docs/rfcs/29-chat-api-shape/README.md Outdated
Comment thread scripts/lint/audit-rfc-status.ts

@kwakayama kwakayama left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Validate the roll-up per delta, not merely per page

scripts/lint/audit-rfc-status.ts:392-399 reduces both sides of Rule 7 to a set of page paths. Consequently, any row for a page with one shipped heading is accepted, regardless of which delta the row names or how many shipped deltas the page has.

The current docs demonstrate the gap: [README.md:43-44] lists two distinct ChatInput deltas, but [components/chat-input.md:180] contains only the ChatInput.Field shipped badge—there is no shipped heading for “ChatInput flat sub-part exports.” The lint passes because both rows collapse to the same page path. This makes the claimed “complete set” at [README.md:34] untrustworthy.

Parse and compare row targets/anchors against individual badges (and reject duplicates), then add coverage for multiple badges on one page and a mismatched row. The existing roll-up tests at audit-rfc-status.test.ts:379-403 cover only one badge per page.

Scoring: correctness 31/40, tests 14/20, reliability/security 15/15, maintainability 12/15, scope/docs 8/10 = 80/100.

Review-Gate:
Reviewer: Codex
Reviewed-SHA: b66554a
Score: 80/100
Actionable-Findings: 1
Verdict: REQUEST_CHANGES

Rule 7 reduced both sides of the comparison to a set of page paths, so any
row for a page carrying one `shipped` badge was accepted regardless of which
delta the row named or how many deltas that page had actually landed. The
corpus already demonstrated the hole: the index listed two distinct ChatInput
deltas, `components/chat-input.md` badged only `ChatInput.Field`, and the lint
passed because both rows collapsed to the same page path. The table's "complete
set" claim was decorative - the same un-checkable corpus-wide assertion the
blanket banners made, which is what this rule exists to stop.

Rows are now paired with badges by the delta's own heading anchor. A badge with
no row, a row whose anchor names no badge, a row that links a page without
naming a delta, and two rows claiming the same delta are all violations.
`parseRollupRows` also stops requiring a `./` prefix: a row written
`[…](helpers.md)` or `[…](../29-chat-api-shape.md)` links the same document,
and dropping it from the parse excused the row rather than checking it.

Fixing the rule turned up the documentation gap it was blind to: "ChatInput
flat sub-part exports" had landed (`src/chat/index.ts:250`) and was described
in prose on the page, but carried no `shipped` badge, so it was never in the
machine-checkable set. The docs moved, not the rule - the delta is now badged.
The index's `src/chat/index.ts:253` anchor was also off by a declaration (253
is `type AttachmentPillProps`); it now cites 250 like the prose above it.
…ibes

README:22 asserted that the flat named exports had landed and linked only the
page. Now that the delta carries its own badge, point at it, so the prose and
the roll-up row cite the same heading.
@kojiwakayama

Copy link
Copy Markdown
Contributor Author

[P2] Validate the roll-up per delta — fixed, and the rule now bites

The finding was correct. Rule 7 was added in this PR to make the "complete set" claim machine-checkable, and as written it checked it at page granularity, so it did not check the claim it existed for.

Reproduced first

Three cases written as tests before touching the rule. All three passed under the old code — the audit returned zero violations for every one:

rejects a page whose second badged delta has no row of its own => AssertionError: - 0 / + 1
rejects a roll-up row whose anchor names no badge on the target page => AssertionError: - 0 / + 1
rejects two roll-up rows pointing at the same badge => AssertionError: - 0 / + 1
FAILED | 0 passed (26 steps) | 1 failed (3 steps)

The fix

Rows and badges are paired by the delta's own heading anchor, not by page path. ShippedBadge gains a slug (headingSlug(), GitHub's anchor rule); auditRollup keys on page#slug. Violations: a badge with no row, a row whose anchor matches no badge, a row linking a page without naming a delta, and two rows claiming the same delta. parseRollupRows also stops requiring a ./ prefix — bare and ../ targets are resolved against RFC_DIR instead of being silently dropped.

The docs moved, not the rule

Against the real corpus the strengthened rule produced 11 violations: six rows carried no anchor, and the two components/chat-input.md rows collapsed onto one key. The substantive one is exactly the gap identified — ChatInput flat sub-part exports had landed (src/chat/index.ts:250) and was described in prose on the page, but carried no shipped badge, so it was never in the machine-checkable set.

I badged the delta rather than weakening the rule. The corpus lints clean at 63 pages. The index's src/chat/index.ts:253 anchor was also off by a declaration (253 is type AttachmentPillProps) and now cites 250, matching the prose above it.

Mutation-tested

A rule reporting zero violations is indistinguishable from a no-op until it is shown to bite:

Mutation Result
Break one row anchor by one character caught (2 violations: orphan row + orphan badge)
Delete the new flat-sub-part-exports badge caught — pre-fix script reports every status ledger matches
Duplicate the useChatScroll row caught (a second row for …)
Rewrite a row link as bare (no ./) still parsed and checked, not dropped
Bare link plus broken anchor caught, and with an accurate message; the old rule caught it only by dropping the row, blaming a missing row that was present

Tree restored after each.

Gates

deno task lint:ci exit 0 · lint:rfc-status 63 pages clean · audit-rfc-status.test.ts 32 steps pass · fmt:check 4778 files clean · doc links 1344 OK.

Both outstanding CodeRabbit threads (README.md:23, audit-rfc-status.ts:424) are answered and resolved — the second raised the duplicate-row and link-form halves of this same defect.

@kojiwakayama

Copy link
Copy Markdown
Contributor Author

Re: Review-Gate REQUEST_CHANGES — [P2] Validate the roll-up per delta, not merely per page

That gate review is stale: its Reviewed-SHA is b66554afd5c3fb155abad8af03b2e52ebb0824cc, but the current head is b24ddf3c14a66e2a87695f958d184ee60fc2d73d. Two commits landed after it, and they address the finding directly. Evidence:

1. The rule now pairs per delta, not per page (91b8d0f9a, scripts/lint/audit-rfc-status.ts)

auditRollup no longer reduces either side to a set of page paths. Every shipped badge now carries its own GitHub heading anchor (ShippedBadge.slug, via the new exported headingSlug()), rows parse their #fragment into RollupRow.anchor, and the two are paired on page#slug. The four failure modes the gate asked for are each a distinct violation:

  • a badge with no row → "…badges the delta "#slug" \shipped` but it has no row in this table"`
  • a row whose anchor names no badge → "this row names no \shipped` delta…"`
  • a row that links a page without naming a delta → "this row links "…" without naming a delta"
  • two rows claiming the same delta → "a second row for "…" in this table"

ROLLUP_ROW_RE also stopped requiring a ./ prefix, and resolveRowTarget() normalises ../. segments — previously a row spelled […](helpers.md) fell out of the parse entirely, which excused the row rather than checking it.

2. The documentation gap the old rule was blind to is closed (91b8d0f9a, b24ddf3c1)

You were right that README.md listed two distinct ChatInput deltas while components/chat-input.md badged only ChatInput.Field. The second delta really had landed but was never in the machine-checkable set, so the docs moved: components/chat-input.md:20 now carries ### \ChatInput` flat sub-part exports - `new` - `shipped` (src/chat/index.ts:250)`, and all seven roll-up rows now link their delta's own heading anchor rather than the bare page.

The index's anchor for that row was also off by a declaration and is corrected: src/chat/index.ts:253 is type AttachmentPillProps, whereas :250 is the export { statement that actually ships the flat sub-parts (ChatInputField:260, ChatInputRoot:265, ChatInputToolbar:275). README.md:22 prose now cites the same heading as the row, so the two can't drift.

3. Coverage for exactly the cases you named (scripts/lint/audit-rfc-status.test.ts)

The gate noted the old tests at :379-403 covered only one badge per page. New cases:

  • rejects a page whose second badged delta has no row of its own — multiple badges on one page
  • rejects a roll-up row whose anchor names no badge on the target page — mismatched row
  • rejects two roll-up rows pointing at the same badge — duplicates
  • rejects a roll-up row that links a page without naming a delta
  • derives a delta's identity from its heading anchor
  • reads roll-up rows whose links are bare or \../`-relative`

Verification on current head (Deno 2.7.7):

deno test --config=scripts/test.deno.json scripts/lint/audit-rfc-status.test.ts
ok | 1 passed (32 steps) | 0 failed (12ms)

deno task lint:rfc-status
rfc status: 63 pages; every status ledger matches the real `veryfront/chat` surface (354 exports).

All CI checks are green. The one failure, NPM Dependency Audit, was infrastructure — Failed to download checksums manifest for v2.7.7 (HTTP 503) from setup-deno, no code involved — and passed on re-run.

Requesting a fresh gate pass against b24ddf3c.

@kojiwakayama

Copy link
Copy Markdown
Contributor Author

The P2 on rule 7 validating per page rather than per delta is addressed as of b24ddf3c, which post-dates the reviewed SHA b66554af.

Two commits:

  • 91b8d0f9aauditRollup now pairs roll-up rows with individual deltas instead of collapsing both sides to a set of page paths, and rejects duplicate rows pointing at the same badge.
  • b24ddf3c1 — the documentation gap the finding identified: components/chat-input.md carried only the ChatInput.Field badge while README.md:43-44 listed two distinct ChatInput deltas. The prose is now linked to the delta it describes.

Worth noting the second commit exists because the rule was tightened first and the real corpus then failed — which is the rule working. The docs were fixed to match reality rather than the rule being loosened to match the docs.

All checks green, no unresolved threads. Happy to revisit if the gate re-runs and disagrees.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/lint/audit-rfc-status.ts`:
- Around line 327-328: Update headingSlug and the document-order heading scan to
preserve Unicode characters and generate GitHub-compatible unique anchors by
appending -1, -2, etc. for repeated slugs instead of overwriting earlier entries
in the badge Map. Ensure completeness checks retain every heading’s badge, and
add tests covering duplicate headings and Unicode headings.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ca844c85-a279-4bf4-9aae-4bf69abb2214

📥 Commits

Reviewing files that changed from the base of the PR and between b66554a and 94692bf.

📒 Files selected for processing (5)
  • deno.json
  • docs/rfcs/29-chat-api-shape/README.md
  • docs/rfcs/29-chat-api-shape/components/chat-input.md
  • scripts/lint/audit-rfc-status.test.ts
  • scripts/lint/audit-rfc-status.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • deno.json
  • docs/rfcs/29-chat-api-shape/components/chat-input.md
  • scripts/lint/audit-rfc-status.test.ts
  • docs/rfcs/29-chat-api-shape/README.md

Comment thread scripts/lint/audit-rfc-status.ts Outdated
Rule 7 keyed each page's `shipped` badges by heading slug, minting one
slug per badge with an ASCII, collision-blind rule. Both halves were
wrong, and both shrank the set the rule claims to check exactly.

Colliding headings collapsed. The slug rule strips punctuation, so a
single comma can be the whole difference between two headings - this
corpus already carries four such pairs. `new Map(badges.map(...))` kept
only the last, so the earlier delta left the checkable set entirely and
its missing roll-up row was never reported, while the later heading's
real `-1` anchor matched no badge and was reported as broken.

Unicode was deleted. `\w` is ASCII-only in JavaScript, so `Café`
anchored as `caf` and a CJK heading slugged to bare punctuation - an
anchor no heading has. The row regex had the same ASCII class, so a
correctly-linked row fell out of the parse entirely.

Headings are now numbered in document order across every heading on the
page, badged or not, because that is what GitHub numbers; repeats take
its `-1`, `-2` suffix. The slug class is Unicode-aware, and code fences
are tracked (CommonMark marker rules) so a `#` line in a sample cannot
take a number GitHub never issued.

Verified against `github-slugger`, the slugger GitHub's renderer uses:
identical output on all 943 headings in this corpus and on Greek,
Cyrillic, CJK, emoji, and ligature cases.

Mutation-tested against the real corpus. Two colliding badges with one
row: the pre-fix script reported "every status ledger matches"; it now
names the `-1` delta as unrowed. The same page with the `-1` row GitHub
really renders, and a Unicode heading with its real anchor, each linted
clean where the pre-fix script raised a false violation. No page needed
changing - 63 pages still lint clean.
Numbering repeats with a per-base counter is not what GitHub does, and
the gap reintroduces the exact collapse the previous commit closed.

A suffixed slug can collide with a heading whose own text ends `-1`.
Given headings `foo`, `foo-1`, `foo`, GitHub issues `foo`, `foo-1`,
`foo-2`; a plain counter hands the third `foo-1` a second time, so two
deltas share one identity and one of them leaves the checkable set
again - one layer further in than where it was just fixed.

`parseShippedBadges` now runs `github-slugger`'s own loop: re-check the
candidate against every slug already issued, and keep spending the
base's counter until one is free.

Fuzzed against `github-slugger` rather than reasoned about. The plain
counter diverges on 139 of 405 collision cases; this loop on none, and
the real `parseShippedBadges` matches it across 3000 randomized
documents mixing collisions, literal `-1` headings, and Unicode. The
new test fails under the plain counter, expecting `-1` where GitHub
issues `-2`.
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit cb313d5 Aug 12, 2026
35 checks passed
@kojiwakayama
kojiwakayama deleted the rfc-status-pass branch August 12, 2026 20:09
@kojiwakayama kojiwakayama mentioned this pull request Aug 12, 2026
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.

2 participants