feat(cowork-chat): chat-ui parity with gitnexus-rs (5 gaps)#40
Draft
phuetz wants to merge 5 commits into
Draft
Conversation
Adds a hover-only Regenerate icon to assistant messages in MessageCard, mirroring the chat-ui gitnexus-rs UX. Walks back to the user message that produced the assistant turn, trims the store from there, and replays via the existing continueSession() IPC. The slice/replay logic is extracted to utils/regenerate-helpers.ts as a pure function (`computeRegenerationPlan`) so it stays testable in the node vitest env. The hook (use-regenerate.ts) is just the React glue — store read + setMessages + continueSession dispatch. Also bumps package.json with mermaid@11 + dompurify@3 deps for the upcoming Phase 3 (Mermaid inline render). They're not used yet but batched here to avoid a separate `chore(deps)` commit. Files: - cowork/src/renderer/utils/regenerate-helpers.ts (new, ~50 LOC) - cowork/src/renderer/hooks/use-regenerate.ts (new, ~40 LOC) - cowork/src/renderer/components/MessageCard.tsx (+RegenerateAction sub-component) - cowork/tests/regenerate-helpers.test.ts (new, 12 tests) - cowork/package.json (+mermaid@11 +dompurify@3) Tests: 12/12 new green. typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the fixed `rows={1}` ChatView textarea with a self-resizing
one that grows between 44 and 200 px as content (typed or pasted)
exceeds a single row. Beyond 200 px the textarea stops growing and an
internal vertical scrollbar takes over. Mirrors the chat-ui gitnexus-rs
ChatInput pattern.
Implementation:
- New hook useTextareaAutogrow(ref, value, opts?) — useLayoutEffect
that resets `style.height = 'auto'` to recompute scrollHeight, then
clamps via the pure helper computeAutogrowHeight() and sets
style.overflowY based on whether we hit the cap.
- Pure helper computeAutogrowHeight() lives in the same module so it
stays unit-testable in the node vitest env.
- ChatView wires the hook with the existing `prompt` state +
textareaRef. Initial inline `style.minHeight: 44` keeps the textarea
at a comfortable size before the first effect runs.
- Existing keyboard handlers (Shift+Enter newline, Enter send,
composition guard, slash/mention triggers) untouched.
Files:
- cowork/src/renderer/hooks/use-textarea-autogrow.ts (new, ~65 LOC)
- cowork/src/renderer/components/ChatView.tsx (+import, +useTextareaAutogrow call, +style.minHeight)
- cowork/tests/textarea-autogrow.test.ts (new, 5 tests on the pure helper)
Tests: 5/5 new green. typecheck clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a permanent backend status badge to the titlebar (right of TabBar, before the orchestrator/fleet/team buttons) so users see at a glance whether the Code Buddy backend is reachable. Mirrors the chat-ui gitnexus-rs `BackendStatus` UX. Auto-polls /api/health every 10s with chained-setTimeout (no overlap), exponential backoff on consecutive failures (10s → 20s → 40s → 60s ceiling) so failed backends don't spam logs. Reads endpoint + apiKey once at mount from the existing CodeBuddy config slice; if disabled the badge hides entirely. Click opens a small popup with endpoint, status, version, last success time and last error. ESC + click-outside close it. The poll loop is a hook (useBackendStatus) — independent of SettingsCodeBuddy's own on-demand testConnection logic, which stays unchanged. Future refactor can DRY them; not the scope here. Files: - cowork/src/renderer/hooks/use-backend-status.ts (new, ~165 LOC) - cowork/src/renderer/components/HealthBadge.tsx (new, ~155 LOC) - cowork/src/renderer/components/Titlebar.tsx (+import +<HealthBadge/> mount) - cowork/tests/backend-status.test.ts (new, 4 tests on the pure backoff helper) Tests: 4/4 new green. typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a compact horizontal strip of tool-call badges at the head of
each assistant message — at-a-glance visibility into what tools the
agent invoked, with status color coding (warning=running animate-spin,
success=done, error=fail). Mirrors the chat-ui gitnexus-rs
ToolCallBadge pattern.
Each badge is a scroll-to anchor on the existing ToolUseBlock card
further down (which already has full input/output detail). Click
flashes the target card with a brief ring highlight to draw the eye.
The detailed ToolUseBlock cards are NOT removed — the strip is a
top-of-message summary, the cards are the source of truth for
expanded detail. This keeps the existing UX intact while adding
the chat-ui parity affordance.
The status-resolution + label-compaction logic was extracted from
ToolUseBlock into a pure helper (utils/tool-status.ts) so the strip
can render consistent state without duplicating the lookup. ToolUseBlock
itself is unchanged behavior-wise; it just got an `id="tool-{block.id}"`
anchor for the scroll-to.
Files:
- cowork/src/renderer/utils/tool-status.ts (new, ~95 LOC)
- cowork/src/renderer/components/message/ToolBadgeStrip.tsx (new, ~140 LOC)
- cowork/src/renderer/components/message/ToolUseBlock.tsx (+id anchor)
- cowork/src/renderer/components/MessageCard.tsx (+import +<ToolBadgeStrip/> mount)
- cowork/tests/tool-status.test.ts (new, 10 tests on resolveToolStatus + compactToolLabel)
Tests: 10/10 new green. typecheck clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds inline Mermaid diagram rendering inside the assistant message
flow. Mirrors the chat-ui gitnexus-rs MermaidBlock pattern: lazy
import('mermaid') the first time a diagram is encountered (saves
~500 KB from the initial bundle), render to SVG, DOMPurify with
strict SVG profile (defense-in-depth on top of mermaid's own
securityLevel:'strict'), then dangerouslySetInnerHTML.
The existing ArtifactPanel Mermaid (CDN-loaded) keeps working — it's
the "Open in panel" affordance for zoom/export. This new component
renders preview-in-flow so users see the diagram immediately without
opening a separate panel.
ContentBlockView's `code` handler routes language === 'mermaid' to
MermaidBlock instead of CodeBlock; everything else routes to
CodeBlock as before.
Render results are cached module-scope by source text — re-renders
of the same diagram (re-mount, scroll-back-into-view) are free.
On render failure, the source text is shown in a `<pre>` block so
the user always sees what was attempted.
Files:
- cowork/src/renderer/components/message/MermaidBlock.tsx (new, ~115 LOC)
- cowork/src/renderer/components/message/ContentBlockView.tsx (+import +mermaid branch in code handler)
No automated tests this phase — MermaidBlock is heavy DOM + lazy
dynamic import + DOMPurify, not testable in the node vitest env
without significant mocking effort. Validation is a smoke test in
the running Cowork app (Phase 4).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0f7b4f5 to
49f4156
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Comble 5 gaps UX entre le chat de Cowork (Electron) et le chat-ui de gitnexus-rs (l'app React que Patrice a developée from scratch comme référence UX maison). Aucune modif côté gitnexus-rs — la parité se fait du côté Cowork.
5 commits indépendants, scope ciblé UX/UI, zéro régression sur la suite Cowork (1079 tests), 31 nouveaux tests sur les helpers extraits purs (les composants React DOM-bound ne sont pas testables en node vitest env, validation = smoke test manuel).
Commits
19742e023ddfef98bc8c121acf4cb4290f7b4f58Total : ~795 LOC nouveau (dont 31 tests), 6 nouveaux fichiers, 5 fichiers existants modifiés.
Composants nouveaux
src/renderer/hooks/use-regenerate.tssrc/renderer/hooks/use-textarea-autogrow.tssrc/renderer/hooks/use-backend-status.tssrc/renderer/components/HealthBadge.tsxsrc/renderer/components/message/ToolBadgeStrip.tsxsrc/renderer/components/message/MermaidBlock.tsxHelpers purs (testables en node env)
src/renderer/utils/regenerate-helpers.ts—findPrecedingUserIndex,computeRegenerationPlansrc/renderer/utils/tool-status.ts—resolveToolStatus,compactToolLabelsrc/renderer/hooks/use-textarea-autogrow.ts—computeAutogrowHeight(exporté à part)src/renderer/hooks/use-backend-status.ts—computeNextPollDelay(exporté à part)Patterns volés au chat-ui gitnexus-rs
import('mermaid')+ DOMPurify strict SVG profile (cf.chat-ui/src/components/MermaidBlock.tsx)useBackendStatuschained-setTimeout (pas de overlap) + backoff exponentiel sur failure (10s → 60s cap)scrollHeightreset-to-auto-then-clamp (cf.chat-ui/src/components/ChatInput.tsx)userIndexpuiscontinueSession()(cf.chat-ui/src/hooks/use-chat.ts:41-56)chat-ui ToolCallBadge)Test plan
npm test— 1079/1079 ✓ (170 fichiers, zéro régression)npm run typecheck— cleannpm run build:e2e— vite build OK, mermaid chunké à part (dist/assets/mermaid.core-*.js)```mermaid graph TD; A→B;→ diagramme rendu inline (pas dans ArtifactPanel)Notes
MermaidBlocksans tests automatiques : composant DOM-bound + lazyimport()+ DOMPurify, pas testable proprement en node vitest env sans gros mocking. Validation par smoke test (point ci-dessus).HealthBadgenon testé côté composant : seulcomputeNextPollDelayest unit-testé. Le hookuseBackendStatusest DOM-bound (poll, fetch, electronAPI). Idem.eslint --extflag déprécié, mismatch eslint 8/9 config) : pas modifié dans cette PR, c'était cassé avant.npm run buildcomplet échoue sur le stepprepare:python:all(GitHub API rate-limit 504 sur python-build-standalone). Indépendant du code de cette PR.npm run build:e2epasse (vite build seul).+ mermaid@11(~500 KB) — chunké lazy, pas dans le bundle initial.+ dompurify@3(~30 KB).Branche
Partie de
origin/main(commit3ceac032), pas de la branchemainlocale qui contient deux commits Phase d.23 non pushés (f8a83f5a+160826b5). Cette PR est totalement indépendante de Phase d.23.🤖 Generated with Claude Code