Skip to content

Refactor: Infrastructure cleanup, shared components, and theme synchronization - #75

Merged
ddutchie merged 5 commits into
mainfrom
ddutchie/cleanupagain
Jul 2, 2026
Merged

ddutchie merged 5 commits into
mainfrom
ddutchie/cleanupagain

Conversation

@ddutchie

@ddutchie ddutchie commented Jul 2, 2026 •

Copy link
Copy Markdown
Owner

What does this PR do?

This PR is a major maintenance release focused on architectural cleanup, code deduplication, and UX polish. Key improvements include consolidating shared UI primitives (hooks and components), unifying theme synchronization across visual components, and modularizing oversized files to improve maintainability.

  • Theme Synchronization: Fixed a bug where code blocks, diff views, and the git panel remained stuck on initial theme load. These views now correctly re-paint instantly upon light/dark theme switches.
  • Component Consolidation: Introduced centralized shared primitives (e.g., useIsDark, useCopyToClipboard, NodeTypeChip, and a unified Toggle switch) to replace ad-hoc logic across the app.
  • Refactoring: Unified the card detail view implementations, split large modules (like the kanban archive view and note PDF export logic) into focused files, and extracted shared syntax-highlighting palettes.
  • UI Consistency: Aligned the "medium" priority badge color to blue (--info) across all surfaces (flow nodes, modals, cards) for a consistent design language.

Type of change

  • Bug fix
  • New feature
  • Refactor / code quality
  • Docs / changelog
  • Tests

Checklist

  • npm run type-check:all passes
  • npm run lint passes
  • npm test passes
  • npm run test:e2e passes
  • No hardcoded colours — CSS variables only
  • No text-[Npx] pixel font classes — rem equivalents only
  • New IPC handlers wrapped in handle() and return IpcResult<T>
  • New DB migrations appended (not edited) in schema.ts
  • New SQL goes in electron/db/queries.ts
  • New dependencies or devDependencies added to ROLE_MAP in scripts/generate-licenses.js and licenses.json regenerated
  • New MCP tools registered in electron/mcp/tools/index.ts dispatch + electron/lib/tool-schemas.ts Zod schema
  • External flags updated in compile script and electron/bundle-guard.test.ts (if applicable)

Notes for reviewer

The architecture review documentation (docs/architecture-review.md) has been updated to reflect the completion of Waves 1, 2, and 3. Most changes are internal structural cleanups with no intended change to visual output, except for the explicit theme-sync fix and color-alignment.

Summary by CodeRabbit

  • New Features

    • Improved navigation from search, previews, notes, cards, and project views so opening an item now lands in the right place more consistently.
    • Added a clearer archived-tasks view with search, restore, delete, and bulk actions.
    • Card details are now shown through a shared, more consistent editor experience.
  • Bug Fixes

    • Theme changes now update code, diff, and graph visuals immediately.
    • Syntax highlighting and PDF exports now use more consistent colors.
    • Several copy-to-clipboard actions now behave more reliably.

ddutchie added 3 commits July 1, 2026 17:33
…1–2)

Consolidates duplicated cross-cutting idioms surfaced in the architecture
review (docs/architecture-review.md).

Wave 1 — shared hooks/helpers:
- getIsDark()/useIsDark() + useThemeRepaint(); migrate 7 data-theme reads
  and 2 canvas observer blocks (fixes stale-theme on live switch)
- useCopyToClipboard(); migrate ~7 copy-with-reset sites
- navigateAndReveal()/revealNote/Card/Column; migrate 13 setView+dispatch
  sites and fix no-timeout race variants
- move withAlpha() into analyticsUtils; unify priority/status colour maps
  (align Tailwind medium hue to --info)

Wave 2 — component extractions:
- merge card-detail + card-detail-panel into CardDetailBody
- single syntax palette (lib/syntax-palette.ts) shared by CodeBlock,
  editor-theme, dashboard-view, PDF export (byte-identical output)
- ui/toggle.tsx primitive; NodeTypeChip shared component
- replace inline avatars/cursors with MessageAvatar/StreamingCursor

type-check:all + 836 tests + compile all green.
- store: extract restorePersistedTheme/restorePersistedUiPrefs shared by
  both hydration paths (removes ~40 duplicated lines)
- notes: extract PDF-export HTML prep into note-pdf-export.ts
- notes: extract shared ReactMarkdown pre→CodeBlock/Mermaid handler into
  markdown-code-fence.ts; used by note-editor, NoteMarkdownPreview, chat
- kanban: extract inline archive view into ArchiveView (board.tsx 657→549)

Slice split of terminal-sessions.ts skipped (high risk, no user benefit)
and GitView decomposition deferred. type-check:all + 836 tests + compile
all green.
…ave 3.5)

- §2.8 move sessionUtils.formatDate → lib/utils formatDateCompact; delete sessionUtils.ts
- §3.5 extract persistPiTranscript() shared by AgentChatPane onDone/onError
- add changelogs/v2.3.6.md; log wave 3.5 in architecture-review.md
@coderabbitai

coderabbitai Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ddutchie, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 28 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e43adb5-d664-4022-93b6-750efe390283

📥 Commits

Reviewing files that changed from the base of the PR and between a0897cc and f2658bb.

📒 Files selected for processing (16)
  • docs/architecture-review.md
  • src/components/agent/AgentChatPane.tsx
  • src/components/chat/chat-panel/ChatMessageBubble.tsx
  • src/components/chat/chat-panel/MarkdownContent.tsx
  • src/components/graph/NodeTypeChip.tsx
  • src/components/kanban/archive-view.tsx
  • src/components/kanban/board.tsx
  • src/components/kanban/card-detail-body.tsx
  • src/components/notes/CodeBlock.tsx
  • src/components/notes/MermaidDiagram.tsx
  • src/components/notes/note-pdf-export.ts
  • src/components/settings/ViewVisibilitySettings.tsx
  • src/components/settings/shared.tsx
  • src/hooks/useIsDark.ts
  • src/lib/events.ts
  • src/lib/syntax-palette.ts
📝 Walkthrough

Walkthrough

This PR introduces shared UI primitives (useIsDark, useCopyToClipboard, navigation reveal helpers, Toggle, NodeTypeChip, syntax color palette), refactors numerous components to consume them, unifies card-detail rendering, extracts archive-view/PDF-export/markdown-fence logic into dedicated modules, consolidates store hydration, and adds a changelog plus architecture review document.

Changes

Shared primitives extraction and consumer refactors

Layer / File(s) Summary
Theme detection hook
src/hooks/useIsDark.ts, src/lib/utils.ts, src/components/agent/{DiffViewer,GitView,AgentEditor}.tsx, src/components/notes/{CodeBlock,MermaidDiagram,dashboard-view}.tsx
Adds useIsDark/getIsDark and replaces manual document.documentElement theme checks across editor, diff, git, and notes components.
Copy-to-clipboard hook
src/hooks/useCopyToClipboard.ts, src/components/agent/DiffViewer.tsx, src/components/notes/{CodeBlock,DashboardApiModal}.tsx, src/components/chat/chat-panel/ChatMessageBubble.tsx, src/components/onboarding/StepMCP.tsx, src/components/settings/{AgentSettings,MCPSettings}.tsx
Adds useCopyToClipboard and refactors multiple copy buttons off manual navigator.clipboard/setTimeout state.
Navigation reveal helpers
src/lib/events.ts, src/components/agent/AgentChatPane.tsx, src/components/chat/{PreviewPane,chat-panel/MarkdownContent}.tsx, src/components/flow/nodes/*.tsx, src/components/layout/project-overview/*, src/components/search/search-panel.tsx, src/components/shared/cairn-ref-chip.tsx
Adds navigateAndReveal/revealNote/revealCard/revealColumn and replaces CairnEvents + setTimeout dispatch patterns.
Transcript persistence
src/components/agent/AgentChatPane.tsx
Adds persistPiTranscript helper used by both onDone/onError IPC handlers.
Shared message UI
src/components/chat/chat-panel/{QuestionForm,ThinkingPanel,ToolCallIndicator}.tsx
Replaces inline bot avatar/streaming pulse markup with MessageAvatar/StreamingCursor.
Syntax color palette
src/lib/syntax-palette.ts, src/components/notes/CodeBlock.tsx, src/lib/editor-theme.ts, src/components/notes/dashboard-view.tsx
Adds SYNTAX_COLORS/DARK_TO_LIGHT and derives highlight styles from the shared palette.
Markdown code-fence renderer
src/components/notes/markdown-code-fence.tsx, src/components/notes/{NoteMarkdownPreview,note-editor}.tsx, src/components/chat/chat-panel/MarkdownContent.tsx
Adds renderCodeFence and consolidates fenced-code rendering; also re-themes chat bubble colors.
PDF export helpers
src/components/notes/note-pdf-export.ts, src/components/notes/note-editor.tsx
Adds prepareNoteHtmlForPdf/pdfSafeTitle and wires them into PDF export flow.
Compact date formatting
src/lib/utils.ts, src/components/agent/sessionUtils.ts, src/components/agent/{AgentEmptyState,AgentSessionTab}.tsx
Adds formatDateCompact, removes formatDate, and updates session timestamp rendering.
Toggle primitive
src/components/ui/toggle.tsx, src/components/settings/{shared,ViewVisibilitySettings}.tsx
Adds a controlled Toggle component and replaces local switch implementations.
NodeTypeChip and graph refactors
src/components/graph/NodeTypeChip.tsx, src/components/graph/{analyticsHooks,analyticsUtils,ForceGraphCanvas,RadialTreeCanvas,KnowledgeGraphView,TableCanvas}.tsx, src/components/insights/InsightsView.tsx, src/lib/constants.ts
Adds NodeTypeChip, shared withAlpha, useThemeRepaint, and consolidates priority/status color constants.
Card detail unification
src/components/kanban/{card-detail-body,card-detail,card-detail-panel}.tsx
Adds CardDetailBody and reduces modal/panel to thin wrappers.
Archive view extraction
src/components/kanban/{archive-view,board}.tsx
Adds ArchiveView component and replaces inline archive UI in the board.
Store hydration consolidation
src/store/index.ts
Adds restorePersistedTheme/restorePersistedUiPrefs helpers used in hydrate and hydrateFromElectron.

Documentation

Layer / File(s) Summary
Changelog and architecture review
changelogs/v2.3.6.md, docs/architecture-review.md
Adds release notes and a comprehensive architecture review document.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UI
  participant revealNote
  participant setView
  participant window
  UI->>revealNote: click note/card reference
  revealNote->>setView: setView("notes"/"board")
  revealNote->>window: dispatchEvent CairnEvents (after REVEAL_DELAY_MS)
  window-->>UI: target view selects/scrolls to item
Loading
sequenceDiagram
  participant Component
  participant useCopyToClipboard
  participant Clipboard
  Component->>useCopyToClipboard: copy(text, key?)
  useCopyToClipboard->>Clipboard: navigator.clipboard.writeText(text)
  Clipboard-->>useCopyToClipboard: success/failure
  useCopyToClipboard-->>Component: copied=true, auto-reset after resetMs
Loading

Possibly related PRs

  • ddutchie/cairn#53: Both PRs touch src/components/shared/cairn-ref-chip.tsx and its reveal/view-selection navigation logic.
  • ddutchie/cairn#62: Both PRs modify src/store/index.ts hydration/restore logic tied to persisted notesSidebarWidth.
  • ddutchie/cairn#69: Both PRs touch the chat PreviewPane "Go to Notes/Board" navigation flow.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the maintenance refactor, shared components, and theme-sync work in the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ddutchie/cleanupagain

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

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

⚠️ Outside diff range comments (1)
src/components/settings/ViewVisibilitySettings.tsx (1)

42-55: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Duplicate accessible name for the two "always visible" switches.

label="Always visible" is hardcoded, so both the "Overview" and "Notes" switches get the identical aria-label. Screen reader users can't tell them apart. The per-row label variable (used correctly below at Line 73 for the toggleable rows) should be interpolated here too.

🛠️ Proposed fix
-            <Toggle checked disabled onCheckedChange={() => {}} label="Always visible" />
+            <Toggle checked disabled onCheckedChange={() => {}} label={`${label} - Always visible`} />
🤖 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 `@src/components/settings/ViewVisibilitySettings.tsx` around lines 42 - 55, The
Toggle in ViewVisibilitySettings is using a hardcoded accessible name, so both
rows announce the same label. Update the Toggle inside the ["Overview", "Notes"]
map to include the per-row label variable in its accessible label, matching the
pattern already used by the toggleable rows elsewhere in the component, so each
switch is uniquely identified by its row name.
🧹 Nitpick comments (6)
src/components/settings/shared.tsx (1)

6-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unify the Toggle entry point This wrapper only forwards checked/onChange/label/id, while ViewVisibilitySettings.tsx imports @/components/ui/toggle directly for disabled. Re-export the canonical Toggle here or forward the full ToggleProps surface so settings code has one import shape.

🤖 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 `@src/components/settings/shared.tsx` at line 6, The shared Toggle wrapper
currently exposes only a narrow prop subset, while other settings code imports
the UI Toggle directly for additional props like disabled, creating two
inconsistent entry points. Update the shared toggle export in shared.tsx so it
re-exports the canonical Toggle or forwards the full ToggleProps surface, and
ensure ViewVisibilitySettings and similar settings components can rely on the
shared import path for all supported props.
src/components/agent/AgentChatPane.tsx (1)

63-81: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Solid consolidation of the duplicated save logic.

Reads fresh state via getState() (safe for IPC callbacks) and correctly filters out in-flight streaming messages before persisting. One small nit: the .catch(console.error) doesn't include sessionId in the log, making it harder to correlate failures across concurrent sessions in the console/log aggregator.

🔍 Optional: add context to the error log
-  window.electron?.piAgent.saveMessages(sessionId, saveable).catch(console.error);
+  window.electron?.piAgent.saveMessages(sessionId, saveable).catch((err) =>
+    console.error(`[AgentChatPane] failed to persist transcript for session ${sessionId}:`, err)
+  );
🤖 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 `@src/components/agent/AgentChatPane.tsx` around lines 63 - 81, The save
failure log in persistPiTranscript lacks session context, which makes concurrent
transcript persistence errors hard to trace. Update the .catch(console.error)
handling in persistPiTranscript so the failure log includes the sessionId
alongside the error, using the same saveMessages call path and preserving the
existing fire-and-forget behavior.
src/lib/events.ts (1)

35-71: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Good consolidation; consider hardening the fixed-delay dispatch.

navigateAndReveal relies on a fixed REVEAL_DELAY_MS (50ms) guess that the target view has mounted its CustomEvent listener. Since this is now the single chokepoint for reveal navigation used across chat, kanban, project-overview, and ref chips (per the docstring), a slow mount (e.g. board/notes view with heavy data) will silently drop the event — the view switches but the intended item never gets revealed, with no error surfaced.

A double requestAnimationFrame wait (mount happens before the second paint) is more robust than a magic-number timeout and is a drop-in replacement here.

♻️ Proposed fix
-/**
- * Delay (ms) between switching views and dispatching the reveal event. The
- * target view needs one render cycle to mount its CustomEvent listener before
- * we fire, otherwise the event is missed. Centralised here so the magic number
- * lives in exactly one place.
- */
-const REVEAL_DELAY_MS = 50;
-
 type ActiveView = AppUIState["activeView"];
 type SetView = (view: ActiveView) => void;

 /**
  * Switch to `view`, then—after the target view has had a render cycle to mount
  * its listener—dispatch `event`. Consolidates the
- * `setView(...); setTimeout(() => window.dispatchEvent(...), 50)` idiom that
+ * `setView(...); setTimeout(() => window.dispatchEvent(...), 50)` idiom that
  * was duplicated across chat, kanban, project-overview, and ref chips.
  */
 export function navigateAndReveal(setView: SetView, view: ActiveView, event: Event) {
   setView(view);
-  setTimeout(() => window.dispatchEvent(event), REVEAL_DELAY_MS);
+  // Wait for two paints so the target view's mount effect has definitely run,
+  // instead of guessing a fixed timeout.
+  requestAnimationFrame(() => requestAnimationFrame(() => window.dispatchEvent(event)));
 }
🤖 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 `@src/lib/events.ts` around lines 35 - 71, The shared reveal helper still
depends on a fixed timeout, which can miss the target view’s event listener if
mounting is slower than expected. Update navigateAndReveal to wait for the next
paint(s) instead of using REVEAL_DELAY_MS, keeping the same setView and
window.dispatchEvent flow but replacing the magic-number delay with a more
reliable readiness wait before firing CairnEvents in revealNote, revealCard, and
revealColumn.
src/components/graph/NodeTypeChip.tsx (1)

51-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add aria-pressed to the toggle chip for accessibility.

The interactive chip acts as a pressed/unpressed toggle but doesn't expose that state to assistive tech.

♿ Proposed fix
     <button
       onClick={onClick}
+      aria-pressed={active}
       className={cn(
🤖 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 `@src/components/graph/NodeTypeChip.tsx` around lines 51 - 70, The toggle chip
rendered in NodeTypeChip as the button element is acting like a
pressed/unpressed control but does not expose its state to assistive
technologies. Update the button in NodeTypeChip to include an aria-pressed
attribute driven by the existing active state so screen readers can announce the
toggle state correctly.
src/components/notes/MermaidDiagram.tsx (1)

19-19: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Mermaid diagrams won't repaint on live theme toggle.

getIsDark() is read once per render call inside getMermaid, but neither DiagramModal's nor MermaidDiagram's render effect depends on theme, so an already-rendered diagram keeps its stale theme colors if the user switches themes while it's mounted — inconsistent with the newly-reactive CodeBlock/DiffViewer/GitView.

♻️ Proposed fix
+import { useIsDark } from "`@/hooks/useIsDark`";
...
 export function MermaidDiagram({ chart }: Props) {
   const containerRef = useRef<HTMLDivElement>(null);
   const [error, setError] = useState<string | null>(null);
   const [expanded, setExpanded] = useState(false);
   const id = useId().replace(/:/g, "");
+  const isDark = useIsDark();

   useEffect(() => {
     if (!containerRef.current) return;
     let cancelled = false;
     async function render() {
       const { mermaid } = await getMermaid(id);
       ...
     }
     render();
     return () => { cancelled = true; };
-  }, [chart, id]);
+  }, [chart, id, isDark]);

Apply the same pattern to DiagramModal.

Also applies to: 27-61, 114-137

🤖 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 `@src/components/notes/MermaidDiagram.tsx` at line 19, Mermaid diagrams are not
reacting to live theme changes, so the rendered diagram can keep stale colors
after a theme toggle. Update the theme-dependent logic in MermaidDiagram and
DiagramModal so they re-render when the active theme changes, using the same
reactive pattern already used by CodeBlock, DiffViewer, and GitView; make sure
the getMermaid flow and the modal render effect both depend on the theme state
instead of reading getIsDark() only once.
src/components/notes/note-pdf-export.ts (1)

21-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Broad selector risks matching unrelated content.

div.my-4 matches any element with that single (common) Tailwind margin utility class, not just CodeBlock's wrapper, and block.querySelector("div") for the header isn't scoped to a direct child. An unrelated note element that happens to share my-4 and contain a <pre> would have its first <div> stripped or, in light mode, unrelated span colors rewritten.

♻️ Suggested tightening

Add a stable marker (e.g. data-codeblock on CodeBlock's root div and its header div) and match on that instead of the generic utility class:

-  for (const block of root.querySelectorAll<HTMLElement>("div.my-4")) {
-    const pre = block.querySelector<HTMLElement>("pre");
-    const header = block.querySelector<HTMLElement>("div");
+  for (const block of root.querySelectorAll<HTMLElement>("div[data-codeblock]")) {
+    const pre = block.querySelector<HTMLElement>(":scope > pre");
+    const header = block.querySelector<HTMLElement>(":scope > div[data-codeblock-header]");
🤖 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 `@src/components/notes/note-pdf-export.ts` around lines 21 - 24, The PDF export
logic in note-pdf-export is using overly broad selectors that can match
unrelated note content. Update the loop in the export routine to target a stable
CodeBlock marker instead of div.my-4, and scope the header lookup to the
CodeBlock-specific header element rather than any nested div. Add matching data
attributes on CodeBlock’s root and header elements so the selector in
note-pdf-export only processes the intended code block structure.
🤖 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 `@docs/architecture-review.md`:
- Around line 216-220: The `card-detail.tsx` / `card-detail-panel.tsx`
duplication recommendation is stale because the `CardDetailBody` refactor has
already shipped, so this section is now contradictory with the progress log.
Update the architecture review entry by removing the open-work guidance or
moving it into the completed work section, and keep the note aligned with the
actual refactor status in the document.

In `@src/components/agent/AgentMessageBubble.tsx`:
- Line 256: The message bubble styling in AgentMessageBubble is hardcoding white
text instead of using the accent foreground design token. Update the className
on the bubble container to use the CSS variable for the accent foreground (the
same token used elsewhere for themed contrast) rather than text-white, so the
text color follows per-theme accessibility rules.

In `@src/components/chat/chat-panel/MarkdownContent.tsx`:
- Around line 99-121: The MarkdownContent user-bubble styling is using raw
`text-white` and hardcoded `#ffffff`-based `color-mix` values, which breaks the
token-only color guideline. Update the user-path token definitions in
`MarkdownContent` (such as `strongColor`, `headingColor`, `listColor`,
`codeClass`, `quoteClass`, `ruleBorder`, `thClass`, and `tdClass`) to use CSS
variables like the existing accent/foreground tokens instead of literal white
values. Keep the same contrast behavior by expressing the alpha variants with
`color-mix(in srgb, var(--token) X%, transparent)` rather than hardcoded color
names.

In `@src/components/kanban/archive-view.tsx`:
- Around line 84-92: The archive card content currently nests
NoteMarkdownPreview inside the button in archive-view, which can create invalid
interactive markup when the preview renders links or other controls. Update the
card rendering to avoid placing markdown preview inside the clickable button:
either move the preview outside the button and make the card container handle
click/keyboard activation, or replace it with a non-interactive plain-text
snippet in this section. Use the onOpenCard handler and the card
button/container structure in ArchiveView to locate the change.
- Line 60: The alpha background utility classes in archive-view should follow
the project color rule instead of using the slash syntax. Update the affected
className values in the archive view markup so any hover or background alpha
tied to var(--danger) (and the other referenced spots in this component) uses
color-mix(in srgb, var(--token) X%, transparent) equivalents. Locate the
affected styles in the archive-view component and replace the var(--token)/10
usage consistently.
- Around line 120-130: In archive-view.tsx, the icon-only action buttons for
restore and permanent delete in the archive card controls need accessible names.
Update the button elements used with restoreCard and deleteCard to include
distinct aria-label values that describe each action, while keeping the existing
Tooltip and icon behavior intact so screen-reader users can identify the
controls.
- Around line 56-60: The delete actions in ArchiveView are immediately
performing permanent removal without confirmation. Update the destructive
handlers in the archive-view.tsx component, including the bulk delete button
that calls deleteCard over filtered cards and the individual delete path around
the other referenced deleteCard usage, to prompt the user for confirmation
before proceeding. Use the existing ArchiveView UI logic and deleteCard
invocation points to gate the actual delete behind a confirm step, so no
permanent delete happens on a single click.

In `@src/components/kanban/card-detail-body.tsx`:
- Around line 122-128: The description editor container in card-detail-body.tsx
is clickable but not keyboard-accessible, so update the div used in the
description area to behave like an interactive control. Add the appropriate role
and tabIndex, and handle Enter/Space in the same onClick handler path that
setsIsEditingDesc(true), while keeping the existing closest("a, button") guard
intact so links and buttons still do not trigger edit mode.
- Line 183: The note-open action in the CardDetailBody button only calls
revealNote and leaves the current surface open. Update the button handler in
card-detail-body.tsx so it also invokes onClose immediately after opening the
linked note, keeping the prop contract consistent. Use the existing
revealNote(setView, note.id) call as the entry point and add the close behavior
in the same onClick flow.

In `@src/components/notes/CodeBlock.tsx`:
- Around line 34-95: The `buildPalette` function in `CodeBlock` hardcodes the
`hljs-strong` colors instead of using the shared syntax tokens, which breaks the
PDF light-theme remap. Add a `strong` entry to `SYNTAX_COLORS` in
`syntax-palette.ts`, then update `buildPalette` to read `hljs-strong` from
`c("strong")` so it participates in `DARK_TO_LIGHT` like the other tokens. Keep
the `DARK` and `LIGHT` palettes derived only from the shared palette data.

In `@src/hooks/useIsDark.ts`:
- Around line 26-32: The server snapshot in useIsDark is hardcoded to dark,
which can mismatch the DOM during hydration when the theme has already been set
to light. Update useIsDark to reuse getIsDark for the third useSyncExternalStore
argument so the initial client render reads the current DOM state while still
falling back safely when document is unavailable.

---

Outside diff comments:
In `@src/components/settings/ViewVisibilitySettings.tsx`:
- Around line 42-55: The Toggle in ViewVisibilitySettings is using a hardcoded
accessible name, so both rows announce the same label. Update the Toggle inside
the ["Overview", "Notes"] map to include the per-row label variable in its
accessible label, matching the pattern already used by the toggleable rows
elsewhere in the component, so each switch is uniquely identified by its row
name.

---

Nitpick comments:
In `@src/components/agent/AgentChatPane.tsx`:
- Around line 63-81: The save failure log in persistPiTranscript lacks session
context, which makes concurrent transcript persistence errors hard to trace.
Update the .catch(console.error) handling in persistPiTranscript so the failure
log includes the sessionId alongside the error, using the same saveMessages call
path and preserving the existing fire-and-forget behavior.

In `@src/components/graph/NodeTypeChip.tsx`:
- Around line 51-70: The toggle chip rendered in NodeTypeChip as the button
element is acting like a pressed/unpressed control but does not expose its state
to assistive technologies. Update the button in NodeTypeChip to include an
aria-pressed attribute driven by the existing active state so screen readers can
announce the toggle state correctly.

In `@src/components/notes/MermaidDiagram.tsx`:
- Line 19: Mermaid diagrams are not reacting to live theme changes, so the
rendered diagram can keep stale colors after a theme toggle. Update the
theme-dependent logic in MermaidDiagram and DiagramModal so they re-render when
the active theme changes, using the same reactive pattern already used by
CodeBlock, DiffViewer, and GitView; make sure the getMermaid flow and the modal
render effect both depend on the theme state instead of reading getIsDark() only
once.

In `@src/components/notes/note-pdf-export.ts`:
- Around line 21-24: The PDF export logic in note-pdf-export is using overly
broad selectors that can match unrelated note content. Update the loop in the
export routine to target a stable CodeBlock marker instead of div.my-4, and
scope the header lookup to the CodeBlock-specific header element rather than any
nested div. Add matching data attributes on CodeBlock’s root and header elements
so the selector in note-pdf-export only processes the intended code block
structure.

In `@src/components/settings/shared.tsx`:
- Line 6: The shared Toggle wrapper currently exposes only a narrow prop subset,
while other settings code imports the UI Toggle directly for additional props
like disabled, creating two inconsistent entry points. Update the shared toggle
export in shared.tsx so it re-exports the canonical Toggle or forwards the full
ToggleProps surface, and ensure ViewVisibilitySettings and similar settings
components can rely on the shared import path for all supported props.

In `@src/lib/events.ts`:
- Around line 35-71: The shared reveal helper still depends on a fixed timeout,
which can miss the target view’s event listener if mounting is slower than
expected. Update navigateAndReveal to wait for the next paint(s) instead of
using REVEAL_DELAY_MS, keeping the same setView and window.dispatchEvent flow
but replacing the magic-number delay with a more reliable readiness wait before
firing CairnEvents in revealNote, revealCard, and revealColumn.
🪄 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 Plus

Run ID: 61b809a3-d1d6-4796-9c3d-74f87ec58913

📥 Commits

Reviewing files that changed from the base of the PR and between a076672 and a0897cc.

📒 Files selected for processing (58)
  • changelogs/v2.3.6.md
  • docs/architecture-review.md
  • src/components/agent/AgentChatPane.tsx
  • src/components/agent/AgentEditor.tsx
  • src/components/agent/AgentEmptyState.tsx
  • src/components/agent/AgentMessageBubble.tsx
  • src/components/agent/AgentSessionTab.tsx
  • src/components/agent/DiffViewer.tsx
  • src/components/agent/GitView.tsx
  • src/components/agent/sessionUtils.ts
  • src/components/chat/PreviewPane.tsx
  • src/components/chat/chat-panel/ChatMessageBubble.tsx
  • src/components/chat/chat-panel/MarkdownContent.tsx
  • src/components/chat/chat-panel/QuestionForm.tsx
  • src/components/chat/chat-panel/ThinkingPanel.tsx
  • src/components/chat/chat-panel/ToolCallIndicator.tsx
  • src/components/flow/nodes/NoteRefNode.tsx
  • src/components/flow/nodes/TaskRefNode.tsx
  • src/components/graph/ForceGraphCanvas.tsx
  • src/components/graph/KnowledgeGraphView.tsx
  • src/components/graph/NodeTypeChip.tsx
  • src/components/graph/RadialTreeCanvas.tsx
  • src/components/graph/TableCanvas.tsx
  • src/components/graph/analyticsHooks.ts
  • src/components/graph/analyticsUtils.ts
  • src/components/insights/InsightsView.tsx
  • src/components/kanban/archive-view.tsx
  • src/components/kanban/board.tsx
  • src/components/kanban/card-detail-body.tsx
  • src/components/kanban/card-detail-panel.tsx
  • src/components/kanban/card-detail.tsx
  • src/components/layout/project-overview/index.tsx
  • src/components/layout/project-overview/sections.tsx
  • src/components/layout/project-overview/useProjectMetrics.ts
  • src/components/notes/CodeBlock.tsx
  • src/components/notes/DashboardApiModal.tsx
  • src/components/notes/MermaidDiagram.tsx
  • src/components/notes/NoteMarkdownPreview.tsx
  • src/components/notes/dashboard-view.tsx
  • src/components/notes/markdown-code-fence.tsx
  • src/components/notes/note-editor.tsx
  • src/components/notes/note-pdf-export.ts
  • src/components/onboarding/StepMCP.tsx
  • src/components/search/search-panel.tsx
  • src/components/settings/AgentSettings.tsx
  • src/components/settings/MCPSettings.tsx
  • src/components/settings/ViewVisibilitySettings.tsx
  • src/components/settings/shared.tsx
  • src/components/shared/cairn-ref-chip.tsx
  • src/components/ui/toggle.tsx
  • src/hooks/useCopyToClipboard.ts
  • src/hooks/useIsDark.ts
  • src/lib/constants.ts
  • src/lib/editor-theme.ts
  • src/lib/events.ts
  • src/lib/syntax-palette.ts
  • src/lib/utils.ts
  • src/store/index.ts
💤 Files with no reviewable changes (1)
  • src/components/agent/sessionUtils.ts

Comment thread docs/architecture-review.md Outdated
Comment thread src/components/agent/AgentMessageBubble.tsx Outdated
Comment thread src/components/chat/chat-panel/MarkdownContent.tsx Outdated
Comment thread src/components/kanban/archive-view.tsx Outdated
Comment thread src/components/kanban/archive-view.tsx Outdated
Comment thread src/components/kanban/archive-view.tsx Outdated
Comment thread src/components/kanban/card-detail-body.tsx
Comment thread src/components/kanban/card-detail-body.tsx Outdated
Comment thread src/components/notes/CodeBlock.tsx
Comment thread src/hooks/useIsDark.ts
Replace hardcoded `text-white` and `#ffffff` color-mix values with the
theme-aware `--accent-fg` token across user message bubbles. Plain white
fails AA contrast on the dark accent surface; `--accent-fg` is
theme-aware and meets AA on the accent background.

Changes:
- AgentMessageBubble and ChatMessageBubble: swap `text-white` for
  `text-[var(--accent-fg)]` on the accent bubble.
- MarkdownContent: migrate strong/heading/list colors, code, quote,
  rule, table header/cell, and link classes to `--accent-fg`-based
  tokens (including color-mix alpha variants). Update the accompanying
  comment to document the contrast rationale.
- AgentChatPane: add contextual logging to the transcript persistence
  error handler for easier debugging.
- Update architecture-review doc to mark the card-detail dedupe (4.1)
  as done in Wave 2.
@ddutchie
ddutchie merged commit bec9b2b into main Jul 2, 2026
6 checks passed
@ddutchie
ddutchie deleted the ddutchie/cleanupagain branch July 2, 2026 14:43
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