Repository navigation
Mobile onboarding & sync, context breakdown, accent colors, icon refresh - #102
Conversation
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.
|
Warning Review limit reached
Next review available in: 48 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis 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. ChangesAccent color system
Table-cell task checkboxes
Mobile workflows and diagnostics
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…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.
There was a problem hiding this comment.
Actionable comments posted: 14
🧹 Nitpick comments (5)
mobile/src/chat/providers/openai.ts (1)
234-242: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winWiden the chunk
usagetype instead of casting.The inline
chunktype already declaresusage?: { prompt_tokens?: number }; adding the two extra fields there removes theusageAnycast 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 valueSegment widths can overflow the bar.
Each visible category is floored at 2pt, so with several tiny categories (or a near-full context)
usedWidthcan exceedBAR_INNER_WIDTH;trackWidthclamps to 0 and the last segments are clipped away, overstating fill. Consider scalingsegWidthsdown when the sum exceedsBAR_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 winUse
var(--accent)directly instead of resolving it viagetComputedStyle+ 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 inglobals.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 oneditOpentoggling).As per coding guidelines,
**/*.{css,tsx,jsx}: "Use CSS custom properties such asvar(--background)andvar(--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 winConsider 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 separateCOUNT(*)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 valueOptional: share the unindexed-notes derivation and copy.
withContent/emptyCountand the explanation wording are duplicated inmobile/app/(tabs)/search/index.tsx(Lines 182-198). A small helper (e.g.summariseUnindexed(rows)returning counts + capped titles) next toUnindexedNotewould 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
⛔ Files ignored due to path filters (15)
build-resources/Cairn.pngis excluded by!**/*.pngbuild-resources/Cairn_Stack.pngis excluded by!**/*.pngbuild-resources/icon.icois excluded by!**/*.icobuild-resources/icon.pngis excluded by!**/*.pngbuild-resources/icon2.icois excluded by!**/*.icodesign/Cairn.psdis excluded by!**/*.psddesign/Cairn_NB.pngis excluded by!**/*.pngmobile/assets/CairnMacIcon.icon/Assets/Cairn_NB.pngis excluded by!**/*.pngmobile/assets/CairnMacIcon.icon/Assets/Cairn_Stack.pngis excluded by!**/*.pngmobile/assets/splashIcon.pngis excluded by!**/*.pngpublic/favicon.svgis excluded by!**/*.svgpublic/icon.pngis excluded by!**/*.pngpublic/icon_win.pngis excluded by!**/*.pngpublic/tray.icois excluded by!**/*.icosrc/app/favicon.icois excluded by!**/*.ico
📒 Files selected for processing (51)
build-resources/icon.icnschangelogs/v2.5.15.mddesign/accent-lab.htmlelectron/ipc/handlers.tselectron/preload.tselectron/splash/bootsplash.tsmobile/app.jsonmobile/app/(tabs)/chat/index.tsxmobile/app/(tabs)/projects/index.tsxmobile/app/(tabs)/search/index.tsxmobile/app/_layout.tsxmobile/app/settings/appearance.tsxmobile/assets/CairnMacIcon.icon/icon.jsonmobile/changelogs/v0.1.4.mdmobile/src/chat/agent.tsmobile/src/chat/providers/openai.tsmobile/src/chat/providers/types.tsmobile/src/chat/token-breakdown.test.tsmobile/src/chat/token-breakdown.tsmobile/src/components/ContextRing.tsxmobile/src/components/DraggableBoard.tsxmobile/src/components/EmbeddingsCard.tsxmobile/src/components/MarkdownView.tsxmobile/src/components/OnboardingGuide.tsxmobile/src/components/SourcePicker.tsxmobile/src/components/graph/graph-webview-html.tsmobile/src/db/chat-store.tsmobile/src/db/embeddings-queries.tsmobile/src/notes/embeddings.tsmobile/src/notes/note-pdf.tsmobile/src/screens/CardDetailScreen.tsxmobile/src/screens/ProjectScreen.tsxmobile/src/sync/useSyncSources.tsmobile/src/theme.tsscripts/features.config.jsshared/notes/markdown.tsshared/notes/pdf-template.tsshared/ui/accents.tssrc/app/globals.csssrc/components/chat/chat-panel/MarkdownContent.tsxsrc/components/layout/QuickSettings.tsxsrc/components/notes/NoteMarkdownPreview.tsxsrc/components/notes/dashboard-view.tsxsrc/components/notes/note-editor-utils.test.tssrc/components/notes/note-markdown-components.tsxsrc/components/notes/render-cell-checkboxes.component.test.tsxsrc/components/settings/GeneralSettings.tsxsrc/components/ui/accent-picker.tsxsrc/store/index.tssrc/store/slices/ui.tstests/fixtures/ipc-mock.ts
💤 Files with no reviewable changes (1)
- mobile/src/components/SourcePicker.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
There was a problem hiding this comment.
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 winHide stale details when the indexing gap is gone.
showDetailsis not cleared byrefresh. After opening the panel, a successful reindex can makebehind === 0while empty notes remain inunindexed; the card then says “All notes are indexed” but still renders the details panel. Gate this condition onbehind > 0or resetshowDetailswhen 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
📒 Files selected for processing (26)
changelogs/v2.5.15.mddesign/accent-lab.htmlelectron/ipc/handlers.tsmobile/app/(tabs)/search/index.tsxmobile/changelogs/v0.1.4.mdmobile/src/chat/providers/openai.tsmobile/src/chat/token-breakdown.test.tsmobile/src/components/ContextRing.tsxmobile/src/components/DraggableBoard.tsxmobile/src/components/EmbeddingsCard.tsxmobile/src/components/KnowledgeGraphWebView.tsxmobile/src/components/MarkdownView.tsxmobile/src/components/OnboardingGuide.tsxmobile/src/dnd/useDragController.tsmobile/src/notes/embeddings.tsmobile/src/theme.tsshared/ui/graph.tssrc/app/globals.csssrc/components/graph/ForceGraphCanvas.tsxsrc/components/graph/KnowledgeGraphView.tsxsrc/components/graph/RadialTreeCanvas.tsxsrc/components/graph/analyticsUtils.tssrc/components/layout/QuickSettings.tsxsrc/components/notes/dashboard-view.tsxsrc/components/ui/accent-picker.tsxsrc/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
…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).
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
Fixes
[ ]/[x]tokens whether space- or<br>-separated. Clickable in the note preview, static in read-only contexts..icns/.ico/.pngapp icons regenerated from the new dark-tile artwork; favicons and apple-touch icon from the transparent stack artwork. Fixedicon.icolosing 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
Fixes
Type of change
Screenshots / recording
Checklist
npm run type-check:allpassesnpm run lintpassesnpm testpasses (runsnpm run compilefirst soelectron/bundle-guard.test.tsactually executes —npm run test:bundleto run just that)npm run test:e2epasses (run before merging UI changes or cutting a release)scripts/features.config.jsso it appears in the "What's New" modal (generated JSON — runnode scripts/generate-features.js)var(--accent),var(--text-primary), etc.)text-[Npx]pixel font classes — rem equivalents only (text-[0.714rem],text-xs, etc.)handle()and returnIpcResult<T>(n/a)schema.ts(n/a)electron/db/queries.ts(n/a)dependenciesordevDependenciesadded toROLE_MAP(n/a — no new deps)--external:<pkg>allowlist updated (n/a)Notes for reviewer
Cairn.png) drivesicon.png/icon.icns; transparent stack cutout (Cairn_Stack.png) drivesicon.ico,public/icon.png,public/tray.ico, and both favicons. macOStrayTemplate.png/@2xdeliberately left unchanged.out/icons are irrelevant — regenerated frompublic/on the nextnpm run build.scripts/generate-tray-icon.jsfails onrequire("sharp"); not exercised here since the tray template is unchanged. Worth fixing separately.Summary by CodeRabbit
New Features
Bug Fixes
<br>cases), with matching behavior in PDF export.