Skip to content

feat(ui): boot context pill at session start#313

Merged
dimakis merged 4 commits into
mainfrom
session/2026-05-04-18226da5229e
May 9, 2026
Merged

feat(ui): boot context pill at session start#313
dimakis merged 4 commits into
mainfrom
session/2026-05-04-18226da5229e

Conversation

@dimakis
Copy link
Copy Markdown
Owner

@dimakis dimakis commented May 4, 2026

Summary

  • Threads ContexGin compile() result from server through WebSocket protocol to a tappable pill at the top of the chat view
  • Shows source count, token count, and compilation engine (green dot for ContexGin, amber for local fallback)
  • Tappable to expand: lists source files and trimmed section count
  • Uses dynamic import('contexgin') to avoid bloating module load for tests

Files changed: server/chat.ts, packages/client/src/slices/messages.ts, packages/client/src/protocol-parser.ts, packages/client/src/index.ts, frontend/src/components/BootContextPill.tsx, frontend/src/components/ChatArea.tsx, frontend/src/pages/ChatView.tsx, frontend/src/styles/global.css

Test plan

  • Start Mitzo dev server with ContexGin running — pill shows green dot, accurate counts
  • Stop ContexGin, start new session — pill shows amber dot, "Fallback"
  • Tap pill — expands to show source file list
  • Resumed sessions — pill should not appear (correct, no re-compilation)
  • npx vitest run server/__tests__/chat.test.ts passes (30/30)
  • npx tsc --noEmit clean for modified files

🤖 Generated with Claude Code

Copy link
Copy Markdown
Owner Author

@dimakis dimakis left a comment

Choose a reason for hiding this comment

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

Centaur Review

Found 7 issue(s) (5 warning).

frontend/src/pages/DesktopChatView.tsx

Functional feature with clean UI integration, but DesktopChatView is missing the new prop (boot context won't render on desktop), the contexgin dependency is undeclared, error handling is overly broad, and the existing test suites were not updated.

  • 🟡 regressions: DesktopChatView does not extract or pass bootContext (or sessionContext) to ChatArea. The mobile ChatView passes both props, but the desktop view omits them — boot context metadata will never render on desktop. [fixable]

server/chat.ts

Functional feature with clean UI integration, but DesktopChatView is missing the new prop (boot context won't render on desktop), the contexgin dependency is undeclared, error handling is overly broad, and the existing test suites were not updated.

  • 🟡 unsafe_assumptions (L726): contexgin is dynamically imported but is not listed in any package.json dependencies. If the package is not installed, every session start will hit the .catch() path and emit a misleading local-fallback with all-zero counts, silently masking a missing dependency rather than signaling a configuration problem. [fixable]
  • 🟡 unsafe_assumptions (L728): The compiled object's shape is assumed without validation — compiled.sources, compiled.bootTokens, and compiled.trimmed are accessed directly. If the contexgin API changes or returns an unexpected shape, undefined values will be sent over the wire and silently coerced by the client parser's as casts. [fixable]
  • 🔵 bugs (L738): The .catch() handler swallows all errors (including runtime errors in the .then() callback like compiled.sources.map failing on a non-array) and emits a local-fallback event. Consider narrowing the catch to only handle the dynamic import failure, or at minimum logging the error so real bugs in the success path aren't silently eaten. [fixable]

packages/client/src/protocol-parser.ts

Functional feature with clean UI integration, but DesktopChatView is missing the new prop (boot context won't render on desktop), the contexgin dependency is undeclared, error handling is overly broad, and the existing test suites were not updated.

  • 🟡 unsafe_assumptions (L197): All fields are coerced via as type assertions (msg.source as 'contexgin' | 'local-fallback', msg.sourceCount as number, etc.) with no runtime validation. If any field is missing or has the wrong type, the assertion passes silently and the UI gets undefined/NaN values. Other message types in this file use the same pattern, but new code should ideally validate shape. [fixable]

packages/client/src/slices/messages.ts

Functional feature with clean UI integration, but DesktopChatView is missing the new prop (boot context won't render on desktop), the contexgin dependency is undeclared, error handling is overly broad, and the existing test suites were not updated.

  • 🟡 missing_tests (L390): Existing test files packages/client/__tests__/messages-slice.test.ts and packages/client/__tests__/protocol-parser.test.ts cover the messages reducer and protocol parser respectively, but neither has been updated with tests for the new SET_BOOT_CONTEXT action or the boot_context parser case. [fixable]

frontend/src/components/BootContextPill.tsx

Functional feature with clean UI integration, but DesktopChatView is missing the new prop (boot context won't render on desktop), the contexgin dependency is undeclared, error handling is overly broad, and the existing test suites were not updated.

  • 🔵 style (L12): Hardcoded color strings '#4ade80' and '#fbbf24' bypass the project's CSS variable system (the rest of the codebase uses var(--*) tokens). The trimmed-count yellow in global.css (line 2362) also hardcodes #fbbf24. Consider using CSS variables for consistency and dark/light theme support. [fixable]

Comment thread server/chat.ts Outdated

// Fire-and-forget: capture prompt comparison for the experiments spoke
// Fire-and-forget: emit boot context metadata to client + capture prompt comparison
import('contexgin')
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 unsafe_assumptions: contexgin is dynamically imported but is not listed in any package.json dependencies. If the package is not installed, every session start will hit the .catch() path and emit a misleading local-fallback with all-zero counts, silently masking a missing dependency rather than signaling a configuration problem. [fixable]

Comment thread server/chat.ts Outdated
// Fire-and-forget: emit boot context metadata to client + capture prompt comparison
import('contexgin')
.then(({ compile }) => compile({ workspaceRoot: cwd, tokenBudget: 8000 }))
.then((compiled) => {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 unsafe_assumptions: The compiled object's shape is assumed without validation — compiled.sources, compiled.bootTokens, and compiled.trimmed are accessed directly. If the contexgin API changes or returns an unexpected shape, undefined values will be sent over the wire and silently coerced by the client parser's as casts. [fixable]

Comment thread server/chat.ts Outdated
sources: compiled.sources.map((s: { relativePath: string }) => s.relativePath),
});
})
.catch(() => {
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 bugs: The .catch() handler swallows all errors (including runtime errors in the .then() callback like compiled.sources.map failing on a non-array) and emits a local-fallback event. Consider narrowing the catch to only handle the dynamic import failure, or at minimum logging the error so real bugs in the success path aren't silently eaten. [fixable]

Comment thread packages/client/src/protocol-parser.ts Outdated
});
break;

case 'boot_context':
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 unsafe_assumptions: All fields are coerced via as type assertions (msg.source as 'contexgin' | 'local-fallback', msg.sourceCount as number, etc.) with no runtime validation. If any field is missing or has the wrong type, the assertion passes silently and the UI gets undefined/NaN values. Other message types in this file use the same pattern, but new code should ideally validate shape. [fixable]

case 'SET_SESSION_CONTEXT':
return { ...state, sessionContext: action.context };

case 'SET_BOOT_CONTEXT':
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 missing_tests: Existing test files packages/client/__tests__/messages-slice.test.ts and packages/client/__tests__/protocol-parser.test.ts cover the messages reducer and protocol parser respectively, but neither has been updated with tests for the new SET_BOOT_CONTEXT action or the boot_context parser case. [fixable]

const [expanded, setExpanded] = useState(false);

const isContexgin = context.source === 'contexgin';
const dotColor = isContexgin ? '#4ade80' : '#fbbf24';
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 style: Hardcoded color strings '#4ade80' and '#fbbf24' bypass the project's CSS variable system (the rest of the codebase uses var(--*) tokens). The trimmed-count yellow in global.css (line 2362) also hardcodes #fbbf24. Consider using CSS variables for consistency and dark/light theme support. [fixable]

dimakis added a commit that referenced this pull request May 4, 2026
…tests

- Wire bootContext + sessionContext to DesktopChatView (was missing)
- Server: async/await with try/catch, validate compiled shape, log errors
- Protocol parser: runtime validation instead of bare `as` casts
- CSS: define --status-ok/--status-warn vars, use class modifiers
- Add 6 tests: SET_BOOT_CONTEXT reducer (3) + boot_context parser (3)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Owner Author

@dimakis dimakis left a comment

Choose a reason for hiding this comment

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

Centaur Review

Found 5 issue(s) (2 warning).

frontend/src/components/ChatArea.tsx

Clean feature addition with solid test coverage on the state/protocol layers. Main concern is a minor UI bug where the empty-state placeholder shows alongside the boot context pill, plus a defensive-coding gap around the sources array type assertion.

  • 🟡 bugs (L158): Empty-state condition checks !sessionContext but not !bootContext. When boot context is present (the pill is rendered) but there is no session context, the "Send a message to start" placeholder still shows beneath the pill. Should include && !bootContext to suppress the empty state when the pill is visible. [fixable]

packages/client/src/protocol-parser.ts

Clean feature addition with solid test coverage on the state/protocol layers. Main concern is a minor UI bug where the empty-state placeholder shows alongside the boot context pill, plus a defensive-coding gap around the sources array type assertion.

  • 🟡 unsafe_assumptions (L200): msg.sources as string[] is a type assertion without element-level validation. If the server sends non-string elements in the array (e.g. objects, numbers), they pass through unchecked and will render as [object Object] in the pill. Consider (msg.sources as unknown[]).filter((s): s is string => typeof s === 'string') or .map(String). [fixable]

frontend/src/components/BootContextPill.tsx

Clean feature addition with solid test coverage on the state/protocol layers. Main concern is a minor UI bug where the empty-state placeholder shows alongside the boot context pill, plus a defensive-coding gap around the sources array type assertion.

  • 🔵 bugs (L35): key={src} uses the source string as the React key. If contexgin ever returns duplicate relativePath entries (e.g. from overlapping config), React will warn about duplicate keys. A safe alternative is key={idx} or key={${idx}-${src}} since this list is static per render. [fixable]

server/chat.ts

Clean feature addition with solid test coverage on the state/protocol layers. Main concern is a minor UI bug where the empty-state placeholder shows alongside the boot context pill, plus a defensive-coding gap around the sources array type assertion.

  • 🔵 style (L738): sources.map((s: { relativePath: string }) => s.relativePath) — the inline type annotation for s is effectively unchecked at runtime. If a source element lacks relativePath, this produces undefined in the array, which reaches the client as a stringified "undefined". Consider defensive access: s?.relativePath ?? s?.path ?? '<unknown>'. [fixable]

packages/client/__tests__/protocol-parser.test.ts

Clean feature addition with solid test coverage on the state/protocol layers. Main concern is a minor UI bug where the empty-state placeholder shows alongside the boot context pill, plus a defensive-coding gap around the sources array type assertion.

  • 🔵 missing_tests (L745): Tests cover missing numeric fields and unknown source, but no test verifies behavior when sources contains non-string elements (e.g. [42, null, {path: 'x'}]). This is the edge the as string[] assertion skips. [fixable]

Comment thread frontend/src/components/ChatArea.tsx Outdated
{bootContext && <BootContextPill context={bootContext} />}
{sessionContext && <ContextBlock content={sessionContext} />}

{messages.length === 0 && !current && !running && !sessionContext && (
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 bugs: Empty-state condition checks !sessionContext but not !bootContext. When boot context is present (the pill is rendered) but there is no session context, the "Send a message to start" placeholder still shows beneath the pill. Should include && !bootContext to suppress the empty state when the pill is visible. [fixable]

case 'boot_context': {
const source = msg.source === 'contexgin' ? 'contexgin' : 'local-fallback';
const sourceCount = typeof msg.sourceCount === 'number' ? msg.sourceCount : 0;
const tokenCount = typeof msg.tokenCount === 'number' ? msg.tokenCount : 0;
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🟡 unsafe_assumptions: msg.sources as string[] is a type assertion without element-level validation. If the server sends non-string elements in the array (e.g. objects, numbers), they pass through unchecked and will render as [object Object] in the pill. Consider (msg.sources as unknown[]).filter((s): s is string => typeof s === 'string') or .map(String). [fixable]

{expanded && (
<div className="boot-context-pill-content">
{context.sources.map((src) => (
<div key={src} className="boot-context-pill-source">
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 bugs: key={src} uses the source string as the React key. If contexgin ever returns duplicate relativePath entries (e.g. from overlapping config), React will warn about duplicate keys. A safe alternative is key={idx} or key={${idx}-${src}} since this list is static per render. [fixable]

Comment thread server/chat.ts Outdated
sourceCount: sources.length,
tokenCount: typeof compiled?.bootTokens === 'number' ? compiled.bootTokens : 0,
trimmedCount: trimmed.length,
sources: sources.map((s: { relativePath: string }) => s.relativePath),
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 style: sources.map((s: { relativePath: string }) => s.relativePath) — the inline type annotation for s is effectively unchecked at runtime. If a source element lacks relativePath, this produces undefined in the array, which reaches the client as a stringified "undefined". Consider defensive access: s?.relativePath ?? s?.path ?? '<unknown>'. [fixable]

sourceCount: 0,
tokenCount: 0,
trimmedCount: 0,
sources: [],
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

🔵 missing_tests: Tests cover missing numeric fields and unknown source, but no test verifies behavior when sources contains non-string elements (e.g. [42, null, {path: 'x'}]). This is the edge the as string[] assertion skips. [fixable]

dimakis and others added 3 commits May 9, 2026 14:43
…session start

Threads ContexGin compile result from server through protocol to a tappable
pill at the top of the chat. Shows source count, token count, and compilation
engine (green dot for ContexGin, amber for local fallback). Expands to list
source files and trimmed section count.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tests

- Wire bootContext + sessionContext to DesktopChatView (was missing)
- Server: async/await with try/catch, validate compiled shape, log errors
- Protocol parser: runtime validation instead of bare `as` casts
- CSS: define --status-ok/--status-warn vars, use class modifiers
- Add 6 tests: SET_BOOT_CONTEXT reducer (3) + boot_context parser (3)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Separate import vs compilation try/catch in server boot context IIFE
- Add runtime shape validation for contexgin compile() return value
- Validate individual source entries before sending over WS
- Filter non-string elements from sources array in protocol parser
- Fix empty-state overlap when bootContext is present
- Use index-based React keys for source list items
- Add tests for sources validation edge cases (non-string, non-array)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dimakis dimakis force-pushed the session/2026-05-04-18226da5229e branch from 9d08b5c to db96539 Compare May 9, 2026 13:43
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@dimakis dimakis merged commit 9f35d32 into main May 9, 2026
1 check passed
@dimakis dimakis deleted the session/2026-05-04-18226da5229e branch May 9, 2026 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant