Skip to content

Add @workkit/chat-react debug frame hook#118

Open
Chris79OG wants to merge 6 commits into
beeeku:masterfrom
Chris79OG:luminaflow/chat-react-debug-frames
Open

Add @workkit/chat-react debug frame hook#118
Chris79OG wants to merge 6 commits into
beeeku:masterfrom
Chris79OG:luminaflow/chat-react-debug-frames

Conversation

@Chris79OG
Copy link
Copy Markdown

@Chris79OG Chris79OG commented May 23, 2026

Summary

  • add shared DebugFrame/DebugFrameDirection types to @workkit/chat
  • scaffold @workkit/chat-react with headless useChatDebugFrames
  • capture inbound message events and outbound socket.send calls with buffer/filter/clear support
  • add focused hook tests plus changeset

Verification

  • bun run --filter @workkit/chat-react test
  • bun run --filter @workkit/chat-react typecheck
  • bun run --filter @workkit/chat-react build
  • bun run --filter @workkit/chat test
  • bun run --filter @workkit/chat typecheck
  • bun x biome check packages/chat/src/index.ts packages/chat/src/types.ts packages/chat-react .changeset/quiet-frames-chat-react.md
  • bun run constitution:check --diff-only --base=origin/master

Closes #84

Summary by CodeRabbit

  • New Features

    • Added a new React package with a headless hook for inspecting WebSocket chat frames
    • Introduced wire-level debug frame types for capturing inbound/outbound traffic
  • Documentation

    • Added package README, docs guide, API reference, and initial CHANGELOG entries
  • Tests

    • Added unit tests verifying frame capture, buffering, filtering, connection state, isolation, and clear behavior

Review Change Stack

Why:
- implement help-wanted chat-react debug frames package for beeeku#84
- share DebugFrame types from @workkit/chat and provide focused hook tests

Verified:
- bun run --filter @workkit/chat-react test
- bun run --filter @workkit/chat-react typecheck
- bun run --filter @workkit/chat-react build
- bun run --filter @workkit/chat test
- bun run --filter @workkit/chat typecheck
- bun x biome check packages/chat/src/index.ts packages/chat/src/types.ts packages/chat-react .changeset/quiet-frames-chat-react.md
@Chris79OG Chris79OG requested a review from beeeku as a code owner May 23, 2026 03:48
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 23, 2026

Warning

Review limit reached

@Chris79OG, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 1 minute and 7 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc738d51-5a77-44ec-9b9b-8e43289d2165

📥 Commits

Reviewing files that changed from the base of the PR and between 20e3b9c and c185255.

📒 Files selected for processing (2)
  • packages/chat-react/src/index.ts
  • packages/chat-react/tests/use-chat-debug-frames.test.ts
📝 Walkthrough

Walkthrough

Adds DebugFrame types to @workkit/chat, creates @workkit/chat-react package, implements a headless useChatDebugFrames React hook with JSON parsing, bounded ring buffer, allowlist filtering, outbound send interception, connection-state tracking, tests, docs, and release metadata.

Changes

Debug frames capture and inspection

Layer / File(s) Summary
Debug frame types in @workkit/chat
packages/chat/src/types.ts, packages/chat/src/index.ts
Introduces DebugFrameDirection and DebugFrame and re-exports them from the package root.
Package infrastructure and configuration
packages/chat-react/package.json, packages/chat-react/bunup.config.ts, packages/chat-react/tsconfig.json, packages/chat-react/vitest.config.ts
Creates @workkit/chat-react package manifest and build/test configs (Bunup ESM build, dts, tsconfig, Vitest test discovery).
Public API surface & readyState allowlists
packages/chat-react/src/index.ts
Exports ChatDebugConnectionState, ChatDebugSocket, UseChatDebugFramesOptions, UseChatDebugFramesResult, and allowlists plus mapping from WebSocket readyState.
Wire parsing utilities
packages/chat-react/src/index.ts
Implements JSON parsing, role/timestamp coercion, byte sizing, and typed toMessage validation for incoming/outgoing payloads.
Frame construction and bounded buffer
packages/chat-react/src/index.ts
Adds makeFrame for success/error frames, sanitizeBufferSize, and appendFrame to enforce allowlist filtering and ring-buffer semantics.
Outbound send recorder
packages/chat-react/src/index.ts
Installs a WeakMap-backed send recorder that wraps socket.send to capture outbound frames and restores original send when recorders are removed.
useChatDebugFrames hook implementation
packages/chat-react/src/index.ts
React hook wiring: state effects for frames and connectionState, message/open/close/error listeners, outbound interception, clear() API, and cleanup on unmount/socket change.
Hook test suite
packages/chat-react/tests/use-chat-debug-frames.test.ts
Adds MockSocket, mount helpers, and tests covering inbound/outbound capture, ordering, filtering, buffer capping, NaN fallback, id isolation, send-capture isolation, role normalization, connection-state, and clear().
Documentation and release notes
packages/chat-react/README.md, apps/docs/.../guides/chat-react.md, docs/api-reference.md, docs/getting-started.md, packages/chat-react/CHANGELOG.md, .changeset/quiet-frames-chat-react.md
Adds package README and guide, API reference, getting-started install snippet, CHANGELOG initial entry, and Changeset documenting version bumps.

Sequence Diagram

sequenceDiagram
  participant Component
  participant useChatDebugFrames
  participant ChatDebugSocket
  participant FrameBuffer

  Component->>useChatDebugFrames: mount(socket, options)
  useChatDebugFrames->>ChatDebugSocket: addEventListener("message"/"open"/"close"/"error")
  useChatDebugFrames->>ChatDebugSocket: installSendRecorder(socket)
  ChatDebugSocket->>useChatDebugFrames: message event (data)
  useChatDebugFrames->>useChatDebugFrames: parse → makeFrame
  useChatDebugFrames->>FrameBuffer: append(inbound DebugFrame)
  Component->>ChatDebugSocket: call send(data)
  ChatDebugSocket->>useChatDebugFrames: wrapped send intercept (outbound)
  useChatDebugFrames->>FrameBuffer: append(outbound DebugFrame)
  useChatDebugFrames->>ChatDebugSocket: call original send(data)
  ChatDebugSocket->>useChatDebugFrames: open/close/error events
  useChatDebugFrames->>Component: update connectionState
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • beeeku/workkit#85: ADR documenting the package shape and API decision for @workkit/chat-react and useChatDebugFrames, directly related to this implementation.

Poem

🐰 I nibble frames both in and out,

I tag each type and watch the route.
A headless hook that keeps a score,
Buffer full, then clear once more. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 pull request title clearly and concisely summarizes the primary change: introducing a new debug hook for @workkit/chat-react.
Linked Issues check ✅ Passed The implementation fully addresses all coding objectives from issue #84: separate @workkit/chat-react package, useChatDebugFrames hook with frame capture, ring buffer with configurable size, include/filter options, connection state tracking, and a headless hook design.
Out of Scope Changes check ✅ Passed All changes are in scope: core chat-react package files, shared type exports from chat, comprehensive tests, documentation updates, and changeset. No unrelated modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown

@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: 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 `@packages/chat-react/src/index.ts`:
- Around line 83-87: The toMessage return currently casts wire.role directly to
ChatMessage["role"], allowing invalid runtime values; update to validate
wire.role against the allowed roles union before assigning (e.g., check if
typeof wire.role === "string" and wire.role is one of the ChatMessage role
values) and then set role to that validated value or fallback to "user" (or
throw) — locate the logic in the toMessage function where id/type/role/content
are returned and replace the direct cast of wire.role with an explicit union
check using the ChatMessage role set.
🪄 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: 112f87fa-e871-4d94-ba62-124810d06975

📥 Commits

Reviewing files that changed from the base of the PR and between 4935d17 and 73cf444.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • .changeset/quiet-frames-chat-react.md
  • packages/chat-react/CHANGELOG.md
  • packages/chat-react/README.md
  • packages/chat-react/bunup.config.ts
  • packages/chat-react/package.json
  • packages/chat-react/src/index.ts
  • packages/chat-react/tests/use-chat-debug-frames.test.ts
  • packages/chat-react/tsconfig.json
  • packages/chat-react/vitest.config.ts
  • packages/chat/src/index.ts
  • packages/chat/src/types.ts

Comment thread packages/chat-react/src/index.ts
Why:
- Address CodeRabbit feedback on PR beeeku#118 by preventing invalid wire roles from entering parsed debug messages.

Verified:
- bun run --filter @workkit/chat-react test
- bun run --filter @workkit/chat-react typecheck
- bun run --filter @workkit/chat-react build
- bun run --filter @workkit/chat test
- bun run --filter @workkit/chat typecheck
- bun x biome check packages/chat-react/src/index.ts packages/chat-react/tests/use-chat-debug-frames.test.ts packages/chat/src/index.ts packages/chat/src/types.ts
- bun run constitution:check --diff-only --base=origin/master
- git diff --check
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a client-side debugging primitive for @workkit/chat by introducing shared DebugFrame types and a new @workkit/chat-react package exposing a headless useChatDebugFrames hook for capturing inbound message events and outbound socket.send calls.

Changes:

  • Add DebugFrame / DebugFrameDirection types to @workkit/chat and re-export them.
  • Introduce @workkit/chat-react with useChatDebugFrames (buffering + include-filtering + clear + connection state).
  • Add focused hook tests and publish tooling/docs (README, changelog, changeset).

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/chat/src/types.ts Adds shared DebugFrame + direction types for client-side frame inspection.
packages/chat/src/index.ts Re-exports the new debug frame types from @workkit/chat.
packages/chat-react/src/index.ts Implements useChatDebugFrames hook (capture/parse/filter/buffer/clear/state).
packages/chat-react/tests/use-chat-debug-frames.test.ts Adds tests for capturing frames, filtering/capping, role validation, and clear/state.
packages/chat-react/package.json New package manifest (exports, peers, scripts, deps).
packages/chat-react/tsconfig.json TS config for DOM + library build output.
packages/chat-react/vitest.config.ts Vitest configuration for the new package.
packages/chat-react/bunup.config.ts Build configuration for bundling + DTS output.
packages/chat-react/README.md Basic usage documentation for the hook.
packages/chat-react/CHANGELOG.md Initial changelog entry for v0.1.0.
.changeset/quiet-frames-chat-react.md Changeset for releasing @workkit/chat (patch) and @workkit/chat-react (minor).
bun.lock Lockfile updates to include the new package and dependency graph changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +88 to +99
const role = isChatMessageRole(wire.role) ? wire.role : "user";
return {
id: typeof wire.id === "string" ? wire.id : "",
type: wire.type as ChatMessageType,
role,
content: wire.content,
metadata:
typeof wire.metadata === "object" && wire.metadata !== null && !Array.isArray(wire.metadata)
? (wire.metadata as Record<string, unknown>)
: undefined,
timestamp: Date.now(),
};
Comment thread packages/chat-react/src/index.ts Outdated
Comment on lines +40 to +41
let nextFrameId = 0;

Comment on lines +55 to +58
function bytesFor(data: unknown): number {
if (typeof data === "string") {
return new TextEncoder().encode(data).byteLength;
}
Comment thread packages/chat-react/src/index.ts Outdated
socket: ChatDebugSocket | null | undefined,
options: UseChatDebugFramesOptions = {},
): UseChatDebugFramesResult {
const bufferSize = Math.max(1, Math.floor(options.bufferSize ?? DEFAULT_BUFFER_SIZE));
@beeeku
Copy link
Copy Markdown
Owner

beeeku commented May 23, 2026

Great PR @Chris79OG ! Appreciate the help . The code looks great for first time contribution , please fix the copilot review comments and we need to update the readme for the new packages and docs site too . If you can attach visual verification proofs that would be great too .
Thanks 🥇

Copy link
Copy Markdown

@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 (1)
packages/chat-react/src/index.ts (1)

195-210: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid clobbering another hook's send wrapper on cleanup.

Line 195 binds whatever socket.send is currently installed, so two useChatDebugFrames instances on the same socket stack wrappers. If the earlier hook unmounts first, Line 208 can disable the later hook; if the later hook unmounts last, it restores the earlier wrapper and leaves a patched send behind after both hooks are gone. This needs shared per-socket patching/ref-counting instead of per-instance replacement. A same-socket double-mount regression test would catch it.

🤖 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/chat-react/src/index.ts` around lines 195 - 210, The current
per-instance replacement of socket.send (using originalSendRef and setting
socket.send directly in useChatDebugFrames) can clobber other hook instances;
replace this with a shared per-socket registry (e.g. a WeakMap keyed by the
socket) that stores {refCount, originalSend, wrapperSend} so mounting increments
refCount and installs a single wrapper that calls recordFrame("out", data) then
delegates to originalSend, and unmounting decrements refCount and only restores
socket.send to originalSend when refCount reaches 0; update references to
originalSendRef/current behavior to use the registry for both install and
cleanup so multiple useChatDebugFrames on the same socket stack cleanly.
🤖 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/chat-react/src/index.ts`:
- Around line 195-210: The current per-instance replacement of socket.send
(using originalSendRef and setting socket.send directly in useChatDebugFrames)
can clobber other hook instances; replace this with a shared per-socket registry
(e.g. a WeakMap keyed by the socket) that stores {refCount, originalSend,
wrapperSend} so mounting increments refCount and installs a single wrapper that
calls recordFrame("out", data) then delegates to originalSend, and unmounting
decrements refCount and only restores socket.send to originalSend when refCount
reaches 0; update references to originalSendRef/current behavior to use the
registry for both install and cleanup so multiple useChatDebugFrames on the same
socket stack cleanly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 11bf1c4b-7a16-469c-9e55-4d7b4d80ab97

📥 Commits

Reviewing files that changed from the base of the PR and between 73cf444 and f42c5a2.

📒 Files selected for processing (2)
  • packages/chat-react/src/index.ts
  • packages/chat-react/tests/use-chat-debug-frames.test.ts

@Chris79OG
Copy link
Copy Markdown
Author

Addressed the latest review/maintainer feedback in commit 22ac9c2.\n\nWhat changed:\n- replaced per-hook send replacement with a shared per-socket send recorder registry, so multiple hook instances on the same socket no longer clobber each other on cleanup\n- added a same-socket double-mount regression test covering recorder isolation and original send restoration\n- added @workkit/chat and @workkit/chat-react to the root README plus docs index, getting-started install list, and API reference\n\nVerification proofs:\n- bun run --filter @workkit/chat build\n- bun run --filter @workkit/chat-react test: 7 passed\n- bun run --filter @workkit/chat-react typecheck\n- bun run --filter @workkit/chat-react build\n- bun x biome check packages/chat-react/src/index.ts packages/chat-react/tests/use-chat-debug-frames.test.ts\n- git diff --check\n\nThe package is headless, so there is no styled visual surface to screenshot; the docs updates are the visible proof surface for this PR.

Copy link
Copy Markdown
Owner

beeeku commented May 24, 2026

Status check against the latest commit (22ac9c2):

Previously flagged review comments — already addressed in 22ac9c2, but the inline threads weren't auto-resolved:

  • toMessage overwriting wire timestamp → fixed via timestampFromWire(wire.timestamp) and verified by the "captures inbound and outbound chat frames newest-last" test which asserts the wire timestamp is preserved (packages/chat-react/src/index.ts:109).
  • bytesFor allocating a new TextEncoder per call → fixed; textEncoder is now module-scoped (line 28).
  • bufferSize NaN handling → fixed via Number.isFinite guard in sanitizeBufferSize() plus the "falls back to the default buffer size when bufferSize is NaN" regression test.
  • nextFrameId not hook-local → fixed via useRef(0) and covered by the "uses hook-local frame ids" test.
  • Same-socket double-mount clobbering send → fixed via the WeakMap-backed sendPatches registry with refcounted recorders, covered by "keeps same-socket send capture isolated across multiple hook instances".

Those Copilot/CodeRabbit threads are stale and can be resolved.

Still outstanding from the maintainer ask — docs site (not just the docs/ bundle):

The maintainer asked for "the readme for the new packages and docs site too" — packages/chat-react/README.md, root README.md, docs/README.md, docs/api-reference.md, and docs/getting-started.md are all covered, but the live Starlight site under apps/docs/src/content/docs/guides/ doesn't have an entry. The sibling @workkit/chat already ships apps/docs/src/content/docs/guides/chat.md. To close the loop:

  • add apps/docs/src/content/docs/guides/chat-react.md (mirror the shape of chat.md), or
  • append a "Client-side debugging" section to the existing chat.md

That's the last thing standing between this PR and merge as far as I can see — code quality reads clean.


Generated by Claude Code

@Chris79OG
Copy link
Copy Markdown
Author

Added the missing docs-site page in commit 20e3b9c.\n\nWhat changed:\n- added apps/docs/src/content/docs/guides/chat-react.md as a generated Starlight guide for @workkit/chat-react\n- covered install, basic hook usage, DebugFrame shape, options, same-socket multi-panel behavior, connection state, and production gating\n\nVerification:\n- bun run --filter @workkit/docs typecheck\n- bun run --filter @workkit/docs build\n- git diff --check\n\nThe docs build now emits /guides/chat-react/index.html.

Copy link
Copy Markdown

@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: 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 `@packages/chat-react/src/index.ts`:
- Around line 167-176: The socket.send override currently calls each
activeRecorder(data) before invoking the underlying send, which can record an
"out" frame even if activePatch.originalSend.call(socket, data) throws; modify
the override in installSendRecorder so it first calls
activePatch.originalSend.call(socket, data) and only after that succeeds (or
after awaiting its result if it can be async) iterate through
activePatch.recorders to invoke them, and ensure any synchronous exception from
originalSend is re-thrown (or propagated) so recorders are not executed on
failure; keep references to sendPatches, activePatch.originalSend, and
activePatch.recorders when making this change.
🪄 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: 63b1b189-e700-4823-960f-8fa0517fd2e9

📥 Commits

Reviewing files that changed from the base of the PR and between f42c5a2 and 20e3b9c.

📒 Files selected for processing (7)
  • README.md
  • apps/docs/src/content/docs/guides/chat-react.md
  • docs/README.md
  • docs/api-reference.md
  • docs/getting-started.md
  • packages/chat-react/src/index.ts
  • packages/chat-react/tests/use-chat-debug-frames.test.ts
✅ Files skipped from review due to trivial changes (4)
  • docs/api-reference.md
  • docs/getting-started.md
  • README.md
  • docs/README.md

Comment thread packages/chat-react/src/index.ts
@Chris79OG
Copy link
Copy Markdown
Author

Addressed the latest review note in commit c185255.

What changed:

  • installSendRecorder now calls the original socket.send first and only records outbound frames after that call succeeds
  • added a regression test proving failed sends are re-thrown and do not create false outbound debug frames

Verification:

  • bun run --filter @workkit/chat-react test (8 passed)
  • bun run --filter @workkit/chat build
  • bun run --filter @workkit/chat-react typecheck
  • bun run --filter @workkit/chat-react build
  • bun x biome check packages/chat-react/src/index.ts packages/chat-react/tests/use-chat-debug-frames.test.ts
  • git diff --check (only existing Windows LF/CRLF warnings)

Copy link
Copy Markdown
Owner

beeeku commented May 25, 2026

Verified c185255 against the latest CodeRabbit ask:

  • installSendRecorder now calls activePatch.originalSend.call(socket, data) first (packages/chat-react/src/index.ts:165) and only iterates activePatch.recorders after the original send returns. A synchronous throw from send propagates without producing a phantom "out" frame.
  • New regression test in packages/chat-react/tests/use-chat-debug-frames.test.ts confirms the throw-path behavior.

Cross-checked all 5 still-unresolved review threads against the latest source — every one is genuinely addressed in the current commit (the threads just never got auto-marked resolved):

Thread Status
toMessage ignores wire timestamp timestampFromWire(wire.timestamp) at :88
TextEncoder allocated per call ✅ module-scoped textEncoder at :39
bufferSize NaN handling Number.isFinite guard in sanitizeBufferSize() at :140
nextFrameId module-scoped useRef(0) at :202
Same-socket double-mount clobbers send sendPatches WeakMap with refcounted recorders at :156-185

Constitution gates: changeset present (.changeset/quiet-frames-chat-react.md), new package wires @workkit/testing, single src/index.ts entry, no cross-package imports outside declared deps, no console.log, TDD evidence in the test file.

Closes #84. Nothing structural left on my end — over to you for the merge call.


Generated by Claude Code

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.

design(chat): useChatDebugFrames hook + optional debug panel for client-side transport inspection

3 participants