Skip to content

feat(cowork-chat): chat-ui parity with gitnexus-rs (5 gaps)#40

Draft
phuetz wants to merge 5 commits into
mainfrom
feat/cowork-chat-ui-polish
Draft

feat(cowork-chat): chat-ui parity with gitnexus-rs (5 gaps)#40
phuetz wants to merge 5 commits into
mainfrom
feat/cowork-chat-ui-polish

Conversation

@phuetz
Copy link
Copy Markdown
Owner

@phuetz phuetz commented May 14, 2026

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

# Commit Gap Effort
1 19742e02 Regenerate button au hover des messages assistant ~50 LOC + 12 tests
2 3ddfef98 Textarea auto-grow (44→200 px) ~70 LOC + 5 tests
3 bc8c121a HealthBadge permanent dans Titlebar (poll /health) ~320 LOC + 4 tests
4 cf4cb429 Tool badges inline au head du message assistant + scroll-to ToolUseBlock ~240 LOC + 10 tests
5 0f7b4f58 Mermaid inline render (lazy import + DOMPurify) ~115 LOC

Total : ~795 LOC nouveau (dont 31 tests), 6 nouveaux fichiers, 5 fichiers existants modifiés.

Composants nouveaux

  • src/renderer/hooks/use-regenerate.ts
  • src/renderer/hooks/use-textarea-autogrow.ts
  • src/renderer/hooks/use-backend-status.ts
  • src/renderer/components/HealthBadge.tsx
  • src/renderer/components/message/ToolBadgeStrip.tsx
  • src/renderer/components/message/MermaidBlock.tsx

Helpers purs (testables en node env)

  • src/renderer/utils/regenerate-helpers.tsfindPrecedingUserIndex, computeRegenerationPlan
  • src/renderer/utils/tool-status.tsresolveToolStatus, compactToolLabel
  • src/renderer/hooks/use-textarea-autogrow.tscomputeAutogrowHeight (exporté à part)
  • src/renderer/hooks/use-backend-status.tscomputeNextPollDelay (exporté à part)

Patterns volés au chat-ui gitnexus-rs

  • Lazy import('mermaid') + DOMPurify strict SVG profile (cf. chat-ui/src/components/MermaidBlock.tsx)
  • useBackendStatus chained-setTimeout (pas de overlap) + backoff exponentiel sur failure (10s → 60s cap)
  • Textarea auto-grow via scrollHeight reset-to-auto-then-clamp (cf. chat-ui/src/components/ChatInput.tsx)
  • Regenerate via slice-and-replay : trim store depuis userIndex puis continueSession() (cf. chat-ui/src/hooks/use-chat.ts:41-56)
  • Tool badges status taxonomy : amber=running animate-spin, success=done, error=fail (cf. chat-ui ToolCallBadge)

Test plan

  • npm test — 1079/1079 ✓ (170 fichiers, zéro régression)
  • npm run typecheck — clean
  • npm run build:e2e — vite build OK, mermaid chunké à part (dist/assets/mermaid.core-*.js)
  • Smoke test manuel dans Cowork live :
    • Regenerate : click sur le bouton hover d'un msg assistant → user msg renvoyé, regen démarre
    • Auto-grow : coller un texte multi-line dans l'input → textarea grandit jusqu'à 200 px puis scroll interne
    • Health badge : kill le backend → badge passe rouge dans 10 s, click → popup avec lastError
    • Tool badges : faire une question qui invoque des tools → strip visible au top du message, click → scroll vers ToolUseBlock + flash highlight
    • Mermaid : demander au LLM de produire ```mermaid graph TD; A→B; → diagramme rendu inline (pas dans ArtifactPanel)

Notes

  • MermaidBlock sans tests automatiques : composant DOM-bound + lazy import() + DOMPurify, pas testable proprement en node vitest env sans gros mocking. Validation par smoke test (point ci-dessus).
  • HealthBadge non testé côté composant : seul computeNextPollDelay est unit-testé. Le hook useBackendStatus est DOM-bound (poll, fetch, electronAPI). Idem.
  • Lint script du repo cassé (eslint --ext flag déprécié, mismatch eslint 8/9 config) : pas modifié dans cette PR, c'était cassé avant.
  • npm run build complet échoue sur le step prepare:python:all (GitHub API rate-limit 504 sur python-build-standalone). Indépendant du code de cette PR. npm run build:e2e passe (vite build seul).
  • Bundle impact : + mermaid@11 (~500 KB) — chunké lazy, pas dans le bundle initial. + dompurify@3 (~30 KB).

Branche

Partie de origin/main (commit 3ceac032), pas de la branche main locale 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

phuetz and others added 5 commits May 14, 2026 11:59
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>
@phuetz phuetz force-pushed the feat/cowork-chat-ui-polish branch from 0f7b4f5 to 49f4156 Compare May 14, 2026 09:59
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