Skip to content

Mobile onboarding & sync, context breakdown, accent colors, icon refresh - #102

Merged
ddutchie merged 31 commits into
mainfrom
mobile/onboarding-and-fixes
Jul 28, 2026
Merged

ddutchie merged 31 commits into
mainfrom
mobile/onboarding-and-fixes

Conversation

@ddutchie

@ddutchie ddutchie commented Jul 28, 2026 •

Copy link
Copy Markdown
Owner

What does this PR do?

Batches the mobile onboarding/sync work, chat context-usage breakdown, board archive/delete, cross-platform table-cell checkboxes, a user-selectable accent color system, and a full desktop/app icon refresh. Changes span both the desktop app (changelogs/v2.5.15.md) and the mobile app (mobile/changelogs/v0.1.4.md).

Desktop (v2.5.15)

Features

  • Choose your accent color — accent-color picker in Settings → General and the topbar Quick Settings popover, with ten curated presets (Sage Moss default, Cairn Teal, Terracotta, Amber Gold, Obsidian Indigo, Slate Blue, Rust Coral, Deep Plum, Pine Green, Copper Bronze). Separate light/dark trios, all WCAG AA, persisted across restarts, follows System auto light/dark, and tints the launch splash. Default accent is now Sage Moss.

Fixes

  • Task-list checkboxes inside table cells now render as real checkboxes (GFM only parses inline content in cells), recovering [ ]/[x] tokens whether space- or <br>-separated. Clickable in the note preview, static in read-only contexts.
  • PDF export renders table checkboxes too (desktop inherits the fixed DOM; mobile PDF recovers cell checkboxes as print-safe glyphs).
  • Refreshed desktop app icon and favicons — .icns/.ico/.png app icons regenerated from the new dark-tile artwork; favicons and apple-touch icon from the transparent stack artwork. Fixed icon.ico losing its transparent background, trimmed excess empty space, re-tightened the opaque tile. Removed dead files (icon2.ico, icon_win.png, icon_dark.png). macOS tray template unchanged.

Mobile (v0.1.4)

New

  • Guided sync setup on first launch — step-by-step guide replacing the static four-step list; confirms the iCloud sync folder live, points to the desktop setting, watches iCloud and surfaces a Connect button when a workspace appears, and detects when iCloud isn't signed in.
  • Archive or delete a task by dragging it — long-press a card to reveal Archive/Delete drop zones (amber/red outlines, Delete confirms first); also delete from the detail screen ⋯ menu. First way to delete a task on mobile.
  • Liquid Glass app icon on iOS — ships an Icon Composer icon for iOS 26 light/dark/tinted/clear appearances.
  • Detailed context-usage breakdown — tapping the chat context ring opens a native popover with a colour-segmented bar and two-column token breakdown (system, tools, MCP, conversation, tool outputs) plus output section, at parity with desktop.
  • Pick your accent color — new Appearance screen with the same ten presets, instant apply, remembered across launches. Default Sage Moss.

Fixes

  • Semantic search now indexes every note — one un-embeddable note no longer aborts the whole pass; each note/task indexed in isolation, empty notes excluded from the count.
  • See why an item isn't indexed — info button on the Search tab / Sync index card explaining skipped notes.
  • Task-list checkboxes render inside table cells (mobile parity with desktop, tappable in notes / static in PDF).
  • Knowledge graph auto-fits on open — force-directed graph zooms to frame the whole graph once layout settles; backs off if you pan/zoom first.

Type of change

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

Screenshots / recording

Checklist

  • npm run type-check:all passes
  • npm run lint passes
  • npm test passes (runs npm run compile first so electron/bundle-guard.test.ts actually executes — npm run test:bundle to run just that)
  • npm run test:e2e passes (run before merging UI changes or cutting a release)
  • If this ships a major, user-facing feature: added an entry to scripts/features.config.js so it appears in the "What's New" modal (generated JSON — run node scripts/generate-features.js)
  • No hardcoded colours — CSS variables only (var(--accent), var(--text-primary), etc.)
  • No text-[Npx] pixel font classes — rem equivalents only (text-[0.714rem], text-xs, etc.)
  • New IPC handlers wrapped in handle() and return IpcResult<T> (n/a)
  • New DB migrations appended (not edited) in schema.ts (n/a)
  • New SQL goes in electron/db/queries.ts (n/a)
  • New dependencies or devDependencies added to ROLE_MAP (n/a — no new deps)
  • New MCP tools registered (n/a)
  • --external:<pkg> allowlist updated (n/a)

Notes for reviewer

  • Icon refresh: opaque dark tile (Cairn.png) drives icon.png/icon.icns; transparent stack cutout (Cairn_Stack.png) drives icon.ico, public/icon.png, public/tray.ico, and both favicons. macOS trayTemplate.png/@2x deliberately left unchanged.
  • Stale out/ icons are irrelevant — regenerated from public/ on the next npm run build.
  • Pre-existing: scripts/generate-tray-icon.js fails on require("sharp"); not exercised here since the tray template is unchanged. Worth fixing separately.

Summary by CodeRabbit

  • New Features

    • Added curated accent-color presets (default: Sage Moss) with an Accent color picker in Settings and Quick Settings; selection persists, supports light/dark + System, and tints the launch splash.
    • Added mobile Appearance settings for the same presets.
    • Expanded mobile chat context details (token breakdown + output tokens) and improved semantic-index visibility.
    • Added drag-and-drop Archive/Delete plus guided first-run setup.
  • Bug Fixes

    • Fixed task-list checkboxes in Markdown tables to render and toggle correctly (including multi-item and <br> cases), with matching behavior in PDF export.
    • Improved semantic indexing resilience and knowledge-graph legibility across accents; refreshed app/browser icons.

ddutchie added 27 commits July 27, 2026 13:16
Replace the static SourcePicker step list with a live-progress guided
checklist (OnboardingGuide) shown when no source is connected:

- Confirms iCloud availability and that opening the app created the
  shared folder (iCloud Drive > Cairn > sync) live.
- Gives the exact desktop path (Settings > Device Sync > Choose folder...)
  and tells the user to point it at that same folder.
- Auto-polls iCloud and surfaces a Connect button the moment a desktop
  workspace appears; detects a signed-out iCloud and explains the fix.

Wire it into the _layout first-run gate; remove the now-superseded
SourcePicker and update the useSyncSources doc reference.
embeddingIndexStats counted every live note in the denominator, but
notes with empty/whitespace bodies produce zero sections
(splitIntoSections returns [] on trimmed-empty content) and can never
be indexed -- so a fully-caught-up index read e.g. '209 of 212' and
looked stuck. Only count notes with non-empty content, matching the
splitter's emptiness test, so a complete index reaches 100%.

Cards are unaffected (always embeddable via title). Also adds the
v0.1.4 changelog covering the onboarding + this fix.
Root cause of the stuck 'N of M indexed' count: embedTexts throws on a
native count mismatch (text that tokenises to nothing), and neither
embedNoteInner/embedCardInner nor the workspace loops caught it — so a
single bad note unwound the entire pass via catchUpIndex's outer catch,
leaving that note and every note after it unindexed.

- Isolate per-section embed failures (note + card) so one skip doesn't
  abort the item; belt-and-suspenders per-item try/catch in the
  workspace loops too.
- Add listUnindexedNotes() diagnostic (live notes with no embedding rows,
  content-length classified).
- Surface an info affordance on the Search tab empty state and the Sync
  EmbeddingsCard explaining which items aren't indexed and why (empty vs
  un-embeddable content).

Also keeps the earlier denominator fix (empty notes excluded from total).
The 'why not all indexed?' button was a child of the empty-state
overlay, where it could sit behind the list / bottom filter bar and not
be visible. Move it into headerRight next to the sparkles toggle (a
warning-tinted info icon, shown when semantic mode is on and some items
are unindexed) so it's always reachable. Tapping it explains what isn't
indexed and offers a 'Reindex now' action.
…e count

The Search 'items indexed' count sums notes AND cards, but the
diagnostic + info button only looked at notes -- so when the unindexed
items were task cards, the list was empty and the button never showed.

- Add listUnindexedCards() and include it in the Search diagnostic.
- Exclude cards with blank title AND description from the liveCards
  denominator (they embed description ?? title, so a fully-blank card
  can't be indexed) -- mirrors the empty-note fix.
- Show the header info button whenever indexed < live (the same numbers
  the user sees), not just when the note-only list was non-empty.
- When the count is behind but no empty/unembeddable items are found,
  the info popup now explains it's likely stale model rows and offers
  Reindex.
Mirror the desktop board: long-pressing a card now reveals an Archive
and a Delete drop zone at the bottom of the board. Dragging a card onto
one archives or deletes it (Delete confirms first, then soft-deletes and
syncs the tombstone). This is the first way to delete a task on mobile.

- DraggableBoard gains onArchive/onDelete props and an always-mounted
  action bar (opacity-gated on drag so the drag core can measure its
  zone frames in panGesture.onBegin, before dragging state flips).
  Zones register as __archive__/__delete__ and route through onDrop.
- ProjectScreen wires them to archiveCard/deleteCard with reload,
  haptics and a toast; Delete shows a destructive confirm alert.
The action bar was an absolute overlay pinned over the bottom of the
columns, so dropping a card there read as a column drop and the zones
were only reachable in the gaps between columns. Make the bar a real
sibling row laid out BELOW the board scroll (which is now flex:1), so
the drop zones sit in genuinely empty space. The bar collapses to zero
height when idle and expands on a lift; since it lays out after the drag
begins (when the core measured zones), a double-rAF re-measure runs once
it's expanded so the zones become hittable.
Two spacing bugs on the board:
- Columns ran under the tab bar when idle (board had a flat 12px bottom
  pad, ignoring the safe area).
- The archive/delete zones floated ~49px too high above the tab bar
  while dragging, because bottomInset added TAB_BAR_BASE on top of
  insets.bottom -- but on a tab screen insets.bottom ALREADY includes the
  tab bar (see TAB_BAR_BASE doc / listBottomPad), so it double-counted.

Now the drag container pads insets.bottom + gap once at the bottom; the
board fills above it (columns clear the tab bar when idle) and the action
bar rests on that padding (zones sit at safe-area + gap when dragging).
Pass bottomInset={insets.bottom} from ProjectScreen and drop the unused
TAB_BAR_BASE import.
- The lifted card's outline now turns the target zone's colour while
  hovering it (warning for Archive, danger for Delete; accent otherwise),
  so the card itself signals the pending action.
- Split the action zone's border into two crossfading layers: the dashed
  idle border fades out exactly as the solid hover border fades in, so
  they're never both visible (previously dashed base + solid overlay
  showed together on hover).
Alongside Archive, the task detail ... menu now has Delete (destructive,
confirms first, then soft-deletes + toast + back). Complements the board
drag-to-delete zone so a task can be removed without dragging.
Both Archive and Delete were marked destructive (identical red). Archive
is recoverable, so drop destructive from it — Archive now renders in the
default colour and Delete stays red, mirroring the board's amber-vs-red
intent (the native iOS menu can't tint an action amber). Also make the
archive confirm button non-destructive to match.
GFM only parses inline content inside table cells, so a checklist crammed
into a cell (`- [ ] a - [x] b`, or a multi-line cell using `<br>`) rendered
as raw `[ ]`/`[x]` text. Recover every checkbox token (optional dash marker,
space- or <br>-separated) across desktop note preview, NoteMarkdownPreview,
chat, mobile view, and both PDF exports. Interactive in the note preview
(toggles the source in document order); static elsewhere.

- shared: CELL_CHECKBOX_RE grammar + toggleCheckboxInSource handles multi/
  dashed/<br> cell checkboxes in DOM order
- mobile PDF: markdown-it plugin recovers cell checkboxes as glyphs
- tests: updated toggle tests + new render-cell-checkboxes component test
Mobile only showed a flat prompt/limit total. Reconstruct the per-category
breakdown on-device (system prompt, tool definitions, MCP & services,
conversation, tool outputs) — mirroring desktop's mcp__/svc__ split so
connected MCP servers/services are visible — and show it in a native Liquid
Glass popover anchored beneath the ring (GlassContainer merges arrow + card;
falls back to a solid card when Liquid Glass is unavailable).

- token-breakdown: computeBreakdown + scaleBreakdown (+ tests)
- agent: attach the scaled breakdown to usage on every final
- openai: capture completion/reasoning tokens for the Output section
- ChatUsage + chat-store persist breakdown/completion/reasoning
- restore iOS scroll-to-top now the ring uses its own Modal popover
The ring is a custom RN view in headerLeft (unlike the native icon-only
right toolbar buttons, which UIKit consumes). Its upward hitSlop pushed the
touch target into the status-bar strip, where iOS reads the tap as a
"scroll to top" gesture and yanks the chat list up. Use an asymmetric
hitSlop (no upward expansion, widen sideways/below only) so the target never
reaches the status bar, and claim/keep the touch responder so it can't
double as the scroll-to-top gesture.
Revert the ring detail from a custom RN Modal/glass popover back to a native
@expo/ui Menu (via GlassMenu). A native menu host consumes the tap at the
SwiftUI layer, so tapping the ring in headerLeft no longer leaks to iOS's
status-bar "scroll to top" gesture — the reason a custom RN view in the
header misbehaved while the native right-side toolbar buttons never did.

The breakdown is rendered as native rows: a per-category list (system prompt,
tools, MCP & services, conversation, tool outputs) each with a tinted colour
dot, grouped under the summary, plus an Output section (answer/thinking/total)
once a turn completes. Non-iOS keeps the Alert fallback. Drops the now-unused
Modal/GlassView/hitSlop machinery.
Render each breakdown row as a custom two-line Button label (VStack of two
Text views): the category name on top (body) and the token count below in a
smaller, muted hierarchical-secondary style — instead of "dot  Name  198" on
one line. Removes the per-category colour-dot SF Symbol and the now-unused
tint import.
SwiftUI Menu rows are single-line only — a custom two-line Button label
(VStack of Text views) collapses and the second line never renders. Revert
to a single-line label per row ("Name   Count") and drop the unused
VStack/Text/font/foregroundStyle machinery.
Menu rows are a single flat string, so pad each formatted count with FIGURE
SPACE (U+2007, digit-width) to a common width per section — lining up the
numbers' right edges into a column — with an em-space gap after the name.
Native SwiftUI Menu rows are single-line, system-styled and can't do column
alignment (the ExpoUI MenuView just forwards children, but SwiftUI's menu
renderer flattens custom layout). A Popover, by contrast, renders arbitrary
SwiftUI, so switch the ring detail to @expo/ui Popover: each row is
HStack{ Text(name) · Spacer · Text(count) } with monospaced-digit,
right-aligned counts, grouped under the summary + an Output section.

The trigger is a native Button hosting the RN ring (via RNHostView), so the
tap is consumed at the SwiftUI layer and never triggers the header
status-bar scroll-to-top gesture. Non-iOS keeps the SVG ring + Alert.
Now that the breakdown lives in a native Popover (arbitrary SwiftUI, not a
restrictive Menu), bring back the desktop segmented bar: an HStack of
colour-filled Rectangles sized in points proportional to each category's
share of the context window, capsule-clipped, with a track for the unused
remainder. Also tint each row's right-aligned count to its category colour
(Output counts stay muted).
Nodes spawn near the centre and the force sim spreads them over ~1s, so the
initial view is an overlapping clump. Call the existing window.__fit() ~1.1s
after render to frame the whole graph automatically. Guarded by a pointer/
wheel listener so it backs off if the user pans or zooms first.
Fit on the force sim's 'end' event instead of a fixed 1.1s timer, so the
zoom-to-fit adapts to however long layout takes to settle. Guarded with a
didAutoFit flag so a later drag-induced settle doesn't re-trigger it, plus
the existing pointer/wheel interaction guard.
Waiting for the sim's 'end' event took a while — the force sim keeps ticking
long after motion is imperceptible (alpha decays toward ~0.001). Watch alpha
during ticks and fit once it drops below ~0.08 (visually settled), skipping
the slow tail. 'end' remains a fallback. Still guarded to fit only once and
back off on manual pan/zoom.
Fit earlier — the graph is visually settled sooner than alpha 0.08.
Add a curated set of ten accent-color presets (Sage Moss, Cairn Teal,
Terracotta, Amber Gold, Obsidian Indigo, Slate Blue, Rust Coral, Deep
Plum, Pine Green, Copper Bronze), replacing the hard-coded Obsidian-style
indigo. Sage Moss is the new default. Each preset ships a separate
light/dark trio tuned for Cairn's warm-neutral surfaces, with WCAG
AA-checked foreground pairings.

Desktop:
- shared/ui/accents.ts: single source of truth for presets (shared with
  mobile + splash)
- store: accentColor state + setAccentColor + applyAccent (injects
  --accent* inline on <html>, re-resolves on theme/system flips),
  restored on hydration
- globals.css: static fallback tokens now Sage Moss
- AccentPicker dropdown in Settings > General and topbar Quick Settings
- CodeMirror cursor/selection follow the chosen accent
- splash reads the persisted accent id via app:setAccent IPC

Mobile:
- theme.ts: reactive accent store (useSyncExternalStore) persisted to the
  device-global meta DB; useTheme() overlays the chosen preset
- new Appearance screen (settings/appearance.tsx) with the ten presets,
  opened from a gear button right of Sync on the Projects tab

Icons updated to the Moss green accent (build-resources, mac icon).
Adds design/accent-lab.html color test suite + source art.

Changelogs + What's New entry included.
Regenerate app icons from the new Cairn artwork:
- Opaque tile (re-tightened to ~72% fill) -> icon.png, icon.icns
- Transparent tight-cropped cutout -> icon.ico, public/icon.png,
  public/tray.ico, favicon.ico, favicon.svg
Fix icon.ico losing its transparent background and trim excess
empty space around the glyph. macOS tray template left unchanged.
Remove dead unreferenced files: icon2.ico, icon_win.png, icon_dark.png.
@coderabbitai

coderabbitai Bot commented Jul 28, 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: 48 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: efa88f1b-b17c-4ece-9062-0e7c8f01239b

📥 Commits

Reviewing files that changed from the base of the PR and between a997f7e and a28ef39.

📒 Files selected for processing (2)
  • mobile/src/components/EmbeddingsCard.tsx
  • mobile/src/dnd/useDragController.ts
📝 Walkthrough

Walkthrough

This PR adds shared accent presets across desktop and mobile, improves table-cell task checkbox rendering and PDF export, and updates mobile onboarding, task actions, chat usage accounting, semantic-index diagnostics, graph styling and fitting, and app assets.

Changes

Accent color system

Layer / File(s) Summary
Accent contracts, persistence, and selection
shared/ui/accents.ts, src/store/..., mobile/src/theme.ts, src/components/ui/accent-picker.tsx, mobile/app/settings/appearance.tsx
Adds ten accent presets, persistent theme integration, desktop and mobile selectors, Electron synchronization, splash tinting, and accent-aware CSS tokens.
Accent validation and release wiring
design/accent-lab.html, scripts/features.config.js, changelogs/v2.5.15.md
Adds interactive contrast and graph-legend validation plus release feature and changelog entries.

Table-cell task checkboxes

Layer / File(s) Summary
Parsing and rendering
shared/notes/markdown.ts, src/components/notes/..., src/components/chat/..., mobile/src/components/MarkdownView.tsx
Indexes, toggles, and renders multiple table-cell checkbox tokens in document order, including dash-prefixed and <br>-separated forms.
PDF output
shared/notes/pdf-template.ts, mobile/src/notes/note-pdf.ts
Adds print-safe checkbox glyphs and preserves table-cell line breaks in PDF output.

Mobile workflows and diagnostics

Layer / File(s) Summary
Onboarding, task actions, and indexing
mobile/src/components/OnboardingGuide.tsx, mobile/src/components/DraggableBoard.tsx, mobile/src/screens/*, mobile/src/notes/embeddings.ts, mobile/src/db/embeddings-queries.ts, mobile/app/(tabs)/search/index.tsx
Adds guided sync onboarding, drag archive/delete actions, resilient embedding passes, unindexed-content diagnostics, and reindex controls.
Chat usage breakdown
mobile/src/chat/*, mobile/src/components/ContextRing.tsx, mobile/src/db/chat-store.ts
Computes, persists, and displays categorized prompt, completion, and reasoning token usage.
Graph styling and fitting
shared/ui/graph.ts, src/components/graph/*, mobile/src/components/graph/*
Uses accent-independent project and semantic graph styling and auto-fits the mobile graph after layout settles.
Supporting assets and metadata
mobile/app.json, mobile/assets/*, tests/fixtures/ipc-mock.ts, mobile/changelogs/*
Updates icon configuration, test IPC support, and mobile release documentation.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AccentPicker
  participant UISlice
  participant ElectronIPC
  participant ThemeFile
  participant BootSplash
  User->>AccentPicker: Select accent preset
  AccentPicker->>UISlice: setAccentColor(accentId)
  UISlice->>ElectronIPC: invoke app:setAccent
  ElectronIPC->>ThemeFile: merge accent into theme.json
  BootSplash->>ThemeFile: read stored theme and accent
  BootSplash-->>User: render themed splash
Loading

Possibly related PRs

  • ddutchie/cairn#74: Overlaps with the knowledge-graph rendering and styling changes.
  • ddutchie/cairn#78: Overlaps with mobile chat context-usage ring and token accounting changes.
  • ddutchie/cairn#85: Overlaps with table-cell task-list checkbox rendering and parsing.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.06% 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 title is concise and accurately reflects the PR’s main themes: mobile onboarding/sync, context breakdowns, accent colors, and icon refresh.
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 mobile/onboarding-and-fixes

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.

ddutchie added 2 commits July 28, 2026 17:10
…iktoken

The mobile vitest project resolves against the root node_modules, but
js-tiktoken is a mobile-only dependency — absent on CI (which runs
`npm ci` at the root only). token-breakdown.ts transitively imports
js-tiktoken/lite via ./tokens, so the test crashed at module load with
'Cannot find package js-tiktoken/lite'.

The tests only assert token counts are >0 / ===0, never exact values,
so mock ./tokens with a chars/4 estimate. This keeps the mobile vitest
project dependency-free as designed (see vitest.config.ts) and skips
loading the 2MB tiktoken rank table (test time 256ms -> 2ms).
The knowledge-graph palette is token-driven, and Project was keyed to the
accent. Now that the accent is user-selectable and defaults to a green
(Sage Moss), Project collided with Card (success green) and the accent-based
edges clustered together.

- shared/ui/graph.ts: add a 'nodeProject' token; nodeTypeToken /
  sunburstTypeToken / project-member edge now use it (fixed violet,
  accent-independent). Semantic edges move from accent to a neutral
  text-tertiary dashed so they don't merge with the accent.
- globals.css + mobile theme.ts: define --node-project / nodeProject
  (violet: #a78bfa dark, #7c5cd6 light).
- analyticsUtils.tokenToCssVar(): single camelCase->kebab token->var helper,
  used by ForceGraphCanvas + RadialTreeCanvas; graph store nodeTypeColor
  updated to match.
- KnowledgeGraphView EDGE_LEGEND aligned to the shared edgeStyle tokens
  (Linked=success, Wikilink=accent, Semantic=neutral, Co-mention=border).
- mobile tokenColor(): handle nodeProject.
- accent-lab.html: add a graph-legend collision check (current vs proposed).

Changelogs updated.

@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: 14

🧹 Nitpick comments (5)
mobile/src/chat/providers/openai.ts (1)

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

Widen the chunk usage type instead of casting.

The inline chunk type already declares usage?: { prompt_tokens?: number }; adding the two extra fields there removes the usageAny cast and keeps all usage parsing type-checked in one place.

♻️ Proposed refactor

Extend the declared shape (line 223):

-            usage?: { prompt_tokens?: number };
+            usage?: {
+              prompt_tokens?: number;
+              completion_tokens?: number;
+              completion_tokens_details?: { reasoning_tokens?: number };
+            };

Then simplify the parsing:

-          const usageAny = chunk.usage as
-            | { completion_tokens?: number; completion_tokens_details?: { reasoning_tokens?: number } }
-            | undefined;
-          if (typeof usageAny?.completion_tokens === "number") {
-            completionTokens = usageAny.completion_tokens;
-          }
-          if (typeof usageAny?.completion_tokens_details?.reasoning_tokens === "number") {
-            reasoningTokens = usageAny.completion_tokens_details.reasoning_tokens;
-          }
+          if (typeof chunk.usage?.completion_tokens === "number") {
+            completionTokens = chunk.usage.completion_tokens;
+          }
+          if (typeof chunk.usage?.completion_tokens_details?.reasoning_tokens === "number") {
+            reasoningTokens = chunk.usage.completion_tokens_details.reasoning_tokens;
+          }
🤖 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 `@mobile/src/chat/providers/openai.ts` around lines 234 - 242, Extend the
declared usage shape in the inline chunk type to include optional
completion_tokens and completion_tokens_details.reasoning_tokens fields. Update
the parsing block to read chunk.usage directly, remove the usageAny cast, and
preserve the existing numeric checks and assignments for completionTokens and
reasoningTokens.
mobile/src/components/ContextRing.tsx (1)

165-180: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Segment widths can overflow the bar.

Each visible category is floored at 2pt, so with several tiny categories (or a near-full context) usedWidth can exceed BAR_INNER_WIDTH; trackWidth clamps to 0 and the last segments are clipped away, overstating fill. Consider scaling segWidths down when the sum exceeds BAR_INNER_WIDTH.

🤖 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 `@mobile/src/components/ContextRing.tsx` around lines 165 - 180, The segmented
bar width calculation in the native multi-segment bar can exceed BAR_INNER_WIDTH
because each category is floored at 2pt. Update the segWidths/usedWidth logic to
proportionally scale all segment widths down when their sum exceeds
BAR_INNER_WIDTH, then compute trackWidth from the bounded total so every visible
segment fits without clipping.
src/components/notes/dashboard-view.tsx (1)

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

Use var(--accent) directly instead of resolving it via getComputedStyle + hardcoded fallback.

EditorView.theme() emits real CSS, so the accent token can be referenced directly (e.g. borderLeftColor: "var(--accent)", color-mix(in srgb, var(--accent) 15%, transparent)) instead of reading the computed value and duplicating default hex fallbacks (#8faf6f / #5c7a3f) that already live in globals.css. This avoids duplicating the token defaults and keeps the cursor/selection colors in sync if the accent changes while this effect isn't re-run (it only re-initializes on editOpen toggling).

As per coding guidelines, **/*.{css,tsx,jsx}: "Use CSS custom properties such as var(--background) and var(--accent) for all colours; never use raw Tailwind colour names."

♻️ Proposed refactor
-      // Follow the user's chosen accent (injected as --accent on <html>).
-      const accent =
-        getComputedStyle(document.documentElement).getPropertyValue("--accent").trim() ||
-        (isDark ? "`#8faf6f`" : "`#5c7a3f`");
+      // Follow the user's chosen accent (injected as --accent on <html>).
+      const accent = "var(--accent)";

Also applies to: 189-192

🤖 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/dashboard-view.tsx` around lines 166 - 169, Update the
accent handling in the dashboard view’s editor theme setup to reference CSS
custom property var(--accent) directly instead of calling getComputedStyle or
using the hardcoded `#8faf6f/`#5c7a3f fallbacks. Replace both accent usages in the
affected theme styles, including border, cursor, selection, and color-mix
expressions, so live CSS variable changes remain synchronized.

Source: Coding guidelines

mobile/src/db/embeddings-queries.ts (1)

308-364: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider bounding the diagnostics queries.

Both callers spread the full result per workspace into memory, but the UI only ever shows 8 titles plus a count. A LIMIT (with a separate COUNT(*) when the exact total matters) keeps this cheap on large workspaces where most notes are empty.

🤖 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 `@mobile/src/db/embeddings-queries.ts` around lines 308 - 364, Bound the
diagnostic result sets returned by listUnindexedNotes and listUnindexedCards to
the UI’s eight-item display limit, while preserving ordering so the most
relevant entries remain first. If callers require the exact unindexed total, add
a separate COUNT(*) query rather than materializing every matching row.
mobile/src/components/EmbeddingsCard.tsx (1)

68-71: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: share the unindexed-notes derivation and copy.

withContent/emptyCount and the explanation wording are duplicated in mobile/app/(tabs)/search/index.tsx (Lines 182-198). A small helper (e.g. summariseUnindexed(rows) returning counts + capped titles) next to UnindexedNote would keep the two surfaces from drifting.

🤖 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 `@mobile/src/components/EmbeddingsCard.tsx` around lines 68 - 71, Extract the
shared unindexed-note derivation and explanatory copy used by EmbeddingsCard and
the search screen into a helper near UnindexedNote, such as summariseUnindexed.
Have it return the content-bearing and empty counts plus the capped titles
needed by both surfaces, then update both callers to consume it and remove their
duplicated logic.
🤖 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 `@design/accent-lab.html`:
- Around line 315-316: Update the shipping-default copy near the “current
shipping accent” text to identify Sage Moss instead of Obsidian Indigo, leaving
the surrounding UI-surface guidance unchanged.

In `@electron/ipc/handlers.ts`:
- Around line 125-136: The app:setTheme handler currently overwrites theme.json
and removes the persisted accent. Centralize the existing theme-file
read/merge/write behavior into a shared helper, then update both app:setAccent
and app:setTheme to use it so each change preserves the other setting while
writing its own value.

In `@mobile/app/`(tabs)/search/index.tsx:
- Around line 180-220: Update showIndexInfo to use neutral “item/items” wording
throughout the semantic-index alert because unindexed combines notes and task
cards. Replace note-specific labels in the explanatory text and content-count
summary while preserving the existing counts, titles, reindex action, and
empty-state behavior.

In `@mobile/src/components/ContextRing.tsx`:
- Around line 182-194: Update the trigger Button in ContextRing’s Popover to
provide accessibilityLabel using the existing a11yLabel value, matching the
context/percentage text used by the non-iOS fallback. Keep the current press
behavior and ring rendering unchanged.

In `@mobile/src/components/DraggableBoard.tsx`:
- Around line 154-188: Update the zone registration and hit-testing flow around
registerZone and zoneAt to support scroll-independent zones. Mark the
archive/delete ActionZone registrations as scroll-independent, and skip
scrollOffset - offsetAtMeasure correction for those zones while preserving
correction for zones inside the scroll view.

In `@mobile/src/components/EmbeddingsCard.tsx`:
- Around line 92-106: Update the Info button condition in the EmbeddingsCard
count row to require an actual indexing gap, such as behind being greater than
zero, rather than relying only on unindexed.length. Keep the existing toggle
behavior and accessibility properties unchanged, and ensure the button is hidden
when the UI reports that all notes are indexed.

In `@mobile/src/components/MarkdownView.tsx`:
- Around line 444-461: The checklist checkbox rendered in the table-cell mapping
should use a larger touch target, matching the sibling list-item task checkbox
pattern. Update the `Text` element around the `CheckSquare`/`Square` icons to
use an appropriate `Pressable` wrapper with `hitSlop` while preserving the
existing `interactive` guard and `onChangeContent` behavior.

In `@mobile/src/components/OnboardingGuide.tsx`:
- Around line 102-122: Update the auto-poll useEffect keyed by probe.phase so
scanning retries during both "waiting" and "checking" phases, preserving the
existing interval and cleanup behavior while ensuring failures before
folderReady continue polling automatically.

In `@mobile/src/db/embeddings-queries.ts`:
- Around line 286-292: Update the task-card count query to match the effective
body used by embedCardInner(): count a card when description is non-null and
non-blank, or when description is null and title is non-blank. Alternatively,
change embedCardInner() to fall back to title when description is blank, while
keeping liveCards and indexing behavior consistent.

In `@mobile/src/theme.ts`:
- Around line 231-236: Memoize the derived theme in useTheme using React’s
useMemo, with scheme and accentId as dependencies, so applyAccentToTheme is only
called when either value changes. Add the required useMemo import and preserve
the existing light/dark base selection and return behavior.

In `@src/app/globals.css`:
- Line 20: Replace both hardcoded rgba() --accent-dim fallbacks in
src/app/globals.css:20-20 and src/app/globals.css:50-50 with color-mix(in srgb,
var(--accent) 15%, transparent) and color-mix(in srgb, var(--accent) 12%,
transparent), respectively.

In `@src/components/layout/QuickSettings.tsx`:
- Around line 72-79: Update the AccentPicker usage in QuickSettings to pass
variant="grid" while preserving the existing className="w-full", so the accent
selector uses the grid picker instead of nesting another dropdown menu.

In `@src/components/ui/accent-picker.tsx`:
- Around line 38-44: Update AccentPicker to accept an optional id prop and
forward it to the dropdown trigger element, preserving the existing variant and
className behavior so SettingsRow’s injected id associates the “Accent color”
label with the button.
- Around line 28-35: Update useIsLightTheme to track the system color-scheme
media query with local state and a change-event subscription, so OS appearance
changes immediately update the returned value when theme is "system". Add effect
cleanup for the media-query listener and preserve the existing light, dark, and
SSR behavior.

---

Nitpick comments:
In `@mobile/src/chat/providers/openai.ts`:
- Around line 234-242: Extend the declared usage shape in the inline chunk type
to include optional completion_tokens and
completion_tokens_details.reasoning_tokens fields. Update the parsing block to
read chunk.usage directly, remove the usageAny cast, and preserve the existing
numeric checks and assignments for completionTokens and reasoningTokens.

In `@mobile/src/components/ContextRing.tsx`:
- Around line 165-180: The segmented bar width calculation in the native
multi-segment bar can exceed BAR_INNER_WIDTH because each category is floored at
2pt. Update the segWidths/usedWidth logic to proportionally scale all segment
widths down when their sum exceeds BAR_INNER_WIDTH, then compute trackWidth from
the bounded total so every visible segment fits without clipping.

In `@mobile/src/components/EmbeddingsCard.tsx`:
- Around line 68-71: Extract the shared unindexed-note derivation and
explanatory copy used by EmbeddingsCard and the search screen into a helper near
UnindexedNote, such as summariseUnindexed. Have it return the content-bearing
and empty counts plus the capped titles needed by both surfaces, then update
both callers to consume it and remove their duplicated logic.

In `@mobile/src/db/embeddings-queries.ts`:
- Around line 308-364: Bound the diagnostic result sets returned by
listUnindexedNotes and listUnindexedCards to the UI’s eight-item display limit,
while preserving ordering so the most relevant entries remain first. If callers
require the exact unindexed total, add a separate COUNT(*) query rather than
materializing every matching row.

In `@src/components/notes/dashboard-view.tsx`:
- Around line 166-169: Update the accent handling in the dashboard view’s editor
theme setup to reference CSS custom property var(--accent) directly instead of
calling getComputedStyle or using the hardcoded `#8faf6f/`#5c7a3f fallbacks.
Replace both accent usages in the affected theme styles, including border,
cursor, selection, and color-mix expressions, so live CSS variable changes
remain synchronized.
🪄 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: 1426e487-da19-4f59-bb52-610604a797d8

📥 Commits

Reviewing files that changed from the base of the PR and between 4acc3cb and 680f85f.

⛔ Files ignored due to path filters (15)
  • build-resources/Cairn.png is excluded by !**/*.png
  • build-resources/Cairn_Stack.png is excluded by !**/*.png
  • build-resources/icon.ico is excluded by !**/*.ico
  • build-resources/icon.png is excluded by !**/*.png
  • build-resources/icon2.ico is excluded by !**/*.ico
  • design/Cairn.psd is excluded by !**/*.psd
  • design/Cairn_NB.png is excluded by !**/*.png
  • mobile/assets/CairnMacIcon.icon/Assets/Cairn_NB.png is excluded by !**/*.png
  • mobile/assets/CairnMacIcon.icon/Assets/Cairn_Stack.png is excluded by !**/*.png
  • mobile/assets/splashIcon.png is excluded by !**/*.png
  • public/favicon.svg is excluded by !**/*.svg
  • public/icon.png is excluded by !**/*.png
  • public/icon_win.png is excluded by !**/*.png
  • public/tray.ico is excluded by !**/*.ico
  • src/app/favicon.ico is excluded by !**/*.ico
📒 Files selected for processing (51)
  • build-resources/icon.icns
  • changelogs/v2.5.15.md
  • design/accent-lab.html
  • electron/ipc/handlers.ts
  • electron/preload.ts
  • electron/splash/bootsplash.ts
  • mobile/app.json
  • mobile/app/(tabs)/chat/index.tsx
  • mobile/app/(tabs)/projects/index.tsx
  • mobile/app/(tabs)/search/index.tsx
  • mobile/app/_layout.tsx
  • mobile/app/settings/appearance.tsx
  • mobile/assets/CairnMacIcon.icon/icon.json
  • mobile/changelogs/v0.1.4.md
  • mobile/src/chat/agent.ts
  • mobile/src/chat/providers/openai.ts
  • mobile/src/chat/providers/types.ts
  • mobile/src/chat/token-breakdown.test.ts
  • mobile/src/chat/token-breakdown.ts
  • mobile/src/components/ContextRing.tsx
  • mobile/src/components/DraggableBoard.tsx
  • mobile/src/components/EmbeddingsCard.tsx
  • mobile/src/components/MarkdownView.tsx
  • mobile/src/components/OnboardingGuide.tsx
  • mobile/src/components/SourcePicker.tsx
  • mobile/src/components/graph/graph-webview-html.ts
  • mobile/src/db/chat-store.ts
  • mobile/src/db/embeddings-queries.ts
  • mobile/src/notes/embeddings.ts
  • mobile/src/notes/note-pdf.ts
  • mobile/src/screens/CardDetailScreen.tsx
  • mobile/src/screens/ProjectScreen.tsx
  • mobile/src/sync/useSyncSources.ts
  • mobile/src/theme.ts
  • scripts/features.config.js
  • shared/notes/markdown.ts
  • shared/notes/pdf-template.ts
  • shared/ui/accents.ts
  • src/app/globals.css
  • src/components/chat/chat-panel/MarkdownContent.tsx
  • src/components/layout/QuickSettings.tsx
  • src/components/notes/NoteMarkdownPreview.tsx
  • src/components/notes/dashboard-view.tsx
  • src/components/notes/note-editor-utils.test.ts
  • src/components/notes/note-markdown-components.tsx
  • src/components/notes/render-cell-checkboxes.component.test.tsx
  • src/components/settings/GeneralSettings.tsx
  • src/components/ui/accent-picker.tsx
  • src/store/index.ts
  • src/store/slices/ui.ts
  • tests/fixtures/ipc-mock.ts
💤 Files with no reviewable changes (1)
  • mobile/src/components/SourcePicker.tsx

Comment thread design/accent-lab.html Outdated
Comment thread electron/ipc/handlers.ts
Comment thread mobile/app/(tabs)/search/index.tsx
Comment thread mobile/src/components/ContextRing.tsx
Comment thread mobile/src/components/DraggableBoard.tsx
Comment thread mobile/src/theme.ts
Comment thread src/app/globals.css Outdated
Comment thread src/components/layout/QuickSettings.tsx
Comment thread src/components/ui/accent-picker.tsx
Comment thread src/components/ui/accent-picker.tsx
Desktop:
- handlers: centralise theme.json read/merge/write so app:setTheme and
  app:setAccent each preserve the other's value (setTheme was clobbering
  the persisted accent)
- globals.css: --accent-dim uses color-mix(var(--accent)) not hardcoded rgba
- accent-picker: forward optional id to the dropdown trigger (SettingsRow
  label association); useIsLightTheme subscribes to the prefers-color-scheme
  change event so swatches react to live OS light/dark flips
- QuickSettings: use AccentPicker variant=grid (no nested Radix dropdown)
- dashboard-view: reference var(--accent) directly instead of a
  getComputedStyle snapshot + hardcoded fallbacks
- accent-lab: current shipping accent is Sage Moss, not Obsidian Indigo

Mobile:
- embeddings: embedCardInner falls back to title when description is blank
  (not just null), matching the trimming liveCards count so the index total
  can't stay permanently short
- ContextRing: add accessibilityLabel to the native Button; scale segmented
  bar widths down proportionally when the 2pt-floored sum overflows the bar
- DraggableBoard/useDragController: register archive/delete zones as
  scroll-independent so the hit-test doesn't shift them by the scroll delta
- MarkdownView: table-cell checkbox uses a Pressable + hitSlop, matching the
  list-item task checkbox touch target
- OnboardingGuide: auto-poll during both waiting and checking phases
- theme: memoise useTheme on scheme + accentId
- search: neutral item/items wording (the list mixes notes and task cards)
- openai: extend the chunk usage type, drop the usageAny cast

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
mobile/src/components/EmbeddingsCard.tsx (1)

115-116: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Hide stale details when the indexing gap is gone.

showDetails is not cleared by refresh. After opening the panel, a successful reindex can make behind === 0 while empty notes remain in unindexed; the card then says “All notes are indexed” but still renders the details panel. Gate this condition on behind > 0 or reset showDetails when the gap disappears.

Suggested fix
-          {showDetails && unindexed.length > 0 && (
+          {showDetails && behind > 0 && unindexed.length > 0 && (
🤖 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 `@mobile/src/components/EmbeddingsCard.tsx` around lines 115 - 116, Update the
details-panel render condition in EmbeddingsCard to require behind > 0 in
addition to showDetails and unindexed.length > 0, so stale details are hidden
once indexing is complete.
🤖 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 `@mobile/src/dnd/useDragController.ts`:
- Around line 192-206: Update registerZone so every non-null registration
synchronizes scrollIndependentZones.value[zoneId] with the current
scrollIndependent argument: set it when true and remove it when false. Preserve
the existing cleanup behavior for null registrations.

---

Outside diff comments:
In `@mobile/src/components/EmbeddingsCard.tsx`:
- Around line 115-116: Update the details-panel render condition in
EmbeddingsCard to require behind > 0 in addition to showDetails and
unindexed.length > 0, so stale details are hidden once indexing is complete.
🪄 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: 7a136889-31b3-443d-8b75-c5e30b433735

📥 Commits

Reviewing files that changed from the base of the PR and between 680f85f and a997f7e.

📒 Files selected for processing (26)
  • changelogs/v2.5.15.md
  • design/accent-lab.html
  • electron/ipc/handlers.ts
  • mobile/app/(tabs)/search/index.tsx
  • mobile/changelogs/v0.1.4.md
  • mobile/src/chat/providers/openai.ts
  • mobile/src/chat/token-breakdown.test.ts
  • mobile/src/components/ContextRing.tsx
  • mobile/src/components/DraggableBoard.tsx
  • mobile/src/components/EmbeddingsCard.tsx
  • mobile/src/components/KnowledgeGraphWebView.tsx
  • mobile/src/components/MarkdownView.tsx
  • mobile/src/components/OnboardingGuide.tsx
  • mobile/src/dnd/useDragController.ts
  • mobile/src/notes/embeddings.ts
  • mobile/src/theme.ts
  • shared/ui/graph.ts
  • src/app/globals.css
  • src/components/graph/ForceGraphCanvas.tsx
  • src/components/graph/KnowledgeGraphView.tsx
  • src/components/graph/RadialTreeCanvas.tsx
  • src/components/graph/analyticsUtils.ts
  • src/components/layout/QuickSettings.tsx
  • src/components/notes/dashboard-view.tsx
  • src/components/ui/accent-picker.tsx
  • src/store/slices/graph.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • mobile/changelogs/v0.1.4.md
  • mobile/src/chat/providers/openai.ts
  • changelogs/v2.5.15.md
  • src/components/notes/dashboard-view.tsx
  • src/app/globals.css
  • src/components/layout/QuickSettings.tsx
  • mobile/src/chat/token-breakdown.test.ts
  • mobile/src/components/DraggableBoard.tsx
  • src/components/ui/accent-picker.tsx
  • mobile/src/notes/embeddings.ts
  • mobile/src/components/MarkdownView.tsx
  • mobile/src/components/ContextRing.tsx
  • mobile/src/components/OnboardingGuide.tsx
  • mobile/src/theme.ts

Comment thread mobile/src/dnd/useDragController.ts
…ils on behind

Follow-up to code-review findings:
- registerZone now clears scrollIndependentZones[zoneId] when a zone is
  re-registered with scrollIndependent=false, not just setting it on true,
  so a re-registered id can't keep a stale flag.
- EmbeddingsCard details panel now also requires behind > 0, so an
  already-toggled panel hides its stale content once indexing completes
  (matching the Info button gate).
@ddutchie
ddutchie merged commit a91c900 into main Jul 28, 2026
6 checks passed
@ddutchie
ddutchie deleted the mobile/onboarding-and-fixes branch July 28, 2026 22:21
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