Skip to content

feat: overhaul AI devtools hook dashboard#632

Open
AlemTuzlak wants to merge 9 commits into
mainfrom
feat/ai-devtools-overhaul
Open

feat: overhaul AI devtools hook dashboard#632
AlemTuzlak wants to merge 9 commits into
mainfrom
feat/ai-devtools-overhaul

Conversation

@AlemTuzlak
Copy link
Copy Markdown
Contributor

@AlemTuzlak AlemTuzlak commented May 24, 2026

Summary

  • add a hook-first AI devtools dashboard with active hook lifecycle tracking, categories, unread activity, run linking, fixture-aware tool replay, and generation previews
  • expand chat inspection with grouped runs/events, structured-output raw/partial rendering, tool approvals, token/usage surfaces, and synced conversation/user-view highlighting
  • wire devtools metadata through chat and generation hooks across framework adapters, plus React example coverage for generation hooks and structured-output useChat

Test Plan

  • pnpm format
  • pnpm test
  • Manual: verified /generations/structured-output registers useChat as a Structured hook in TanStack AI devtools

Summary by CodeRabbit

  • New Features

    • Hook Dashboard in devtools for monitoring AI generation hooks with state snapshots and turn-based timelines
    • Tool Fixture Replay feature for testing with schema-driven fixture forms
    • Generation Hooks example page demonstrating multiple AI capabilities (image, audio, speech, transcription, summarization, video)
  • Documentation

    • Expanded devtools guide with Hook Dashboard, Tool Fixtures, and Event Sources sections

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

ChatClient now integrates a devtools bridge to track runs, emit snapshots, and observe stream chunks. The structured-output API refactors to use centralized request parsing and phase-count emission. A new /generation-hooks demo page showcases six AI generation hooks with fixture-based streaming. Documentation updates describe hook dashboard, tool fixtures, and event sources.

Changes

ChatClient Devtools Integration and Hook Dashboard Demo

Layer / File(s) Summary
ChatClient devtools bridge integration and lifecycle
packages/ai-client/src/chat-client.ts
ChatClient constructor creates a ChatDevtoolsBridge from client/thread IDs. Public entry points (sendMessage, append, reload, addToolResult, etc.) mount devtools as needed and emit snapshots when state changes. During streaming, the client tracks the active devtools run ID, begins/ends runs with lifecycle events (run:created/started/errored/cancelled/completed), observes each chunk via the bridge, and clears devtools state in finally blocks. Tool execution captures run context for later tool-result attribution. dispose() tears down the devtools bridge.
Structured-output API streaming with phase counts and SSE
examples/ts-react-chat/src/routes/api.structured-output.ts
POST handler parses input via chatParamsFromRequestBody and resolves provider from params.forwardedProps using an isProvider type guard. withTrailingPhaseCounts now emits phase-counts CUSTOM event when RUN_FINISHED/RUN_ERROR is observed. Non-streaming path creates SSE stream from structuredOutputResultStream async generator (emitting structured-output events + phase-counts + RUN_FINISHED). Adds exported GuitarRecommendation type inferred from schema.
Structured-output page refactored to useChat hook
examples/ts-react-chat/src/routes/generations.structured-output.tsx
Replaces manual fetch + SSE parsing with useChat hook. Centralizes handleChunk to route TEXT_MESSAGE_CONTENT/REASONING_MESSAGE_CONTENT/phase-counts/structured-output.complete events, tracks completion via sawCompleteRef to verify stream validity, and derives isLoading from chat.isLoading. Generation/abort handlers use chat.clear()/chat.sendMessage()/chat.stop().
Generation hooks demo page with fixture streaming
examples/ts-react-chat/src/routes/generation-hooks.tsx
New /generation-hooks page demonstrates useGenerateImage, useGenerateAudio, useGenerateSpeech, useTranscription, useSummarize, useGenerateVideo with simulated fixture connections. Fixture adapters (createGenerationConnection, createVideoConnection) yield ordered devtools stream events (run started → progress → result → run finished). HookCard components render generated media (images, audio/speech players, transcription, summary, video). Utilities generate tone WAV base64, SVG data URLs, and XML escaping for safe embedding.
Example app routing and header navigation
examples/ts-react-chat/src/components/Header.tsx, examples/ts-react-chat/src/routeTree.gen.ts
Header adds "Generation Hooks" sidebar link with Activity icon routing to /generation-hooks. routeTree.gen.ts registers GenerationHooksRoute with route type mappings (FileRoutesByFullPath/FileRoutesByTo/FileRoutesById) and module augmentation for @tanstack/react-router.
Package exports and documentation
packages/ai-client/package.json, docs/getting-started/devtools.md, .changeset/ai-devtools-hook-dashboard.md
ai-client package.json adds ./devtools subpath export. Devtools docs introduce Hook Dashboard (hook discovery, turn-based timeline, state snapshots, devtools.name), Tool Fixtures (schema-driven forms, fixture replay), and Event Sources (client/server event separation). Changeset records version bumps and hook-aware devtools registration features.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • crutchcorn

🐰 A hook-aware devtools bridge hops into place,
Streaming phases counted in their rightful space,
Six generation hooks dance with fixture light,
Route and docs aligned—the dashboard shines bright!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.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
Title check ✅ Passed The title 'feat: overhaul AI devtools hook dashboard' directly aligns with the PR's main objective of adding a hook-first AI devtools dashboard with lifecycle tracking, categories, and related features.
Description check ✅ Passed The PR description provides a comprehensive summary of changes and includes a test plan with specific commands and manual verification steps, addressing the key requirements of the repository template.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ai-devtools-overhaul

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 and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 24, 2026

🚀 Changeset Version Preview

8 package(s) bumped directly, 15 bumped as dependents.

🟥 Major bumps

Package Version Reason
@tanstack/ai-elevenlabs 0.2.12 → 1.0.0 Dependent
@tanstack/ai-openai 0.10.2 → 1.0.0 Dependent
@tanstack/ai-react-ui 0.8.2 → 1.0.0 Dependent
@tanstack/ai-solid-ui 0.7.2 → 1.0.0 Dependent

🟨 Minor bumps

Package Version Reason
@tanstack/ai-client 0.12.0 → 0.13.0 Changeset
@tanstack/ai-devtools-core 0.3.38 → 0.4.0 Changeset
@tanstack/ai-event-client 0.3.11 → 0.4.0 Changeset

🟩 Patch bumps

Package Version Reason
@tanstack/ai-preact 0.6.34 → 0.6.35 Changeset
@tanstack/ai-react 0.12.0 → 0.12.1 Changeset
@tanstack/ai-solid 0.10.9 → 0.10.10 Changeset
@tanstack/ai-svelte 0.10.9 → 0.10.10 Changeset
@tanstack/ai-vue 0.10.10 → 0.10.11 Changeset
@tanstack/ai 0.22.0 → 0.22.1 Dependent
@tanstack/ai-code-mode 0.1.21 → 0.1.22 Dependent
@tanstack/ai-code-mode-skills 0.1.21 → 0.1.22 Dependent
@tanstack/ai-fal 0.7.14 → 0.7.15 Dependent
@tanstack/ai-isolate-cloudflare 0.2.12 → 0.2.13 Dependent
@tanstack/ai-isolate-node 0.1.21 → 0.1.22 Dependent
@tanstack/ai-isolate-quickjs 0.1.21 → 0.1.22 Dependent
@tanstack/ai-vue-ui 0.2.5 → 0.2.6 Dependent
@tanstack/preact-ai-devtools 0.1.42 → 0.1.43 Dependent
@tanstack/react-ai-devtools 0.2.42 → 0.2.43 Dependent
@tanstack/solid-ai-devtools 0.2.42 → 0.2.43 Dependent

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 24, 2026

View your CI Pipeline Execution ↗ for commit 5685614

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 3m 33s View ↗
nx run-many --targets=build --exclude=examples/... ✅ Succeeded 48s View ↗

☁️ Nx Cloud last updated this comment at 2026-05-26 13:29:07 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 24, 2026

Open in StackBlitz

@tanstack/ai

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

@tanstack/ai-anthropic

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

@tanstack/ai-client

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

@tanstack/ai-code-mode

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

@tanstack/ai-code-mode-skills

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

@tanstack/ai-devtools-core

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

@tanstack/ai-elevenlabs

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

@tanstack/ai-event-client

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

@tanstack/ai-fal

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

@tanstack/ai-gemini

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

@tanstack/ai-grok

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

@tanstack/ai-groq

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

@tanstack/ai-isolate-cloudflare

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

@tanstack/ai-isolate-node

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

@tanstack/ai-isolate-quickjs

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

@tanstack/ai-ollama

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

@tanstack/ai-openai

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

@tanstack/ai-openrouter

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

@tanstack/ai-preact

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

@tanstack/ai-react

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

@tanstack/ai-react-ui

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

@tanstack/ai-solid

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

@tanstack/ai-solid-ui

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

@tanstack/ai-svelte

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

@tanstack/ai-utils

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

@tanstack/ai-vue

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

@tanstack/ai-vue-ui

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

@tanstack/openai-base

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

@tanstack/preact-ai-devtools

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

@tanstack/react-ai-devtools

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

@tanstack/solid-ai-devtools

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

commit: 5685614

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
packages/typescript/ai-event-client/src/envelope.ts (1)

72-102: 💤 Low value

Verify intentional use of module-level runtimeId at line 93.

Line 82 uses resolvedRuntimeId (which respects input.runtimeId), but line 93 uses the module-level runtimeId directly. If this is intentional (to ensure local uniqueness via the module's own runtime ID + counter), consider adding a brief comment explaining the distinction. Otherwise, line 93 should use resolvedRuntimeId for consistency.

🤖 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 `@packages/typescript/ai-event-client/src/envelope.ts` around lines 72 - 102,
The eventId construction in createAIDevtoolsEventEnvelope mixes
resolvedRuntimeId (which respects input.runtimeId) and the module-level
runtimeId directly; decide and make it consistent: either replace the literal
runtimeId in the eventId parts with resolvedRuntimeId so the generated eventId
uses the resolved value throughout, or, if the intent is to combine the resolved
runtimeId with the module-level runtimeId for local uniqueness, add a short
comment above the array explaining why runtimeId (module-level) is used
intentionally; update the array used to build eventId accordingly (references:
createAIDevtoolsEventEnvelope, resolvedRuntimeId, runtimeId, eventId).
packages/typescript/ai-client/src/chat-client.ts (1)

508-516: 💤 Low value

Remove underscore prefix from used parameter.

The _fixture parameter naming convention suggests an unused variable, but it's immediately assigned to fixture and used throughout the method. Consider renaming to just fixture directly in the parameter.

Suggested fix
   private async applyToolFixture(
-    _fixture: AIDevtoolsToolFixture,
+    fixture: AIDevtoolsToolFixture,
   ): Promise<void> {
-    const fixture = _fixture
     const messages = this.processor.getMessages() as Array<UIMessage>
🤖 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 `@packages/typescript/ai-client/src/chat-client.ts` around lines 508 - 516, In
applyToolFixture, the parameter is named _fixture (suggesting unused) but
immediately assigned to fixture and used; rename the parameter from _fixture to
fixture and remove the redundant const fixture = _fixture assignment, then
update usages in the function (e.g., threadId = fixture.threadId ??
this.threadId and the call to this.executeToolFixture(fixture, messages,
threadId)) so the method uses the parameter directly.
packages/typescript/ai-devtools/tests/preview-model.test.ts (1)

54-96: ⚡ Quick win

Consider testing with actual DOM elements using happy-dom.

The test currently uses plain objects to simulate element.dataset, which effectively tests the matching logic. However, if hoverTargetMatchesElement is designed to work with real DOM elements in production, consider adding integration tests with actual elements using happy-dom to verify the complete flow.

As per coding guidelines, Vitest unit tests should use happy-dom for DOM testing.

🧪 Example integration test with happy-dom
+import { beforeEach } from 'vitest'
+
+describe('preview model with DOM', () => {
+  beforeEach(() => {
+    document.body.innerHTML = ''
+  })
+
+  it('matches hover targets against real DOM elements', () => {
+    const target = createHoverTarget({
+      messageIds: ['message-1'],
+      partIds: [toolCallPartId('call-1')],
+      origin: 'preview',
+    })
+
+    const element = document.createElement('div')
+    element.dataset.aiDevtoolsHoverMessageIds = 'message-1'
+    element.dataset.aiDevtoolsHoverPartIds = 'tool-call:call-1'
+
+    expect(hoverTargetMatchesElement(target, element.dataset)).toBe(true)
+  })
+})
🤖 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 `@packages/typescript/ai-devtools/tests/preview-model.test.ts` around lines 54
- 96, Test currently passes plain objects as element.dataset; replace/add an
integration variant that uses a real DOM element created via happy-dom and
exercise the same flows: create an element via the happy-dom document (e.g.,
document.createElement('div')), set dataset attributes or setAttribute for the
same keys produced by getHoverDataAttributes, then assert
hoverTargetMatchesElement(element, ...) and
createHoverTargetFromDataAttributes(element.dataset) behave identically to the
plain-object expectations; reference the existing helpers createHoverTarget,
getHoverDataAttributes, hoverTargetMatchesElement, and
createHoverTargetFromDataAttributes to locate where to plug the DOM-backed
assertions and ensure the test file uses happy-dom as the Vitest DOM
environment.
🤖 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 `@examples/ts-react-chat/src/routes/generations.structured-output.tsx`:
- Around line 223-233: In handleGenerate the guard that reports a missing
structured-output.complete is inverted; change the final condition from checking
chat.status !== 'ready' to chat.status === 'ready' so that when stream is true
and sawCompleteRef.current is false and chat.status === 'ready' you call
setError('Stream ended before structured-output.complete'); update the check
that references sawCompleteRef, chat.status and stream in the handleGenerate
function accordingly.

In `@packages/typescript/ai-devtools/src/components/hooks/HookDetails.tsx`:
- Around line 785-831: The part rows rendered in HookDetails (inside the For
over visiblePreviewPartsForMessage(message)) are missing
data-ai-devtools-hover-* attributes, so setHoverTargetFromEvent cannot resolve
part-level hover targets; update the outer div for each part (the element with
class styles().hookDetails.previewPart) to include data attributes such as
data-ai-devtools-hover-type="part",
data-ai-devtools-hover-message-id={message.id} (or message._id), and a unique
part identifier like data-ai-devtools-hover-part={part.key || part.label ||
index} so setHoverTargetFromEvent can detect and distinguish part-level targets
and enable part-level sync/scroll to preview.

In `@packages/typescript/ai-devtools/src/components/hooks/ToolFixtureForm.tsx`:
- Around line 275-283: In parseFieldValue, stop silently truncating integer
inputs: when field.type === 'integer' validate that the parsed Number(rawValue)
is an integer (use Number.isInteger) and throw a descriptive Error like "<name>
must be an integer" if it's not; otherwise return the integer value — replace
the current Math.trunc-based behavior in the parseFieldValue function.

In `@packages/typescript/ai-devtools/src/components/Shell.tsx`:
- Around line 89-99: onCleanup currently removes document event listeners and
emits the devtools:closed event but can leave document.body in drag mode if
handleMouseUp didn't run; update the onCleanup block to also reset global
drag-related styles by setting document.body.style.cursor = '' and
document.body.style.userSelect = '' (or revert to their prior values) so any
drag state set by handleMouseMove/handleMouseUp is cleared; locate the onCleanup
call in Shell.tsx and add these two style resets alongside the existing
document.removeEventListener and
aiEventClient.emit/createAIDevtoolsEventEnvelope logic.

In `@packages/typescript/ai-devtools/src/store/ai-context.tsx`:
- Around line 1341-1348: The normalization for message parts currently discards
the tool-result's non-text output by not including part.output in the returned
object; update the branch that handles part.type === 'tool-result' (the object
constructed with type: 'tool-result', toolCallId, content, state, error) to also
preserve output by adding output: part.output so downstream previews and store
state retain non-text tool results.

In `@packages/typescript/ai-preact/src/use-chat.ts`:
- Around line 160-170: The cleanup effect currently depends on options.live
causing client.dispose() (and client.stop()/client.unsubscribe()) to run when
live toggles; change the effect so it only runs on unmount or when the client
instance changes by removing options.live from the dependency array (i.e. depend
only on client) so that client.mountDevtools() is mounted once per client and
client.dispose() is invoked only on unmount/client replacement; leave the
dedicated subscribe/unsubscribe effect to handle options.live toggling.

In `@packages/typescript/ai-react/src/use-chat.ts`:
- Around line 157-168: The devtools mounting effect currently depends on
options.live and its cleanup always calls client.dispose(), causing the client
to be torn down whenever live toggles; change the effect to only depend on
client (remove options.live from the dependency array) and reference
optionsRef.current.live inside the cleanup to decide between
client.unsubscribe() and client.stop() so the separate live-toggle effect
continues to manage subscribe/unsubscribe; specifically update the useEffect
around client.mountDevtools to use optionsRef.current.live instead of
options.live and remove options.live from the dependency list so
client.dispose() is not invoked on live changes.

---

Nitpick comments:
In `@packages/typescript/ai-client/src/chat-client.ts`:
- Around line 508-516: In applyToolFixture, the parameter is named _fixture
(suggesting unused) but immediately assigned to fixture and used; rename the
parameter from _fixture to fixture and remove the redundant const fixture =
_fixture assignment, then update usages in the function (e.g., threadId =
fixture.threadId ?? this.threadId and the call to
this.executeToolFixture(fixture, messages, threadId)) so the method uses the
parameter directly.

In `@packages/typescript/ai-devtools/tests/preview-model.test.ts`:
- Around line 54-96: Test currently passes plain objects as element.dataset;
replace/add an integration variant that uses a real DOM element created via
happy-dom and exercise the same flows: create an element via the happy-dom
document (e.g., document.createElement('div')), set dataset attributes or
setAttribute for the same keys produced by getHoverDataAttributes, then assert
hoverTargetMatchesElement(element, ...) and
createHoverTargetFromDataAttributes(element.dataset) behave identically to the
plain-object expectations; reference the existing helpers createHoverTarget,
getHoverDataAttributes, hoverTargetMatchesElement, and
createHoverTargetFromDataAttributes to locate where to plug the DOM-backed
assertions and ensure the test file uses happy-dom as the Vitest DOM
environment.

In `@packages/typescript/ai-event-client/src/envelope.ts`:
- Around line 72-102: The eventId construction in createAIDevtoolsEventEnvelope
mixes resolvedRuntimeId (which respects input.runtimeId) and the module-level
runtimeId directly; decide and make it consistent: either replace the literal
runtimeId in the eventId parts with resolvedRuntimeId so the generated eventId
uses the resolved value throughout, or, if the intent is to combine the resolved
runtimeId with the module-level runtimeId for local uniqueness, add a short
comment above the array explaining why runtimeId (module-level) is used
intentionally; update the array used to build eventId accordingly (references:
createAIDevtoolsEventEnvelope, resolvedRuntimeId, runtimeId, eventId).
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd30edd5-da12-4872-a9c7-da8b3aa8d16e

📥 Commits

Reviewing files that changed from the base of the PR and between 6176067 and 4b0f6ce.

📒 Files selected for processing (101)
  • .changeset/ai-devtools-hook-dashboard.md
  • docs/getting-started/devtools.md
  • examples/ts-react-chat/src/components/Header.tsx
  • examples/ts-react-chat/src/routeTree.gen.ts
  • examples/ts-react-chat/src/routes/api.structured-output.ts
  • examples/ts-react-chat/src/routes/generation-hooks.tsx
  • examples/ts-react-chat/src/routes/generations.structured-output.tsx
  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai-client/src/devtools.ts
  • packages/typescript/ai-client/src/events.ts
  • packages/typescript/ai-client/src/generation-client.ts
  • packages/typescript/ai-client/src/generation-types.ts
  • packages/typescript/ai-client/src/index.ts
  • packages/typescript/ai-client/src/types.ts
  • packages/typescript/ai-client/src/video-generation-client.ts
  • packages/typescript/ai-client/tests/devtools.test.ts
  • packages/typescript/ai-client/tests/events.test.ts
  • packages/typescript/ai-client/tests/generation-client.test.ts
  • packages/typescript/ai-client/tests/generation-devtools.test.ts
  • packages/typescript/ai-client/tests/video-generation-client.test.ts
  • packages/typescript/ai-devtools/src/components/ConversationDetails.tsx
  • packages/typescript/ai-devtools/src/components/ConversationsList.tsx
  • packages/typescript/ai-devtools/src/components/Shell.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ActivityEventsTab.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ChunkBadges.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ChunkItem.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ChunksCollapsible.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ChunksTab.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ConversationHeader.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ConversationTabs.tsx
  • packages/typescript/ai-devtools/src/components/conversation/IterationCard.tsx
  • packages/typescript/ai-devtools/src/components/conversation/IterationTimeline.tsx
  • packages/typescript/ai-devtools/src/components/conversation/MessageCard.tsx
  • packages/typescript/ai-devtools/src/components/conversation/MessageGroup.tsx
  • packages/typescript/ai-devtools/src/components/conversation/MessagesTab.tsx
  • packages/typescript/ai-devtools/src/components/conversation/SummariesTab.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ToolCallDisplay.tsx
  • packages/typescript/ai-devtools/src/components/conversation/index.ts
  • packages/typescript/ai-devtools/src/components/hooks/GenerationPanel.tsx
  • packages/typescript/ai-devtools/src/components/hooks/HookDashboard.tsx
  • packages/typescript/ai-devtools/src/components/hooks/HookDetails.tsx
  • packages/typescript/ai-devtools/src/components/hooks/ToolFixtureForm.tsx
  • packages/typescript/ai-devtools/src/components/hooks/hook-dashboard-model.ts
  • packages/typescript/ai-devtools/src/components/hooks/index.ts
  • packages/typescript/ai-devtools/src/components/hooks/preview-messages.ts
  • packages/typescript/ai-devtools/src/components/hooks/preview-model.ts
  • packages/typescript/ai-devtools/src/components/list/ConversationRow.tsx
  • packages/typescript/ai-devtools/src/components/list/index.ts
  • packages/typescript/ai-devtools/src/components/utils/format.ts
  • packages/typescript/ai-devtools/src/store/ai-context.tsx
  • packages/typescript/ai-devtools/src/store/hook-registry.ts
  • packages/typescript/ai-devtools/src/store/message-event-utils.ts
  • packages/typescript/ai-devtools/src/styles/use-styles.ts
  • packages/typescript/ai-devtools/tests/hook-dashboard-model.test.ts
  • packages/typescript/ai-devtools/tests/hook-registry.test.ts
  • packages/typescript/ai-devtools/tests/message-event-utils.test.ts
  • packages/typescript/ai-devtools/tests/preview-messages.test.ts
  • packages/typescript/ai-devtools/tests/preview-model.test.ts
  • packages/typescript/ai-event-client/src/devtools-middleware.ts
  • packages/typescript/ai-event-client/src/envelope.ts
  • packages/typescript/ai-event-client/src/index.ts
  • packages/typescript/ai-event-client/tests/emit.test.ts
  • packages/typescript/ai-event-client/tests/envelope.test.ts
  • packages/typescript/ai-preact/src/types.ts
  • packages/typescript/ai-preact/src/use-chat.ts
  • packages/typescript/ai-react/src/use-chat.ts
  • packages/typescript/ai-react/src/use-generate-audio.ts
  • packages/typescript/ai-react/src/use-generate-image.ts
  • packages/typescript/ai-react/src/use-generate-speech.ts
  • packages/typescript/ai-react/src/use-generate-video.ts
  • packages/typescript/ai-react/src/use-generation.ts
  • packages/typescript/ai-react/src/use-summarize.ts
  • packages/typescript/ai-react/src/use-transcription.ts
  • packages/typescript/ai-solid/src/use-chat.ts
  • packages/typescript/ai-solid/src/use-generate-audio.ts
  • packages/typescript/ai-solid/src/use-generate-image.ts
  • packages/typescript/ai-solid/src/use-generate-speech.ts
  • packages/typescript/ai-solid/src/use-generate-video.ts
  • packages/typescript/ai-solid/src/use-generation.ts
  • packages/typescript/ai-solid/src/use-summarize.ts
  • packages/typescript/ai-solid/src/use-transcription.ts
  • packages/typescript/ai-svelte/src/create-chat.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-audio.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-image.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-speech.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-video.svelte.ts
  • packages/typescript/ai-svelte/src/create-generation.svelte.ts
  • packages/typescript/ai-svelte/src/create-summarize.svelte.ts
  • packages/typescript/ai-svelte/src/create-transcription.svelte.ts
  • packages/typescript/ai-svelte/src/types.ts
  • packages/typescript/ai-vue/src/use-chat.ts
  • packages/typescript/ai-vue/src/use-generate-audio.ts
  • packages/typescript/ai-vue/src/use-generate-image.ts
  • packages/typescript/ai-vue/src/use-generate-speech.ts
  • packages/typescript/ai-vue/src/use-generate-video.ts
  • packages/typescript/ai-vue/src/use-generation.ts
  • packages/typescript/ai-vue/src/use-summarize.ts
  • packages/typescript/ai-vue/src/use-transcription.ts
  • packages/typescript/ai/src/activities/chat/index.ts
  • packages/typescript/ai/src/activities/chat/middleware/types.ts
  • packages/typescript/ai/src/activities/chat/stream/processor.ts
💤 Files with no reviewable changes (18)
  • packages/typescript/ai-devtools/src/components/conversation/ToolCallDisplay.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ChunksTab.tsx
  • packages/typescript/ai-devtools/src/components/list/ConversationRow.tsx
  • packages/typescript/ai-devtools/src/components/ConversationsList.tsx
  • packages/typescript/ai-devtools/src/components/conversation/SummariesTab.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ChunkBadges.tsx
  • packages/typescript/ai-devtools/src/components/list/index.ts
  • packages/typescript/ai-devtools/src/components/conversation/ConversationTabs.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ActivityEventsTab.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ConversationHeader.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ChunkItem.tsx
  • packages/typescript/ai-devtools/src/components/ConversationDetails.tsx
  • packages/typescript/ai-devtools/src/components/conversation/index.ts
  • packages/typescript/ai-devtools/src/components/conversation/MessageCard.tsx
  • packages/typescript/ai-devtools/src/components/utils/format.ts
  • packages/typescript/ai-devtools/src/components/conversation/MessagesTab.tsx
  • packages/typescript/ai-devtools/src/components/conversation/MessageGroup.tsx
  • packages/typescript/ai-devtools/src/components/conversation/ChunksCollapsible.tsx

Comment thread examples/ts-react-chat/src/routes/generations.structured-output.tsx
Comment thread packages/ai-devtools/src/components/hooks/HookDetails.tsx
Comment thread packages/ai-devtools/src/components/hooks/ToolFixtureForm.tsx
Comment thread packages/ai-devtools/src/components/Shell.tsx
Comment on lines +1341 to +1348
if (part.type === 'tool-result') {
return {
type: 'tool-result',
toolCallId: part.toolCallId,
content: part.content,
state: part.state,
error: part.error,
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve tool-result output when normalizing message parts.

Line 1341-1348 drops part.output, which loses non-text tool results in store state and downstream previews.

Suggested patch
               if (part.type === 'tool-result') {
                 return {
                   type: 'tool-result',
                   toolCallId: part.toolCallId,
+                  output: part.output,
                   content: part.content,
                   state: part.state,
                   error: part.error,
                 }
               }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (part.type === 'tool-result') {
return {
type: 'tool-result',
toolCallId: part.toolCallId,
content: part.content,
state: part.state,
error: part.error,
}
if (part.type === 'tool-result') {
return {
type: 'tool-result',
toolCallId: part.toolCallId,
output: part.output,
content: part.content,
state: part.state,
error: part.error,
}
🤖 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 `@packages/typescript/ai-devtools/src/store/ai-context.tsx` around lines 1341 -
1348, The normalization for message parts currently discards the tool-result's
non-text output by not including part.output in the returned object; update the
branch that handles part.type === 'tool-result' (the object constructed with
type: 'tool-result', toolCallId, content, state, error) to also preserve output
by adding output: part.output so downstream previews and store state retain
non-text tool results.

Comment thread packages/typescript/ai-preact/src/use-chat.ts Outdated
Comment thread packages/typescript/ai-react/src/use-chat.ts Outdated
AlemTuzlak and others added 3 commits May 25, 2026 19:47
- Extract bridge construction into buildDevtoolsBridgeOptions helpers on
  ChatClient, GenerationClient, VideoGenerationClient
- Add ChatDevtoolsAwareEventEmitter that auto-attaches run context and
  emits snapshots so clients call this.events.X(...) like normal events
- Ship NoOp bridges by default; tree-shake real bridges into the
  @tanstack/ai-client/devtools subpath entry
- Update React, Vue, Solid, Svelte, Preact hooks to pass the real
  bridge factory
- Expand E2E suite: registration, chat, structured output, tools,
  generation hooks, responsive layout
- Add devtools-name-types tests per framework integration
…haul

# Conflicts:
#	examples/ts-react-chat/src/routes/api.structured-output.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
packages/typescript/ai-client/src/generation-client.ts (1)

247-261: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use chunkRunId when starting the run for CUSTOM chunks
processStream extracts chunkRunId, but the CUSTOM branch ignores it (ensureRunStarted(streamRunId ?? fallbackRunId)). If a CUSTOM chunk carries a server runId before RUN_STARTED, the devtools bridge will start the fallback run first and then create a second run when RUN_STARTED arrives (it can only rename before the first run:started). Prefer chunkRunId ?? streamRunId ?? fallbackRunId and keep streamRunId in sync.

Suggested fix
         case 'CUSTOM': {
-          this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId)
+          const activeRunId = chunkRunId ?? streamRunId ?? fallbackRunId
+          streamRunId = activeRunId
+          this.devtoolsBridge.ensureRunStarted(activeRunId)
           if (chunk.name === GENERATION_EVENTS.RESULT) {
             this.setResult(chunk.value as TResult)
           } else if (chunk.name === GENERATION_EVENTS.PROGRESS) {
🤖 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 `@packages/typescript/ai-client/src/generation-client.ts` around lines 247 -
261, The CUSTOM branch currently calls
this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId) but ignores
the extracted chunkRunId; change the logic to prefer chunkRunId by calling
ensureRunStarted(chunkRunId ?? streamRunId ?? fallbackRunId) and, when
chunkRunId is present, set streamRunId = chunkRunId so streamRunId stays in
sync; update the CUSTOM case in processStream (the switch handling chunk.type,
around the GENERATION_EVENTS.RESULT handling) to use those symbols (chunkRunId,
streamRunId, fallbackRunId) and call ensureRunStarted with the combined
fallback.
🧹 Nitpick comments (2)
packages/typescript/ai-devtools/src/store/hook-registry.ts (1)

640-695: 💤 Low value

Consider handling duplicate run IDs from multiple hooks.

syncRunsFromSnapshot unconditionally overwrites existing.hookId when updating an existing run record (line 680). If two hooks share the same run ID (unlikely but possible in edge cases), the last snapshot processed wins, potentially causing incorrect hook→run associations.

This is a minor edge case given the current architecture, but worth noting.

🤖 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 `@packages/typescript/ai-devtools/src/store/hook-registry.ts` around lines 640
- 695, syncRunsFromSnapshot currently unconditionally overwrites existing.hookId
which can misassociate runs if the same run ID appears for multiple hooks;
change the update logic in syncRunsFromSnapshot so it does not overwrite
existing.hookId when an existing run already has a different hookId (e.g., only
set existing.hookId = snapshot.hookId if !existing.hookId or existing.hookId ===
snapshot.hookId), and rely on attachRunToHook(state, snapshot.hookId, run.id) to
record the hook→run relationship (or extend the run model to track multiple
hookIds if you need multi-association); update any consumers expecting a single
hookId accordingly.
packages/typescript/ai-devtools/src/components/hooks/HookDetails.tsx (1)

1226-1254: 💤 Low value

Animation cleanup on unmount is not handled.

The scrollAnimations WeakMap (line 51) stores setTimeout handles, but there's no cleanup when the component unmounts. If HookDetails unmounts while an animation is in progress, the timeout callback will still fire and attempt to modify element.scrollTop on a potentially detached element. While this won't crash (it's a no-op on detached elements), it's a minor resource leak.

Consider using onCleanup from Solid to clear any active animation timeouts when the component unmounts.

🤖 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 `@packages/typescript/ai-devtools/src/components/hooks/HookDetails.tsx` around
lines 1226 - 1254, animateScrollTop currently stores timeouts in the
module-scoped scrollAnimations WeakMap but provides no way for HookDetails to
cancel them on unmount; change animateScrollTop to return the active timeout id
(or a cancel function) when starting the animation and/or convert
scrollAnimations to an iterable Map so pending timeouts can be enumerated, then
import onCleanup in the HookDetails component and register a cleanup that clears
any outstanding timeouts (window.clearTimeout) for that component (use the
returned ids or iterate the Map entries keyed by the element) to ensure timeouts
are cleared when HookDetails unmounts.
🤖 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 `@packages/typescript/ai-client/src/devtools-noop.ts`:
- Around line 60-107: NoOpChatDevtoolsBridge is missing the public methods
mountWithTools, notifyToolsChanged, and recordStreamId from the
ChatDevtoolsBridge contract, causing runtime crashes; update the class to add
matching method signatures (mountWithTools(...), notifyToolsChanged(...),
recordStreamId(streamId: string | null): void) with no-op implementations and
correct parameter types so the no-op bridge fully implements the
ChatDevtoolsBridge surface (match the signatures defined in ChatDevtoolsBridge
in devtools.ts).

In `@packages/typescript/ai-client/src/devtools.ts`:
- Around line 1348-1368: ensureRunStarted currently skips renaming when
activeRunStarted is true, causing a stale provisional run to remain; fix by
moving the rename logic before the early-return and always calling
this.renameRun(this.activeRunId, runId) when this.activeRunId exists and !==
runId (regardless of activeRunStarted). Keep the early return if
this.activeRunStarted && this.activeRunId === runId, then proceed to set
this.activeRunId = runId, this.activeRunStarted = true, upsertRun(...
'generating' ...), and emitRunLifecycle/emitState so the renamed entry retains
the started lifecycle; reference ensureRunStarted, activeRunId,
activeRunStarted, and renameRun.

In `@packages/typescript/ai-devtools/src/components/hooks/GenerationPanel.tsx`:
- Around line 108-115: The auto-scroll createEffect is setting scrollTop on the
non-scrollable runsContainer (the generationRuns div) so it never scrolls;
either change the ref to target the actual overflow element (the outer pane
container used to hold generation runs—e.g., introduce runsScrollContainer and
attach the ref to that DOM node used for scrolling) or make the generationRuns
element itself the scroll container by adding overflow:auto/overflow-y:auto and
a max-height in the styles (use-styles.ts) so
runsContainer.scrollTop/scrollHeight in createEffect (and the identical logic at
lines 129-134) operate on the real scrollable element. Ensure the referenced
symbol names (runsContainer/generationRuns or the new runsScrollContainer) and
createEffect are updated accordingly.

---

Outside diff comments:
In `@packages/typescript/ai-client/src/generation-client.ts`:
- Around line 247-261: The CUSTOM branch currently calls
this.devtoolsBridge.ensureRunStarted(streamRunId ?? fallbackRunId) but ignores
the extracted chunkRunId; change the logic to prefer chunkRunId by calling
ensureRunStarted(chunkRunId ?? streamRunId ?? fallbackRunId) and, when
chunkRunId is present, set streamRunId = chunkRunId so streamRunId stays in
sync; update the CUSTOM case in processStream (the switch handling chunk.type,
around the GENERATION_EVENTS.RESULT handling) to use those symbols (chunkRunId,
streamRunId, fallbackRunId) and call ensureRunStarted with the combined
fallback.

---

Nitpick comments:
In `@packages/typescript/ai-devtools/src/components/hooks/HookDetails.tsx`:
- Around line 1226-1254: animateScrollTop currently stores timeouts in the
module-scoped scrollAnimations WeakMap but provides no way for HookDetails to
cancel them on unmount; change animateScrollTop to return the active timeout id
(or a cancel function) when starting the animation and/or convert
scrollAnimations to an iterable Map so pending timeouts can be enumerated, then
import onCleanup in the HookDetails component and register a cleanup that clears
any outstanding timeouts (window.clearTimeout) for that component (use the
returned ids or iterate the Map entries keyed by the element) to ensure timeouts
are cleared when HookDetails unmounts.

In `@packages/typescript/ai-devtools/src/store/hook-registry.ts`:
- Around line 640-695: syncRunsFromSnapshot currently unconditionally overwrites
existing.hookId which can misassociate runs if the same run ID appears for
multiple hooks; change the update logic in syncRunsFromSnapshot so it does not
overwrite existing.hookId when an existing run already has a different hookId
(e.g., only set existing.hookId = snapshot.hookId if !existing.hookId or
existing.hookId === snapshot.hookId), and rely on attachRunToHook(state,
snapshot.hookId, run.id) to record the hook→run relationship (or extend the run
model to track multiple hookIds if you need multi-association); update any
consumers expecting a single hookId accordingly.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0155858d-bdcf-4372-ada6-f475bf7cde13

📥 Commits

Reviewing files that changed from the base of the PR and between 4b0f6ce and 4fd865b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (90)
  • docs/getting-started/devtools.md
  • examples/ts-react-chat/src/routes/generations.structured-output.tsx
  • packages/typescript/ai-client/package.json
  • packages/typescript/ai-client/src/chat-client.ts
  • packages/typescript/ai-client/src/devtools-noop.ts
  • packages/typescript/ai-client/src/devtools.ts
  • packages/typescript/ai-client/src/generation-client.ts
  • packages/typescript/ai-client/src/generation-types.ts
  • packages/typescript/ai-client/src/index.ts
  • packages/typescript/ai-client/src/types.ts
  • packages/typescript/ai-client/src/video-generation-client.ts
  • packages/typescript/ai-client/tests/devtools.test.ts
  • packages/typescript/ai-client/tests/use-real-devtools-bridges.ts
  • packages/typescript/ai-client/vite.config.ts
  • packages/typescript/ai-devtools/src/components/Shell.tsx
  • packages/typescript/ai-devtools/src/components/hooks/FixtureNamePopover.tsx
  • packages/typescript/ai-devtools/src/components/hooks/GenerationPanel.tsx
  • packages/typescript/ai-devtools/src/components/hooks/HookDashboard.tsx
  • packages/typescript/ai-devtools/src/components/hooks/HookDetails.tsx
  • packages/typescript/ai-devtools/src/components/hooks/ToolFixtureForm.tsx
  • packages/typescript/ai-devtools/src/components/hooks/hook-dashboard-model.ts
  • packages/typescript/ai-devtools/src/store/hook-registry.ts
  • packages/typescript/ai-devtools/src/styles/use-styles.ts
  • packages/typescript/ai-devtools/tests/hook-dashboard-model.test.ts
  • packages/typescript/ai-devtools/tests/hook-registry.test.ts
  • packages/typescript/ai-event-client/src/index.ts
  • packages/typescript/ai-event-client/tests/emit.test.ts
  • packages/typescript/ai-preact/src/types.ts
  • packages/typescript/ai-preact/src/use-chat.ts
  • packages/typescript/ai-preact/tests/devtools-name-types.test.ts
  • packages/typescript/ai-react/src/types.ts
  • packages/typescript/ai-react/src/use-chat.ts
  • packages/typescript/ai-react/src/use-generate-audio.ts
  • packages/typescript/ai-react/src/use-generate-image.ts
  • packages/typescript/ai-react/src/use-generate-speech.ts
  • packages/typescript/ai-react/src/use-generate-video.ts
  • packages/typescript/ai-react/src/use-generation.ts
  • packages/typescript/ai-react/src/use-summarize.ts
  • packages/typescript/ai-react/src/use-transcription.ts
  • packages/typescript/ai-react/tests/devtools-name-types.test.ts
  • packages/typescript/ai-solid/src/types.ts
  • packages/typescript/ai-solid/src/use-chat.ts
  • packages/typescript/ai-solid/src/use-generate-audio.ts
  • packages/typescript/ai-solid/src/use-generate-image.ts
  • packages/typescript/ai-solid/src/use-generate-speech.ts
  • packages/typescript/ai-solid/src/use-generate-video.ts
  • packages/typescript/ai-solid/src/use-generation.ts
  • packages/typescript/ai-solid/src/use-summarize.ts
  • packages/typescript/ai-solid/src/use-transcription.ts
  • packages/typescript/ai-solid/tests/devtools-name-types.test.ts
  • packages/typescript/ai-svelte/src/create-chat.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-audio.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-image.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-speech.svelte.ts
  • packages/typescript/ai-svelte/src/create-generate-video.svelte.ts
  • packages/typescript/ai-svelte/src/create-generation.svelte.ts
  • packages/typescript/ai-svelte/src/create-summarize.svelte.ts
  • packages/typescript/ai-svelte/src/create-transcription.svelte.ts
  • packages/typescript/ai-svelte/src/types.ts
  • packages/typescript/ai-svelte/tests/devtools-name-types.test.ts
  • packages/typescript/ai-vue/src/types.ts
  • packages/typescript/ai-vue/src/use-chat.ts
  • packages/typescript/ai-vue/src/use-generate-audio.ts
  • packages/typescript/ai-vue/src/use-generate-image.ts
  • packages/typescript/ai-vue/src/use-generate-speech.ts
  • packages/typescript/ai-vue/src/use-generate-video.ts
  • packages/typescript/ai-vue/src/use-generation.ts
  • packages/typescript/ai-vue/src/use-summarize.ts
  • packages/typescript/ai-vue/src/use-transcription.ts
  • packages/typescript/ai-vue/tests/devtools-name-types.test.ts
  • packages/typescript/react-ai-devtools/src/AiDevtools.tsx
  • testing/e2e/fixtures/audio-gen/basic.json
  • testing/e2e/fixtures/tts/basic.json
  • testing/e2e/package.json
  • testing/e2e/src/components/DevtoolsHarness.tsx
  • testing/e2e/src/lib/devtools-test.ts
  • testing/e2e/src/routeTree.gen.ts
  • testing/e2e/src/routes/api.summarize.ts
  • testing/e2e/src/routes/devtools-chat.tsx
  • testing/e2e/src/routes/devtools-generation-hooks.tsx
  • testing/e2e/src/routes/devtools-route-a.tsx
  • testing/e2e/src/routes/devtools-route-b.tsx
  • testing/e2e/src/routes/devtools-structured.tsx
  • testing/e2e/src/routes/devtools-tools.tsx
  • testing/e2e/tests/devtools-chat-structured.spec.ts
  • testing/e2e/tests/devtools-generation-hooks.spec.ts
  • testing/e2e/tests/devtools-helpers.ts
  • testing/e2e/tests/devtools-registration.spec.ts
  • testing/e2e/tests/devtools-responsive.spec.ts
  • testing/e2e/tests/devtools-tools.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/getting-started/devtools.md

Comment on lines +60 to +107
export class NoOpChatDevtoolsBridge {
readonly events: ChatClientEventEmitter

constructor(options: ChatDevtoolsBridgeOptions) {
this.events = new NoOpChatClientEventEmitter(options.clientId)
}

// base bridge surface
emitRegistered(): void {}
emitUpdated(): void {}
emitSnapshot(): void {}
emitToolsRegistered(): void {}
emitRunLifecycle(
_eventType: unknown,
_runId: string,
_status: unknown,
_options?: { error?: string },
): void {}
deactivate(): void {}
supersede(): void {}
dispose(): void {}

// chat-specific surface
setCurrentStreamId(_streamId: string | null): void {}
getCurrentStreamId(): string | null {
return null
}
getLastStreamId(): string | null {
return null
}
resolveStreamId(): string {
return ''
}
observeChunk(_chunk: StreamChunk): void {}
beginRun(_runId: string, _threadId: string): void {}
getCurrentRunEventContext(): ChatClientRunEventContext | undefined {
return undefined
}
getCurrentOrLastRunEventContext(): ChatClientRunEventContext | undefined {
return undefined
}
findToolCallContext(toolCallId: string): ChatClientEventContext {
return { toolCallId }
}
async applyFixture(_fixture: AIDevtoolsToolFixture): Promise<void> {
// intentionally empty
}
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

No-op chat bridge is missing public methods from the real bridge contract.

Because the factory casts through unknown, type-checking won’t catch drift. NoOpChatDevtoolsBridge currently omits mountWithTools, notifyToolsChanged, and recordStreamId, which are present on ChatDevtoolsBridge (packages/typescript/ai-client/src/devtools.ts). Any call on the default no-op path can crash at runtime.

Suggested fix
 export class NoOpChatDevtoolsBridge {
   readonly events: ChatClientEventEmitter

   constructor(options: ChatDevtoolsBridgeOptions) {
     this.events = new NoOpChatClientEventEmitter(options.clientId)
   }

   // base bridge surface
   emitRegistered(): void {}
   emitUpdated(): void {}
   emitSnapshot(): void {}
   emitToolsRegistered(): void {}
@@
   // chat-specific surface
+  mountWithTools(_initialMessageCount: number): void {}
+  notifyToolsChanged(): void {}
   setCurrentStreamId(_streamId: string | null): void {}
+  recordStreamId(_streamId: string): void {}
   getCurrentStreamId(): string | null {
     return null
   }
🤖 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 `@packages/typescript/ai-client/src/devtools-noop.ts` around lines 60 - 107,
NoOpChatDevtoolsBridge is missing the public methods mountWithTools,
notifyToolsChanged, and recordStreamId from the ChatDevtoolsBridge contract,
causing runtime crashes; update the class to add matching method signatures
(mountWithTools(...), notifyToolsChanged(...), recordStreamId(streamId: string |
null): void) with no-op implementations and correct parameter types so the no-op
bridge fully implements the ChatDevtoolsBridge surface (match the signatures
defined in ChatDevtoolsBridge in devtools.ts).

Comment on lines +1348 to +1368
ensureRunStarted(runId: string): void {
if (this.activeRunStarted && this.activeRunId === runId) return

if (
!this.activeRunStarted &&
this.activeRunId &&
this.activeRunId !== runId
) {
this.renameRun(this.activeRunId, runId)
}

this.activeRunId = runId
this.activeRunStarted = true
this.upsertRun(runId, {
status: 'generating',
isLoading: true,
clearError: true,
})
this.emitRunLifecycle('run:started', runId, 'started')
this.emitState()
}
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rename the provisional run even after early updates have started it.

beginRun() creates a client-side run id, and callers now invoke ensureRunStarted(...) from early CUSTOM/progress events before RUN_STARTED arrives. If the server later sends a different runId, this branch skips renameRun() because activeRunStarted is already true, so one stale run stays stuck in generating while later patches move to a second run. Rename whenever activeRunId !== runId before swapping the active id, then keep the started lifecycle on that renamed entry. A regression test where a CUSTOM chunk precedes RUN_STARTED would lock this down.

💡 Minimal fix
   ensureRunStarted(runId: string): void {
-    if (this.activeRunStarted && this.activeRunId === runId) return
-
-    if (
-      !this.activeRunStarted &&
-      this.activeRunId &&
-      this.activeRunId !== runId
-    ) {
+    if (this.activeRunId && this.activeRunId !== runId) {
       this.renameRun(this.activeRunId, runId)
     }
+    if (this.activeRunStarted && this.activeRunId === runId) return
 
     this.activeRunId = runId
     this.activeRunStarted = true
🤖 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 `@packages/typescript/ai-client/src/devtools.ts` around lines 1348 - 1368,
ensureRunStarted currently skips renaming when activeRunStarted is true, causing
a stale provisional run to remain; fix by moving the rename logic before the
early-return and always calling this.renameRun(this.activeRunId, runId) when
this.activeRunId exists and !== runId (regardless of activeRunStarted). Keep the
early return if this.activeRunStarted && this.activeRunId === runId, then
proceed to set this.activeRunId = runId, this.activeRunStarted = true,
upsertRun(... 'generating' ...), and emitRunLifecycle/emitState so the renamed
entry retains the started lifecycle; reference ensureRunStarted, activeRunId,
activeRunStarted, and renameRun.

Comment on lines +108 to +115
createEffect(() => {
generationRuns().length
queueMicrotask(() => {
if (runsContainer) {
runsContainer.scrollTop = runsContainer.scrollHeight
}
})
})
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Auto-scroll is targeting a non-scrollable wrapper.

runsContainer is the generationRuns div, but that class is still just a growing flex column in packages/typescript/ai-devtools/src/styles/use-styles.ts. Once this section overflows, the scrollable element is the outer pane, so this microtask won't actually keep the latest run in view. Point the ref at the real overflow container, or make generationRuns the scroll container itself.

Also applies to: 129-134

🤖 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 `@packages/typescript/ai-devtools/src/components/hooks/GenerationPanel.tsx`
around lines 108 - 115, The auto-scroll createEffect is setting scrollTop on the
non-scrollable runsContainer (the generationRuns div) so it never scrolls;
either change the ref to target the actual overflow element (the outer pane
container used to hold generation runs—e.g., introduce runsScrollContainer and
attach the ref to that DOM node used for scrolling) or make the generationRuns
element itself the scroll container by adding overflow:auto/overflow-y:auto and
a max-height in the styles (use-styles.ts) so
runsContainer.scrollTop/scrollHeight in createEffect (and the identical logic at
lines 129-134) operate on the real scrollable element. Ensure the referenced
symbol names (runsContainer/generationRuns or the new runsScrollContainer) and
createEffect are updated accordingly.

- Sort imports alphabetically across ai-client, ai-react, ai-solid,
  ai-vue, and ai-devtools-core
- Drop leading underscore from NoOpGenerationDevtoolsBridge type
  parameter (used in constructor signature)
- Disable no-restricted-syntax on intentional `as unknown as` casts in
  no-op bridge factories and generation result assignment, with reasons
- Remove unnecessary type assertion in devtools.ts (auto-fixed)
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
packages/typescript/ai-client/src/video-generation-client.ts (1)

284-305: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate VIDEO_* CUSTOM event payloads before updating client/devtools state

parseSSEResponse deserializes SSE chunks with JSON.parse (no runtime schema checks), and video-generation-client.ts then as-casts chunk.value for video:job:created, video:status, generation:result, and generation:progress before calling setJobId, setVideoStatus, setResult, and setProgress. Add Zod schemas for these CUSTOM payloads and safeParse them (fail fast or ignore invalid events) to prevent malformed/version-skewed payloads from corrupting state.

🤖 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 `@packages/typescript/ai-client/src/video-generation-client.ts` around lines
284 - 305, The CUSTOM-case is trusting JSON-parsed chunk.value and casting it
for VIDEO_JOB_CREATED, VIDEO_STATUS, RESULT, and PROGRESS which can corrupt
client/devtools state on malformed payloads; add Zod schemas (e.g.,
VideoJobCreatedSchema, VideoStatusSchema, VideoResultSchema,
VideoProgressSchema) and import zod, then replace the unchecked `as` casts
inside the case 'CUSTOM' branch of video-generation-client.ts with safe parses
(schema.safeParse(chunk.value)) and only call setJobId, setVideoStatus,
setResult, setProgress, and callbacks when safeParse succeeds (otherwise
ignore/log and fail fast if appropriate); reference the existing
functions/methods setJobId, setVideoStatus, setResult, setProgress, the
GENERATION_EVENTS names, and VideoStatusInfo/VideoGenerateResult types to guide
schema shapes.
packages/typescript/ai-client/src/generation-client.ts (1)

258-268: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate streamed GENERATION_EVENTS.PROGRESS payloads instead of type-casting
processStream writes chunk.value to progress state/devtools with no runtime checks in both packages/typescript/ai-client/src/generation-client.ts and packages/typescript/ai-client/src/video-generation-client.ts; parseSSEResponse only JSON.parses, so malformed progress payloads can slip through. Implement protocol-boundary validation (e.g., a Zod schema) and ensure zod is available at runtime (move from devDependencies to dependencies), or reuse existing runtime validators (e.g., numberField/stringField patterns from devtools.ts).

🤖 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 `@packages/typescript/ai-client/src/generation-client.ts` around lines 258 -
268, The GENERATION_EVENTS.PROGRESS branch in generation-client.ts currently
type-casts chunk.value and calls setProgress without runtime validation; update
processStream handling (the 'CUSTOM' case where chunk.name ===
GENERATION_EVENTS.PROGRESS) to validate chunk.value against a runtime schema
before calling setProgress (e.g., require { progress: number, message?: string
}) — either add a Zod schema and move zod from devDependencies to dependencies
so it’s available at runtime, or reuse the existing runtime validators
(numberField/stringField) from devtools.ts to validate progress and message;
ensure parseSSEResponse consumers (including video-generation-client.ts) use the
same validation and reject or log malformed payloads instead of blindly calling
setProgress.
♻️ Duplicate comments (2)
packages/typescript/ai-client/src/devtools-noop.ts (1)

60-107: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add the missing no-op chat bridge methods.

createNoOpChatDevtoolsBridge() still casts this class to ChatDevtoolsBridge, but mountWithTools, notifyToolsChanged, and recordStreamId are not implemented here. Any consumer on the default no-op path that calls those methods will throw at runtime.

Minimal patch
 export class NoOpChatDevtoolsBridge {
   readonly events: ChatClientEventEmitter

   constructor(options: ChatDevtoolsBridgeOptions) {
     this.events = new NoOpChatClientEventEmitter(options.clientId)
   }

   // base bridge surface
   emitRegistered(): void {}
   emitUpdated(): void {}
   emitSnapshot(): void {}
   emitToolsRegistered(): void {}
@@
   // chat-specific surface
+  mountWithTools(_initialMessageCount: number): void {}
+  notifyToolsChanged(): void {}
   setCurrentStreamId(_streamId: string | null): void {}
+  recordStreamId(_streamId: string): void {}
   getCurrentStreamId(): string | null {
     return null
   }

Also applies to: 164-171

🤖 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 `@packages/typescript/ai-client/src/devtools-noop.ts` around lines 60 - 107,
NoOpChatDevtoolsBridge is missing no-op implementations for mountWithTools,
notifyToolsChanged, and recordStreamId even though createNoOpChatDevtoolsBridge
casts it to ChatDevtoolsBridge; add empty/no-op method stubs named
mountWithTools(...), notifyToolsChanged(...), and recordStreamId(streamId:
string | null) (matching the ChatDevtoolsBridge signatures) to the
NoOpChatDevtoolsBridge class so callers won't throw at runtime, keeping their
bodies intentionally empty or returning appropriate no-op values.
packages/typescript/ai-client/src/devtools.ts (1)

1347-1367: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rename provisional runs before the started-run early return.

ensureRunStarted() still only renames while activeRunStarted is false. If an early update starts the provisional run and RUN_STARTED later arrives with a different runId, one stale run stays in generating and later updates attach to a second run.

Minimal patch
   ensureRunStarted(runId: string): void {
-    if (this.activeRunStarted && this.activeRunId === runId) return
-
-    if (
-      !this.activeRunStarted &&
-      this.activeRunId &&
-      this.activeRunId !== runId
-    ) {
+    if (this.activeRunId && this.activeRunId !== runId) {
       this.renameRun(this.activeRunId, runId)
     }
+    if (this.activeRunStarted && this.activeRunId === runId) return

     this.activeRunId = runId
     this.activeRunStarted = true
🤖 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 `@packages/typescript/ai-client/src/devtools.ts` around lines 1347 - 1367, The
rename of provisional runs should happen before the early-return so stale runs
don't remain; inside ensureRunStarted, detect when this.activeRunId exists and
differs from the incoming runId and call renameRun(this.activeRunId, runId)
before returning, rather than only when activeRunStarted is false. In short: in
ensureRunStarted, move/adjust the block that calls renameRun to run whenever
this.activeRunId && this.activeRunId !== runId (regardless of
this.activeRunStarted) and then keep the existing early-return for the case
this.activeRunStarted && this.activeRunId === runId; references:
ensureRunStarted, renameRun, activeRunId, activeRunStarted.
🤖 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.

Outside diff comments:
In `@packages/typescript/ai-client/src/generation-client.ts`:
- Around line 258-268: The GENERATION_EVENTS.PROGRESS branch in
generation-client.ts currently type-casts chunk.value and calls setProgress
without runtime validation; update processStream handling (the 'CUSTOM' case
where chunk.name === GENERATION_EVENTS.PROGRESS) to validate chunk.value against
a runtime schema before calling setProgress (e.g., require { progress: number,
message?: string }) — either add a Zod schema and move zod from devDependencies
to dependencies so it’s available at runtime, or reuse the existing runtime
validators (numberField/stringField) from devtools.ts to validate progress and
message; ensure parseSSEResponse consumers (including
video-generation-client.ts) use the same validation and reject or log malformed
payloads instead of blindly calling setProgress.

In `@packages/typescript/ai-client/src/video-generation-client.ts`:
- Around line 284-305: The CUSTOM-case is trusting JSON-parsed chunk.value and
casting it for VIDEO_JOB_CREATED, VIDEO_STATUS, RESULT, and PROGRESS which can
corrupt client/devtools state on malformed payloads; add Zod schemas (e.g.,
VideoJobCreatedSchema, VideoStatusSchema, VideoResultSchema,
VideoProgressSchema) and import zod, then replace the unchecked `as` casts
inside the case 'CUSTOM' branch of video-generation-client.ts with safe parses
(schema.safeParse(chunk.value)) and only call setJobId, setVideoStatus,
setResult, setProgress, and callbacks when safeParse succeeds (otherwise
ignore/log and fail fast if appropriate); reference the existing
functions/methods setJobId, setVideoStatus, setResult, setProgress, the
GENERATION_EVENTS names, and VideoStatusInfo/VideoGenerateResult types to guide
schema shapes.

---

Duplicate comments:
In `@packages/typescript/ai-client/src/devtools-noop.ts`:
- Around line 60-107: NoOpChatDevtoolsBridge is missing no-op implementations
for mountWithTools, notifyToolsChanged, and recordStreamId even though
createNoOpChatDevtoolsBridge casts it to ChatDevtoolsBridge; add empty/no-op
method stubs named mountWithTools(...), notifyToolsChanged(...), and
recordStreamId(streamId: string | null) (matching the ChatDevtoolsBridge
signatures) to the NoOpChatDevtoolsBridge class so callers won't throw at
runtime, keeping their bodies intentionally empty or returning appropriate no-op
values.

In `@packages/typescript/ai-client/src/devtools.ts`:
- Around line 1347-1367: The rename of provisional runs should happen before the
early-return so stale runs don't remain; inside ensureRunStarted, detect when
this.activeRunId exists and differs from the incoming runId and call
renameRun(this.activeRunId, runId) before returning, rather than only when
activeRunStarted is false. In short: in ensureRunStarted, move/adjust the block
that calls renameRun to run whenever this.activeRunId && this.activeRunId !==
runId (regardless of this.activeRunStarted) and then keep the existing
early-return for the case this.activeRunStarted && this.activeRunId === runId;
references: ensureRunStarted, renameRun, activeRunId, activeRunStarted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cbc38b0a-0e15-472c-855a-f316a27d1147

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3bb87 and 901fe25.

📒 Files selected for processing (8)
  • packages/typescript/ai-client/src/devtools-noop.ts
  • packages/typescript/ai-client/src/devtools.ts
  • packages/typescript/ai-client/src/generation-client.ts
  • packages/typescript/ai-client/src/video-generation-client.ts
  • packages/typescript/ai-devtools/src/components/hooks/HookDetails.tsx
  • packages/typescript/ai-react/src/types.ts
  • packages/typescript/ai-solid/src/types.ts
  • packages/typescript/ai-vue/src/types.ts

}),
)
} catch {
localStorage.removeItem(fixturesStorageKey)
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.

Critical: silently destroys user-saved fixtures on any parse error.

A transient JSON.parse failure, schema drift after a future migration, or even a quota error during removeItem will silently wipe the user's entire saved-fixture library — no log, no toast, no Sentry surface. They'll relaunch devtools and wonder where their fixtures went.

Per CLAUDE.md: "Don't create fallback code. It hides problems. Just display errors to the user."

Suggest at minimum logging the error, and reconsider whether removeItem is the right reaction (keeping the bad payload around for inspection is often safer than nuking it).

threadId,
output: null,
errorText:
error instanceof Error ? error.message : 'Tool execution failed.',
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.

Critical: tool-fixture execution errors are flattened into a generic string with no logging.

This catch swallows everything thrown by executeFunc (and anything thrown by addToolResultForFixture itself — event emit, processor mutation, etc.). Non-Error throws are coerced to "Tool execution failed." with no stack, no console.error, no error event. The devtools UI shows a fixture marked output-error with nothing actionable.

At minimum: log the error before reporting it, and narrow the try to executeFunc(fixture.input) only so unrelated bugs in addToolResultForFixture don't masquerade as tool failures.

Per CLAUDE.md: "Don't create fallback code. It hides problems. Just display errors to the user."

if (state.seenEventIds[key]) {
return false
}
state.seenEventIds[key] = true
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.

Critical: seenEventIds grows unboundedly for the life of a session.

Every devtools event inserts a key here at line 209, and the only place entries are ever removed is clearHookRegistry (line 377). In a long-running app — especially one with frequent streaming runs / tool chunks — this map will accumulate millions of keys, all of which are retained by the Solid store + immer produce copies on every state update. That's a real memory leak, not a theoretical one.

Options:

  • LRU/ring buffer capped at e.g. 10k entries
  • Evict keys associated with a hook when hook:unregistered fires
  • Evict keys older than some window when a new event is recorded

Whichever you pick, the dedupe contract should be made explicit ("events older than X minutes may dedupe-miss") so consumers don't depend on perfect global dedupe forever.

- log + preserve raw localStorage payload on fixture parse failure instead
  of silently calling removeItem
- log + report full error name/message from tool-fixture execute, and
  narrow the try block to executeFunc only so unrelated bugs in
  addToolResultForFixture don't masquerade as tool failures
- cap seenEventIds at 10k via FIFO ring buffer to prevent unbounded
  memory growth over the lifetime of long-running sessions; also reset
  the order array in clearHookRegistry
- restore ai-code-mode/vite.config.ts to main (rename detection swapped
  it with ai-client's during the merge); ai-client now correctly
  declares devtools.ts as a separate entry
@AlemTuzlak
Copy link
Copy Markdown
Contributor Author

Synced with main (merge — directory-rename made a true rebase impractical) and pushed PR feedback fixes in b12231a:

  • ai-context.tsx fixture loader — no longer calls removeItem on parse failure. Logs the error via console.error and preserves the raw payload so users can recover saved fixtures manually. Also logs (instead of silently returning) when the parsed payload isn't an array.
  • devtools.ts tool-fixture executor — narrowed the try to wrap only executeFunc(fixture.input) so unrelated bugs in addToolResultForFixture no longer surface as fixture failures. Now logs the error via console.error and reports name: message (or String(error) for non-Error throws) instead of the generic "Tool execution failed." string.
  • hook-registry.ts seenEventIds — added SEEN_EVENT_IDS_CAP (10 000) and a parallel seenEventIdOrder array so the dedupe map evicts oldest entries FIFO once capped. clearHookRegistry now resets the order array too. Documented the dedupe contract (events older than the window may dedupe-miss) on the constant.

Add a mandatory pre-PR quality gate to CLAUDE.md and a new AGENTS.md
(cross-agent file) documenting that `pnpm test:pr` — the exact target set
the CI `PR` workflow runs — must be run and pass locally before pushing.
Falling back on CI as the first signal wastes review cycles when a quick
local run would have caught the failure.
…types + comments)

Critical
- ToolFixtureForm: remove dead `'{}' ? : '{}'` ternary leftover from refactor
- ChatDevtoolsBridge.executeFixture: warn when execute=true but no client tool
  is registered instead of silently substituting the saved output
- stringifyFixtureValue / parseFixtureResultContent: log + report the original
  error instead of swallowing JSON.stringify / JSON.parse failures
- devtools-middleware: replace `chunk: any` with a mirrored discriminated
  union (DevtoolsKnownChunk) + isKnownChunk type guard, drop the `as` cast
  on chunk.error, fall back with the raw chunk payload instead of "Unknown
  error" when RUN_ERROR.message is missing
- normalizeRunStatusFromSnapshot: return `null` for unknown statuses and let
  the caller skip + warn instead of silently coercing them to `'updated'`

Important
- Remove unreachable `'unmounted'` HookLifecycle variant + dead filters
- Drop unused `runs` prop on GenerationPanel
- removeHookRecord: also clean up TimelineEvents attached to the hook
- IterationTimeline `totalUsage`: compare on `totalTokens` (not
  prompt+completion) so providers that include reasoning tokens in
  totalTokens don't get their max overwritten by a smaller later reading
- jsonSafeValue / stringifyToolArguments: log + return an explicit
  placeholder instead of returning the original on failure
- No-op bridges: add compile-time parity checks (Exclude<keyof, keyof>)
  so adding a public method to the real bridge fails the build until the
  no-op is updated, instead of becoming a runtime TypeError
- devtools-middleware: wrap every emit with `safeEmit` so a subscriber
  throw can't bubble into the host chat engine
- markEventSeen: warn when a dedupe key falls back entirely to literal
  sentinels; tools:registered: warn when hookName is missing
- hook-registry tests: cover snapshot run backfill (success/error/idle/
  unknown statuses) and lifecycle inference from snapshot state

Type design
- AIDevtoolsGenerationRunStatus union ('idle' | 'generating' | 'success'
  | 'error' | 'cancelled') replacing the previous `status: string` and
  threaded through GenerationDevtoolsCoreState / SnapshotBase / RunPatch
- Drop redundant `unknown | null` on input / videoStatus fields
- Remove `extends Record<string, unknown>` from public snapshot interfaces;
  ClientDevtoolsBridge constraint relaxed to `extends object` and the
  wire-envelope widening is pushed to emitSnapshot
- Drop pointless `as Record<string, unknown>` casts at JsonTree call sites
  (JsonTree's TData accepts arbitrary value)

Comment hygiene
- Strip file-top docblocks and class-banner JSDoc rewriting the PR
  description (devtools-noop, use-real-devtools-bridges, devtools-system-
  prompt-mirror.test, several blocks in devtools.ts)
- Remove "for backward compatibility" / "exactly like it did before the
  devtools work landed" PR-narrative phrases
- Drop ASCII section-banner separators in devtools.ts, use-styles.ts,
  ai-context.tsx
- Compress multi-line JSDoc on short methods to one-liners or remove
  where the name was self-describing
- Strip obvious line-narration comments in ai-context.tsx batching helpers
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