Skip to content

feat(electron): add canvas-first workspace shell#5

Merged
crs48 merged 11 commits intomainfrom
codex/canvas-first-shell
Mar 6, 2026
Merged

feat(electron): add canvas-first workspace shell#5
crs48 merged 11 commits intomainfrom
codex/canvas-first-shell

Conversation

@crs48
Copy link
Copy Markdown
Owner

@crs48 crs48 commented Mar 6, 2026

Summary

  • make the primary app open into a persistent home canvas with focused page and database overlays
  • replace the old sidebar-heavy shell with a bottom action dock and top-right system menu
  • add canvas-linked creation flows and compact focused-mode chrome for page and database views
  • allow pnpm to build better-sqlite3 so the repo test/push path works in fresh worktrees

Validation

  • PATH=/Users/crs/.nvm/versions/node/v23.11.1/bin:$PATH pnpm typecheck
  • PATH=/Users/crs/.nvm/versions/node/v23.11.1/bin:$PATH pnpm test

Summary by CodeRabbit

  • New Features

    • Canvas-first shell: top system menu, bottom action dock, command palette, overlay-based focused views
    • Rich canvas interactions: embed/open linked documents, create canvas notes, viewport snapshot/restore and fit-to-rect controls, quick-action palette entries
    • Minimal/compact chrome modes and updated document header controls
  • Documentation

    • Added a detailed canvas-first UX exploration and migration plan
  • Tests

    • Added tests for canvas-shell note detection and rendering logic
  • Chores

    • Small package config addition for build tooling

crs48 and others added 5 commits March 5, 2026 23:16
- analyze the current Electron shell, view chrome, and canvas primitives

- recommend a hybrid canvas-first shell with bottom dock and top-right system menu

- capture migration phases, tradeoffs, validation steps, and implementation example code
- rename the exploration from 0103 to 0104

- update the document title to match the new numbering
- replace the sidebar-first renderer shell

- keep a persistent home canvas with overlay focus surfaces

- add a bottom action dock and top-right system menu

- create linked page and database cards on the canvas

- extend canvas viewport APIs for zoom-based transitions

- update the exploration checklist for implemented items
- add compact header controls for focused page views

- reduce database toolbar density inside the overlay shell

- wire compact mode from the new app shell

- update the exploration checklist for the page chrome item
- add pnpm onlyBuiltDependencies entry for better-sqlite3
- unblock sqlite adapter tests in fresh worktrees where native builds were skipped
- keep the existing deps:node workflow able to rebuild the native binding before test runs
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes a canvas-first shell with state-driven navigation and overlays; adds ActionDock and SystemMenu UI primitives; upgrades CanvasView to a forwardRef with linked-document and viewport snapshot APIs; exposes fit/snapshot methods in canvas hooks/renderer; and adds minimalChrome/compact toggles across page/database/document headers.

Changes

Cohort / File(s) Summary
App Shell & Shell State
apps/electron/src/renderer/App.tsx
Rewrote App to a shell/state-driven model (states: canvas-home, page-focus, database-focus, settings). Added overlay rendering, home-canvas bootstrap with retry, document aggregation, command palette/SystemMenu integration, viewport snapshot orchestration, and navigation/focus helpers.
Canvas View & Shell Helpers
apps/electron/src/renderer/components/CanvasView.tsx, apps/electron/src/renderer/lib/canvas-shell.ts, apps/electron/src/renderer/lib/canvas-shell.test.ts
Made CanvasView a forwardRef exposing CanvasViewHandle (addLinkedDocumentNode, addCanvasNote, focusLinkedDocument, restoreViewport). Added linked-document types and shell-note utilities (createCanvasShellNoteProperties, isCanvasShellNote, shouldRenderCanvasShellCard) and unit tests for these helpers.
Canvas/Viewport APIs
packages/canvas/src/hooks/useCanvas.ts, packages/canvas/src/renderer/Canvas.tsx
Exposed fitToRect, getViewportSnapshot, and setViewportSnapshot on useCanvas return value and on the Canvas imperative handle to enable viewport saving/restoring and rect-fitting.
New UI Primitives
apps/electron/src/renderer/components/ActionDock.tsx, apps/electron/src/renderer/components/SystemMenu.tsx
Added ActionDock (bottom-centered dock with focused/home modes and create/search/recent actions) and SystemMenu (top-right menu with Workspace/Theme/Recent sections) wired to shell callbacks.
View Chrome Toggling
apps/electron/src/renderer/components/PageView.tsx, apps/electron/src/renderer/components/DatabaseView.tsx, apps/electron/src/renderer/components/DocumentHeader.tsx
Introduced minimalChrome, compact, and showShareButton props to conditionally hide chrome elements (PresenceAvatars, share/clone, unresolved badges) and adjust typography/spacing; propagated flags across Page/Database/Document header components.
Tests
apps/electron/src/renderer/lib/canvas-shell.test.ts
Added unit tests validating canvas-shell helpers and rendering predicates.
Docs & Config
docs/explorations/0104_...md, package.json
Added a long exploration doc describing canvas-first UX and migration plan; added pnpm.onlyBuiltDependencies with ["better-sqlite3","electron"] to package.json.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant App as App (Shell)
    participant Canvas as CanvasView
    participant View as Page/Database View
    participant Viewport as Viewport Service

    User->>App: open linked document (double-click / menu)
    App->>Canvas: focusLinkedDocument(docId)
    Canvas-->>App: return ViewportSnapshot
    App->>Viewport: store snapshot
    App->>App: set shell state -> page-focus / database-focus
    App->>View: render focused View (minimalChrome = true)
    View-->>User: show focused document
    User->>App: return to canvas
    App->>Viewport: retrieve snapshot
    App->>Canvas: restoreViewport(snapshot)
    Canvas-->>User: canvas restored with nodes and zoom
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 I hopped through nodes and menus, quick and bright,
I planted docks and palettes to guide the night,
I kept each zoom and place in a tiny snap,
I hide the chrome and tuck notes in my lap,
🥕 A rabbit’s cheer for canvas-first delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat(electron): add canvas-first workspace shell' clearly and accurately reflects the main architectural change—introducing a canvas-first workspace shell as the primary app entry point.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/canvas-first-shell

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 6, 2026

Preview removed for PR #5.

github-actions bot added a commit that referenced this pull request Mar 6, 2026
- limit the canvas-first card renderer to linked documents and note cards
- keep existing non-document canvas nodes on their default rendering path
- reduce the risk of breaking older canvases that contain shapes or other rich node types
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
apps/electron/src/renderer/components/DatabaseView.tsx (1)

1434-1526: ⚠️ Potential issue | 🟠 Major

minimalChrome is not applied in the empty-database branch.

You implemented compact behavior for the main toolbar, but the early return at Line 1399 still renders full chrome (presence + share) even when minimalChrome is true. This breaks focused-mode consistency on new/empty databases.

💡 Suggested fix
-  if (columns.length === 0) {
+  if (columns.length === 0) {
     return (
       <div ref={containerRef} className="flex-1 flex flex-col overflow-hidden">
         {/* Toolbar */}
-        <div className="flex items-center gap-2 p-3 border-b border-border bg-secondary">
+        <div
+          className={[
+            'flex items-center gap-2 border-b border-border',
+            minimalChrome ? 'bg-background/80 px-5 py-3 backdrop-blur-xl' : 'bg-secondary p-3'
+          ].join(' ')}
+        >
           <input
             type="text"
-            className="text-lg font-semibold border-none bg-transparent text-foreground outline-none placeholder:text-muted-foreground"
+            className={[
+              'border-none bg-transparent text-foreground outline-none placeholder:text-muted-foreground',
+              minimalChrome ? 'text-base font-semibold' : 'text-lg font-semibold'
+            ].join(' ')}
             value={database?.title || ''}
             onChange={(e) => update({ title: e.target.value })}
             placeholder="Untitled"
           />
-          <PresenceAvatars presence={presence} localDid={did} />
+          {!minimalChrome && <PresenceAvatars presence={presence} localDid={did} />}
           <div className="flex-1" />
-          <ShareButton docId={docId} docType="database" />
+          {!minimalChrome && <ShareButton docId={docId} docType="database" />}
         </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/DatabaseView.tsx` around lines 1434 -
1526, The empty-database early-return branch is still rendering full chrome
regardless of minimalChrome; update that branch so it respects minimalChrome by
conditionally rendering the same compact elements as the main toolbar.
Concretely, in the empty-database JSX ensure PresenceAvatars (presence, did),
the schema badge (schemaMetadata, setSchemaInfoModalOpen), the Clone button
(columns.length, setCloneSchemaModalOpen), the unresolved comments button
(commentUnresolvedCount, setSidebarOpen), the view switcher (setViewMode,
viewMode) and ShareButton (docId, docType) are wrapped with the same
!minimalChrome checks (or render their minimal variants) as in the non-empty
toolbar, so the UI is consistent in focused/minimal mode.
🧹 Nitpick comments (2)
apps/electron/src/renderer/components/PageView.tsx (1)

726-740: Add Playwright coverage for minimalChrome branches.

These branches hide multiple interaction affordances and change layout density; please add an Electron Playwright test that validates both modes and key actions still work in focused mode.

Based on learnings: "Test UI changes in Electron with Playwright after implementing".

Also applies to: 746-749

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/PageView.tsx` around lines 726 - 740,
Add an Electron Playwright test that exercises the PageView minimalChrome
branches: render PageView with minimalChrome false and true, assert
visibility/state of SyncIndicator (SyncIndicator), share button
(showShareButton), unresolved comment button (unresolvedCount and its
title/text), and PresenceAvatars (PresenceAvatars/localDid); simulate clicking
the unresolved comment button to ensure setSidebarOpen toggles the sidebar;
verify compact layout differences when compact/minimalChrome is set. Name the
test to cover both modes and include assertions that UI affordances are hidden
when minimalChrome is true and interactive behaviors (clicking unresolvedCount,
presence display) still work when focused.
apps/electron/src/renderer/components/SystemMenu.tsx (1)

10-21: Use type aliases for these object shapes.

RecentDocument and SystemMenuProps are currently interfaces; per coding guidelines, type is preferred for defining object shapes.

Proposed fix
-interface RecentDocument {
+type RecentDocument = {
   id: string
   title: string
   type: 'page' | 'database' | 'canvas'
-}
+}
 
-interface SystemMenuProps {
+type SystemMenuProps = {
   recentDocuments: RecentDocument[]
   onOpenDocument: (docId: string) => void
   onOpenSettings: () => void
   onAddShared: () => void
-}
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/SystemMenu.tsx` around lines 10 - 21,
Replace the interface declarations with type aliases: change the interface
RecentDocument to a type alias named RecentDocument that describes the same
object shape (id, title, type: 'page' | 'database' | 'canvas') and convert
SystemMenuProps to a type alias named SystemMenuProps that keeps the same
properties (recentDocuments: RecentDocument[], onOpenDocument, onOpenSettings,
onAddShared). Update any references if necessary (e.g., recentDocuments,
onOpenDocument) so the rest of the file continues to use the new type aliases
instead of interfaces.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/electron/src/renderer/App.tsx`:
- Line 287: Before navigating to settings, clear any pending transition timer
stored in transitionTimerRef to prevent the delayed overlay from firing after
navigation; update the handlers that call setShellState({ kind: 'settings' })
(the execute callbacks that currently do just setShellState) to call
clearTimeout(transitionTimerRef.current) and set transitionTimerRef.current =
null (or undefined) immediately before calling setShellState, and apply the same
change to all entry points that open Settings (the other execute handler and any
settings navigation code paths).
- Around line 193-209: The async handler handleCreateLinkedDocument should catch
rejections from create(schema, { title }) to prevent unhandled promise
rejections; wrap the existing body in a try-catch, await create inside the try,
and in catch log or surface the error via the app logger/UI per guidelines
(include the error object), then return early on failure; preserve the current
behavior of calling canvasViewRef.current?.addLinkedDocumentNode({ id:
newDocument.id, title, type }), setShellState({ kind: 'canvas-home' }) and
setActiveNodeId(homeCanvasId) only when newDocument is truthy, and ensure any
type narrowing on newDocument follows existing patterns used elsewhere in this
file.
- Around line 121-130: The fire-and-forget call to create(CanvasSchema, ...) can
reject and currently has no rejection handler, leaving creatingHomeCanvasRef and
the UI loading state stuck; add a .catch handler to the promise returned by
create() to handle errors explicitly: in the .catch capture the error, call your
UI error reporting (e.g., show a toast or set an error state) and/or clear any
loading state, then keep the existing .finally that sets
creatingHomeCanvasRef.current = false; ensure you still only call
setHomeCanvasId and setActiveNodeId inside the successful .then branch so
failures never leave the app in the "home canvas created" state.

In `@apps/electron/src/renderer/components/CanvasView.tsx`:
- Line 5: The import line currently uses "import type" which erases runtime
symbols; change the import so that Canvas and createNode are imported as runtime
values (not type-only) so createNode (used around lines where it's invoked) and
the JSX <Canvas /> component resolve at runtime; update the import to include
Canvas and createNode as regular imports while keeping type imports like
CanvasNode, Rect, CanvasHandle as type-only if desired.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md:
- Line 13: Replace the user-facing string "titlebar" with "title bar" throughout
the document; specifically update the occurrences of "titlebar" (e.g., the line
currently reading "- a top titlebar plus per-view top toolbars" and the other
instances noted at lines 54 and 420) so all references use the two-word form
"title bar" for consistency and readability.

In `@packages/canvas/src/hooks/useCanvas.ts`:
- Around line 302-307: The setViewportSnapshot function currently accepts
unvalidated external input; validate that snapshot.x, snapshot.y, and
snapshot.zoom are finite numbers (reject or coerce NaN/undefined), then clamp
zoom to safe bounds (e.g., introduce MIN_ZOOM and MAX_ZOOM constants and set
zoom = Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, snapshot.zoom))); optionally clamp
x/y to acceptable bounds if you have canvas limits (or at least ensure they are
finite), then assign the sanitized values to viewportRef.current.x/y/zoom and
call setViewportState(viewportRef.current.clone()) as before; update
setViewportSnapshot to perform these checks before assignment.

---

Outside diff comments:
In `@apps/electron/src/renderer/components/DatabaseView.tsx`:
- Around line 1434-1526: The empty-database early-return branch is still
rendering full chrome regardless of minimalChrome; update that branch so it
respects minimalChrome by conditionally rendering the same compact elements as
the main toolbar. Concretely, in the empty-database JSX ensure PresenceAvatars
(presence, did), the schema badge (schemaMetadata, setSchemaInfoModalOpen), the
Clone button (columns.length, setCloneSchemaModalOpen), the unresolved comments
button (commentUnresolvedCount, setSidebarOpen), the view switcher (setViewMode,
viewMode) and ShareButton (docId, docType) are wrapped with the same
!minimalChrome checks (or render their minimal variants) as in the non-empty
toolbar, so the UI is consistent in focused/minimal mode.

---

Nitpick comments:
In `@apps/electron/src/renderer/components/PageView.tsx`:
- Around line 726-740: Add an Electron Playwright test that exercises the
PageView minimalChrome branches: render PageView with minimalChrome false and
true, assert visibility/state of SyncIndicator (SyncIndicator), share button
(showShareButton), unresolved comment button (unresolvedCount and its
title/text), and PresenceAvatars (PresenceAvatars/localDid); simulate clicking
the unresolved comment button to ensure setSidebarOpen toggles the sidebar;
verify compact layout differences when compact/minimalChrome is set. Name the
test to cover both modes and include assertions that UI affordances are hidden
when minimalChrome is true and interactive behaviors (clicking unresolvedCount,
presence display) still work when focused.

In `@apps/electron/src/renderer/components/SystemMenu.tsx`:
- Around line 10-21: Replace the interface declarations with type aliases:
change the interface RecentDocument to a type alias named RecentDocument that
describes the same object shape (id, title, type: 'page' | 'database' |
'canvas') and convert SystemMenuProps to a type alias named SystemMenuProps that
keeps the same properties (recentDocuments: RecentDocument[], onOpenDocument,
onOpenSettings, onAddShared). Update any references if necessary (e.g.,
recentDocuments, onOpenDocument) so the rest of the file continues to use the
new type aliases instead of interfaces.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4babc0a-c8df-4a21-9c98-fc5ccd38aef2

📥 Commits

Reviewing files that changed from the base of the PR and between 06f062b and 5d51e65.

📒 Files selected for processing (11)
  • apps/electron/src/renderer/App.tsx
  • apps/electron/src/renderer/components/ActionDock.tsx
  • apps/electron/src/renderer/components/CanvasView.tsx
  • apps/electron/src/renderer/components/DatabaseView.tsx
  • apps/electron/src/renderer/components/DocumentHeader.tsx
  • apps/electron/src/renderer/components/PageView.tsx
  • apps/electron/src/renderer/components/SystemMenu.tsx
  • docs/explorations/0104_[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
  • package.json
  • packages/canvas/src/hooks/useCanvas.ts
  • packages/canvas/src/renderer/Canvas.tsx

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 6, 2026

Greptile Summary

This PR replaces the sidebar-driven shell with a canvas-first workspace: the home view is a persistent infinite canvas, page/database documents open as floating overlays via a compact focused-mode chrome, and a bottom action dock + top-right system menu replace the old sidebar controls. The shell state machine (canvas-home / page-focus / database-focus / settings) lives in App.tsx and drives both the overlay rendering and a 180 ms animation delay that lets the canvas visually zoom to a linked card before the overlay appears.

Key points from this review (items already discussed in prior rounds are excluded):

  • Canvas-linked nodes render with wrong icon and description (CanvasView.tsx lines 64–99): because App.tsx passes the full documents array (including canvases) to CanvasView, any canvas-typed linked node falls into the StickyNote icon branch and renders the copy "A lightweight note pinned directly to the workspace." — which is the note description, not a canvas description. This will affect any legacy canvas-linked nodes.
  • Linear node scan in focusLinkedDocument (CanvasView.tsx line 199): Array.from(nodesMap.values()).find(...) materialises a full copy of the Y.Map on every double-click-to-open event. A direct for...of loop over nodesMap.values() avoids the allocation.
  • handleReturnHome dependency on shellState (App.tsx line 258): because shellState is in the useCallback dep array, the callback is recreated on every navigation transition and causes an ActionDock re-render at exactly the moments where smooth animation matters most. Reading shell state via a ref at call-time keeps the callback stable.
  • The new canvas-shell.ts utility module and its accompanying test suite are well-structured and easy to extend.
  • The bootstrapHomeCanvas / stale-homeCanvasId guards introduced in this round (addressing prior review comments) correctly handle the external-deletion and retry scenarios.

Confidence Score: 3/5

  • Mergeable after addressing the canvas-node icon/description bug; remaining issues are performance optimisations and do not affect correctness for the current UI flows
  • The core shell state machine is well-structured and the prior-round critical bugs (import type, infinite loading, viewport coordinate, palette scope) were addressed. Two new issues remain: a logic bug where canvas-typed linked nodes render with the wrong StickyNote icon and misleading note description text, and a performance concern where focusLinkedDocument allocates a full node-array copy on every call. The handleReturnHome callback instability is a minor render-performance concern that doesn't affect correctness.
  • apps/electron/src/renderer/components/CanvasView.tsx (canvas-linked node rendering and linear scan), apps/electron/src/renderer/App.tsx (handleReturnHome stability)

Important Files Changed

Filename Overview
apps/electron/src/renderer/App.tsx Heavy rewrite replacing sidebar+select model with canvas-home shell state machine; several edge-case bugs (infinite loading, stale homeCanvasId, missing clearTransitionTimer on canvas branch) were flagged in prior review rounds and partially addressed; handleReturnHome is recreated on every shell state change causing unnecessary ActionDock re-renders
apps/electron/src/renderer/components/CanvasView.tsx New canvas shell component exposing imperative handle; node placement uses screen-space viewport coordinates as canvas coordinates (flagged previously); canvas-linked nodes render with wrong StickyNote icon and "lightweight note" description; focusLinkedDocument creates a full array copy on every call
apps/electron/src/renderer/components/ActionDock.tsx New bottom dock component with canvas-home / focused mode toggle; clean, accessible implementation with type="button" on all interactive elements
apps/electron/src/renderer/components/SystemMenu.tsx New system menu component; the previously flagged Plus icon for the System theme option has been corrected to Monitor
apps/electron/src/renderer/lib/canvas-shell.ts Thin utility module for canvas-shell note detection and linked-document card rendering; clean, well-scoped functions
packages/canvas/src/hooks/useCanvas.ts Added getViewportSnapshot / setViewportSnapshot to the hook's public API; correctly returns plain {x, y, zoom} from viewport clone; no issues

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    LOAD[App mounts / isLoading]
    LOAD -->|canvases.length === 0| BOOTSTRAP[bootstrapHomeCanvas]
    LOAD -->|canvases exist| SET_HOME[setHomeCanvasId to most-recent canvas]
    BOOTSTRAP -->|success| SET_HOME
    BOOTSTRAP -->|failure| ERROR_SCREEN[Error screen with Retry button]
    ERROR_SCREEN -->|user clicks Retry| BOOTSTRAP

    SET_HOME --> CANVAS_HOME

    subgraph Shell States
        CANVAS_HOME["canvas-home\n(CanvasView fullscreen)"]
        PAGE_FOCUS["page-focus\n(PageView overlay)"]
        DB_FOCUS["database-focus\n(DatabaseView overlay)"]
        SETTINGS["settings\n(SettingsView overlay)"]
    end

    CANVAS_HOME -->|double-click linked page/db node\nfocusDocument + 180 ms timer| PAGE_FOCUS
    CANVAS_HOME -->|double-click linked db node\nfocusDocument + 180 ms timer| DB_FOCUS
    CANVAS_HOME -->|ActionDock / palette: Open Settings| SETTINGS
    CANVAS_HOME -->|ActionDock: Create Page/Database\nhandleCreateLinkedDocument| CANVAS_HOME

    PAGE_FOCUS -->|ActionDock: Canvas button\nhandleReturnHome| CANVAS_HOME
    DB_FOCUS -->|ActionDock: Canvas button\nhandleReturnHome| CANVAS_HOME
    SETTINGS -->|SettingsView onClose\nhandleReturnHome| CANVAS_HOME

    PAGE_FOCUS -->|clearTransitionTimer called by any transition| CANCEL_TIMER[cancel pending\noverlay timer]
    DB_FOCUS --> CANCEL_TIMER
    SETTINGS --> CANCEL_TIMER
    CANCEL_TIMER --> CANVAS_HOME
Loading

Last reviewed commit: 91d57de

- extract the canvas shell card-selection logic into a renderer helper
- mark shell-created notes explicitly instead of styling every card node
- add a regression test so generic canvas cards keep their default rendering
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/electron/src/renderer/components/CanvasView.tsx (1)

5-5: ⚠️ Potential issue | 🔴 Critical

Import Canvas and createNode as runtime values (not import type).

Line 5 currently erases both symbols at compile time, but they are used at runtime (Line 137 / Line 157 for createNode, Line 260 for <Canvas />).

Fix
-import type { CanvasNode, Rect, Canvas, createNode, type CanvasHandle } from '@xnetjs/canvas'
+import type { CanvasHandle, CanvasNode, Rect } from '@xnetjs/canvas'
+import { Canvas, createNode } from '@xnetjs/canvas'

Run this to verify the mismatch quickly:

#!/bin/bash
set -euo pipefail
FILE="apps/electron/src/renderer/components/CanvasView.tsx"

echo "=== Import line ==="
sed -n '5p' "$FILE"

echo "=== Runtime usage of createNode ==="
rg -nP '\bcreateNode\s*\(' "$FILE"

echo "=== Runtime usage of Canvas component ==="
rg -n '<Canvas\b' "$FILE"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/CanvasView.tsx` at line 5, The imports
for Canvas and createNode are currently marked as type-only and get erased at
compile time, causing runtime errors where createNode() (used around the
createNode calls) and the <Canvas /> component (used where Canvas is rendered)
are referenced; update the import from '@xnetjs/canvas' so that Canvas and
createNode are imported as runtime values (not type-only) while keeping purely
type imports (CanvasNode, Rect, CanvasHandle) as types, i.e. split or adjust the
import list so Canvas and createNode are value imports and the others remain
type imports.
🧹 Nitpick comments (3)
apps/electron/src/renderer/components/CanvasView.tsx (2)

27-44: Prefer type aliases for these object-shape declarations.

Lines 27-44 define object shapes with interface; this repo standard prefers type.

Suggested refactor
-interface ViewportSnapshot {
+type ViewportSnapshot = {
   x: number
   y: number
   zoom: number
-}
+}

-interface CanvasViewProps {
+type CanvasViewProps = {
   docId: string
   documents?: LinkedDocumentItem[]
   onOpenDocument?: (docId: string, docType: Exclude<LinkedDocType, 'canvas'>) => void
-}
+}

-export interface CanvasViewHandle {
+export type CanvasViewHandle = {
   addLinkedDocumentNode: (document: LinkedDocumentItem) => void
   addCanvasNote: () => void
   focusLinkedDocument: (docId: string) => ViewportSnapshot | null
   restoreViewport: (snapshot: ViewportSnapshot) => void
-}
+}

As per coding guidelines: Prefer type over interface for defining object shapes in TypeScript.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/CanvasView.tsx` around lines 27 - 44,
The declarations ViewportSnapshot, CanvasViewProps, and CanvasViewHandle are
currently defined with interface but the repo prefers type aliases; refactor
each interface to a type alias with the same name and identical shape (e.g.,
convert "interface ViewportSnapshot { ... }" to "type ViewportSnapshot = { ...
}"), keeping all property names and signatures (including addLinkedDocumentNode,
addCanvasNote, focusLinkedDocument, restoreViewport) unchanged so all usages
remain valid.

224-287: Add one Playwright flow for the new canvas-shell interaction path.

This UI now depends on interactions (double-click linked card open + viewport actions) that are hard to protect with unit tests alone; one Electron Playwright regression would reduce breakage risk.

Based on learnings: Test UI changes in Electron with Playwright after implementing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/CanvasView.tsx` around lines 224 - 287,
Add a Playwright Electron test that covers the new canvas-shell interaction:
locate a canvas node rendered via Canvas (use
shouldRenderCanvasShellCard/renderNodeCard to identify the shell card), simulate
a double-click on a linked card and assert onOpenDocument behavior opens the
expected document (matching linkedNodeId and linkedType), then exercise viewport
actions by clicking the Fit and Reset IconButton controls (which call
canvasRef.current?.fitToContent and resetView) and assert the canvas
transform/zoom changed and then returned; place the flow as an end-to-end
Electron Playwright test to catch regressions in onNodeDoubleClick and the
toolbar actions.
apps/electron/src/renderer/lib/canvas-shell.ts (1)

5-9: Use a type alias for LinkedDocumentItem.

Line 5 uses interface, while this codebase standardizes object-shape declarations with type.

Suggested refactor
-export interface LinkedDocumentItem {
+export type LinkedDocumentItem = {
   id: string
   title: string
   type: LinkedDocType
-}
+}

As per coding guidelines: Prefer type over interface for defining object shapes in TypeScript.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/lib/canvas-shell.ts` around lines 5 - 9, Replace
the interface declaration for LinkedDocumentItem with a TypeScript type alias to
match the codebase standard: change the declaration of LinkedDocumentItem (which
currently uses "interface LinkedDocumentItem { id: string; title: string; type:
LinkedDocType }") to a type alias using "type LinkedDocumentItem = { ... }",
preserving the property names and types (id, title, type: LinkedDocType); update
any imports/exports if needed to keep usages intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/electron/src/renderer/components/CanvasView.tsx`:
- Line 5: The imports for Canvas and createNode are currently marked as
type-only and get erased at compile time, causing runtime errors where
createNode() (used around the createNode calls) and the <Canvas /> component
(used where Canvas is rendered) are referenced; update the import from
'@xnetjs/canvas' so that Canvas and createNode are imported as runtime values
(not type-only) while keeping purely type imports (CanvasNode, Rect,
CanvasHandle) as types, i.e. split or adjust the import list so Canvas and
createNode are value imports and the others remain type imports.

---

Nitpick comments:
In `@apps/electron/src/renderer/components/CanvasView.tsx`:
- Around line 27-44: The declarations ViewportSnapshot, CanvasViewProps, and
CanvasViewHandle are currently defined with interface but the repo prefers type
aliases; refactor each interface to a type alias with the same name and
identical shape (e.g., convert "interface ViewportSnapshot { ... }" to "type
ViewportSnapshot = { ... }"), keeping all property names and signatures
(including addLinkedDocumentNode, addCanvasNote, focusLinkedDocument,
restoreViewport) unchanged so all usages remain valid.
- Around line 224-287: Add a Playwright Electron test that covers the new
canvas-shell interaction: locate a canvas node rendered via Canvas (use
shouldRenderCanvasShellCard/renderNodeCard to identify the shell card), simulate
a double-click on a linked card and assert onOpenDocument behavior opens the
expected document (matching linkedNodeId and linkedType), then exercise viewport
actions by clicking the Fit and Reset IconButton controls (which call
canvasRef.current?.fitToContent and resetView) and assert the canvas
transform/zoom changed and then returned; place the flow as an end-to-end
Electron Playwright test to catch regressions in onNodeDoubleClick and the
toolbar actions.

In `@apps/electron/src/renderer/lib/canvas-shell.ts`:
- Around line 5-9: Replace the interface declaration for LinkedDocumentItem with
a TypeScript type alias to match the codebase standard: change the declaration
of LinkedDocumentItem (which currently uses "interface LinkedDocumentItem { id:
string; title: string; type: LinkedDocType }") to a type alias using "type
LinkedDocumentItem = { ... }", preserving the property names and types (id,
title, type: LinkedDocType); update any imports/exports if needed to keep usages
intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 62df4a0e-3d4d-4acc-80a7-7b99cd03ce95

📥 Commits

Reviewing files that changed from the base of the PR and between 5d51e65 and 6a0f4fb.

📒 Files selected for processing (3)
  • apps/electron/src/renderer/components/CanvasView.tsx
  • apps/electron/src/renderer/lib/canvas-shell.test.ts
  • apps/electron/src/renderer/lib/canvas-shell.ts

- catch home-canvas creation failures

- catch linked document creation failures

- restore runtime canvas imports

- hide onboarding after nodes exist

- add a debug-panel action to the system menu

- skip zoom-style shell transitions for reduced motion

- sanitize restored viewport snapshots
github-actions bot added a commit that referenced this pull request Mar 6, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
apps/electron/src/renderer/App.tsx (1)

296-301: ⚠️ Potential issue | 🟡 Minor

Clear pending transition timer before opening Settings.

A delayed overlay transition scheduled via transitionTimerRef (Line 175) can still fire after navigating to settings. Both the palette command (Line 300) and the SystemMenu callback (Line 378) should clear the timer first.

Suggested fix
+  const handleOpenSettings = useCallback(() => {
+    clearTransitionTimer()
+    setShellState({ kind: 'settings' })
+  }, [clearTransitionTimer])
+
   const paletteCommands = useMemo<PaletteCommand[]>(
     () => [
       // ... other commands ...
       {
         id: 'open-settings',
         name: 'Open Settings',
         description: 'Open the system settings overlay',
         icon: 'settings',
-        execute: () => setShellState({ kind: 'settings' })
+        execute: handleOpenSettings
       },

And update the SystemMenu callback at Line 378:

-            onOpenSettings={() => setShellState({ kind: 'settings' })}
+            onOpenSettings={handleOpenSettings}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/App.tsx` around lines 296 - 301, The delayed
overlay transition timer referenced by transitionTimerRef can fire after
navigation to settings; before calling setShellState({ kind: 'settings' }) in
the 'open-settings' command (id: 'open-settings') and in the SystemMenu
callback, clear any pending timer by checking transitionTimerRef.current and
calling clearTimeout(transitionTimerRef.current) (and then setting
transitionTimerRef.current = null) so the pending transition cannot run after
the settings overlay opens.
🧹 Nitpick comments (5)
docs/explorations/0104_[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md (2)

548-568: Refine the return type to match implementation.

The function always returns type: 'embed', but the return type declares type: 'card' | 'embed'. Consider narrowing the type to accurately reflect the implementation:

♻️ Proposed fix
 export function createLinkedNodeAtViewport(
   intent: CreateIntent,
   viewport: ViewportSnapshot
 ): {
-  type: 'card' | 'embed'
+  type: 'embed'
   linkedSchema: 'page' | 'database' | 'canvas'
   position: { x: number; y: number; width: number; height: number }
 } {

Alternatively, if 'card' is intentionally supported, add the conditional logic to return it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
around lines 548 - 568, The declared return type for createLinkedNodeAtViewport
is too broad (type: 'card' | 'embed') while the implementation always returns
type: 'embed'; either tighten the signature to return only 'embed' or implement
branching logic in createLinkedNodeAtViewport to return 'card' where intended
(for example based on intent values like 'card' or 'canvas-card'), and ensure
linkedSchema mapping and position logic remain correct when adding that
conditional.

511-520: Consider completing the validation checklist.

The implementation checklist shows most items as complete, but the validation checklist remains entirely unchecked. Since the PR summary mentions validation steps were recorded (pnpm typecheck, pnpm test), consider checking off applicable validation items or documenting which remain to be verified before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
around lines 511 - 520, The validation checklist at the bottom is still all
unchecked; please either mark each applicable item (e.g., "Verify the app always
lands on a canvas", "Verify a new page/database appears at the current viewport
center", "Verify keyboard-only flows still work", "Verify reduced-motion mode
falls back", "Verify Playwright/Electron manual checks cover canvas home, page
focus, database focus, and collaboration affordances") as completed or add notes
documenting which items remain unverified, how to reproduce them, which
test/command validated them (e.g., pnpm typecheck, pnpm test,
Playwright/Electron), and who/when validated each item so reviewers can confirm
readiness for merge.
apps/electron/src/renderer/App.tsx (1)

34-40: Prefer type over interface for object shapes.

Per coding guidelines, use type instead of interface for defining object shapes in TypeScript.

Suggested change
-interface DocumentItem {
+type DocumentItem = {
   id: string
   title: string
   type: DocType
   createdAt?: number
   updatedAt?: number
 }

As per coding guidelines: "Prefer type over interface for defining object shapes in TypeScript"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/App.tsx` around lines 34 - 40, Replace the
DocumentItem interface with a type alias per project conventions: change
"interface DocumentItem { ... }" to "type DocumentItem = { id: string; title:
string; type: DocType; createdAt?: number; updatedAt?: number }", keeping the
same property names and optional modifiers (and update any imports/exports that
reference DocumentItem if necessary).
apps/electron/src/renderer/components/CanvasView.tsx (2)

28-38: Prefer type over interface for internal object shapes.

ViewportSnapshot and CanvasViewProps are not exported and don't require declaration merging. Per coding guidelines, prefer type for object shapes.

Suggested change
-interface ViewportSnapshot {
+type ViewportSnapshot = {
   x: number
   y: number
   zoom: number
 }

-interface CanvasViewProps {
+type CanvasViewProps = {
   docId: string
   documents?: LinkedDocumentItem[]
   onOpenDocument?: (docId: string, docType: Exclude<LinkedDocType, 'canvas'>) => void
 }

As per coding guidelines: "Prefer type over interface for defining object shapes in TypeScript"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/CanvasView.tsx` around lines 28 - 38,
Replace the two internal interfaces with type aliases: change the declarations
of ViewportSnapshot and CanvasViewProps to use type instead of interface while
preserving the same property shapes (including docId, documents?:
LinkedDocumentItem[], and onOpenDocument?: (docId: string, docType:
Exclude<LinkedDocType, 'canvas'>) => void). Update any local references to these
names if necessary; no export or behavior changes are needed.

5-26: Import order: external packages should precede internal packages.

Per coding guidelines, the import order should be: type-only imports → external packages → internal packages → local relative imports. Currently, lucide-react and react (external) come after @xnetjs/* (internal).

Suggested reordering
 import type { CanvasHandle, CanvasNode, Rect } from '@xnetjs/canvas'
+import { Compass, Database, FileText, Maximize2, StickyNote } from 'lucide-react'
+import React, {
+  forwardRef,
+  useEffect,
+  useState,
+  useCallback,
+  useRef,
+  useImperativeHandle,
+  useMemo
+} from 'react'
 import { Canvas, createNode } from '@xnetjs/canvas'
 import { CanvasSchema } from '@xnetjs/data'
 import { useNode, useIdentity } from '@xnetjs/react'
 import { IconButton } from '@xnetjs/ui'
-import { Compass, Database, FileText, Maximize2, StickyNote } from 'lucide-react'
-import React, {
-  forwardRef,
-  useEffect,
-  useState,
-  useCallback,
-  useRef,
-  useImperativeHandle,
-  useMemo
-} from 'react'
 import {
   createCanvasShellNoteProperties,
   isCanvasShellNote,
   shouldRenderCanvasShellCard,
   type LinkedDocType,
   type LinkedDocumentItem
 } from '../lib/canvas-shell'

As per coding guidelines: "External package imports should follow type-only imports" and "Internal package imports should follow external packages"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/CanvasView.tsx` around lines 5 - 26,
Reorder the imports in CanvasView.tsx so type-only imports (CanvasHandle,
CanvasNode, Rect) remain first, then external packages (React and named imports
like forwardRef, useEffect, useState, useCallback, useRef, useImperativeHandle,
useMemo and lucide-react icons Compass, Database, FileText, Maximize2,
StickyNote), followed by internal `@xnetjs` packages (Canvas, createNode,
CanvasSchema, useNode, useIdentity, IconButton) and finally the local relative
import (createCanvasShellNoteProperties, isCanvasShellNote,
shouldRenderCanvasShellCard, LinkedDocType, LinkedDocumentItem from
../lib/canvas-shell); adjust import grouping/order but keep the same imported
symbols and semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/electron/src/renderer/App.tsx`:
- Around line 296-301: The delayed overlay transition timer referenced by
transitionTimerRef can fire after navigation to settings; before calling
setShellState({ kind: 'settings' }) in the 'open-settings' command (id:
'open-settings') and in the SystemMenu callback, clear any pending timer by
checking transitionTimerRef.current and calling
clearTimeout(transitionTimerRef.current) (and then setting
transitionTimerRef.current = null) so the pending transition cannot run after
the settings overlay opens.

---

Nitpick comments:
In `@apps/electron/src/renderer/App.tsx`:
- Around line 34-40: Replace the DocumentItem interface with a type alias per
project conventions: change "interface DocumentItem { ... }" to "type
DocumentItem = { id: string; title: string; type: DocType; createdAt?: number;
updatedAt?: number }", keeping the same property names and optional modifiers
(and update any imports/exports that reference DocumentItem if necessary).

In `@apps/electron/src/renderer/components/CanvasView.tsx`:
- Around line 28-38: Replace the two internal interfaces with type aliases:
change the declarations of ViewportSnapshot and CanvasViewProps to use type
instead of interface while preserving the same property shapes (including docId,
documents?: LinkedDocumentItem[], and onOpenDocument?: (docId: string, docType:
Exclude<LinkedDocType, 'canvas'>) => void). Update any local references to these
names if necessary; no export or behavior changes are needed.
- Around line 5-26: Reorder the imports in CanvasView.tsx so type-only imports
(CanvasHandle, CanvasNode, Rect) remain first, then external packages (React and
named imports like forwardRef, useEffect, useState, useCallback, useRef,
useImperativeHandle, useMemo and lucide-react icons Compass, Database, FileText,
Maximize2, StickyNote), followed by internal `@xnetjs` packages (Canvas,
createNode, CanvasSchema, useNode, useIdentity, IconButton) and finally the
local relative import (createCanvasShellNoteProperties, isCanvasShellNote,
shouldRenderCanvasShellCard, LinkedDocType, LinkedDocumentItem from
../lib/canvas-shell); adjust import grouping/order but keep the same imported
symbols and semantics.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md:
- Around line 548-568: The declared return type for createLinkedNodeAtViewport
is too broad (type: 'card' | 'embed') while the implementation always returns
type: 'embed'; either tighten the signature to return only 'embed' or implement
branching logic in createLinkedNodeAtViewport to return 'card' where intended
(for example based on intent values like 'card' or 'canvas-card'), and ensure
linkedSchema mapping and position logic remain correct when adding that
conditional.
- Around line 511-520: The validation checklist at the bottom is still all
unchecked; please either mark each applicable item (e.g., "Verify the app always
lands on a canvas", "Verify a new page/database appears at the current viewport
center", "Verify keyboard-only flows still work", "Verify reduced-motion mode
falls back", "Verify Playwright/Electron manual checks cover canvas home, page
focus, database focus, and collaboration affordances") as completed or add notes
documenting which items remain unverified, how to reproduce them, which
test/command validated them (e.g., pnpm typecheck, pnpm test,
Playwright/Electron), and who/when validated each item so reviewers can confirm
readiness for merge.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 834a9beb-e9df-4891-88b5-8f981d67fec9

📥 Commits

Reviewing files that changed from the base of the PR and between 6a0f4fb and b37a514.

📒 Files selected for processing (5)
  • apps/electron/src/renderer/App.tsx
  • apps/electron/src/renderer/components/CanvasView.tsx
  • apps/electron/src/renderer/components/SystemMenu.tsx
  • docs/explorations/0104_[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
  • packages/canvas/src/hooks/useCanvas.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/canvas/src/hooks/useCanvas.ts
  • apps/electron/src/renderer/components/SystemMenu.tsx

- add retryable home-canvas bootstrap recovery

- limit palette recents and clear transitions before settings

- hide non-actionable open affordances on canvas-linked cards

- allow electron install scripts in fresh worktrees

- record completed validation for the canvas-first exploration
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (3)
docs/explorations/0104_[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md (1)

13-13: ⚠️ Potential issue | 🟡 Minor

Use “title bar” consistently in user-facing text.

Line 13, Line 54, and Line 420 still use “titlebar”; please normalize to “title bar” for consistency/readability.

Also applies to: 54-54, 420-420

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
at line 13, The document uses the compound "titlebar" in multiple places; update
every occurrence of the string "titlebar" to the two-word form "title bar"
(ensure exact replacement of "titlebar" in the body, headings and metadata) so
user-facing text is consistent—search for the token "titlebar" and replace with
"title bar" across the file.
apps/electron/src/renderer/App.tsx (2)

224-285: ⚠️ Potential issue | 🟡 Minor

Clear pending transition timer before forcing canvas-home state.

focusDocument schedules delayed overlay open (Line 197). In Line 237, Line 248, and Line 277, handlers switch shell state without clearing that timer, so a stale delayed overlay can still open afterward.

⏱️ Proposed fix
 const handleCreateLinkedDocument = useCallback(
   async (type: Exclude<DocType, 'canvas'>) => {
+    clearTransitionTimer()
     try {
       const schema = type === 'page' ? PageSchema : DatabaseSchema
@@
     } catch (error) {
       console.error('Failed to create linked document', toError(error))
     }
   },
-  [create, homeCanvasId, setActiveNodeId]
+  [clearTransitionTimer, create, homeCanvasId, setActiveNodeId]
 )

 const handleCreateCanvasNote = useCallback(() => {
+  clearTransitionTimer()
   canvasViewRef.current?.addCanvasNote()
   setShellState({ kind: 'canvas-home' })
   setActiveNodeId(homeCanvasId)
-}, [homeCanvasId, setActiveNodeId])
+}, [clearTransitionTimer, homeCanvasId, setActiveNodeId])

 const handleAddShared = useCallback(
   async (input: AddSharedInput) => {
@@
     if (input.docType === 'canvas') {
+      clearTransitionTimer()
       setHomeCanvasId(input.docId)
       setShellState({ kind: 'canvas-home' })
       setActiveNodeId(input.docId)
       return
     }
@@
   },
-  [focusDocument, setActiveNodeId]
+  [clearTransitionTimer, focusDocument, setActiveNodeId]
 )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/App.tsx` around lines 224 - 285, Call
clearTransitionTimer() before forcing the canvas-home state to prevent any stale
delayed overlay from opening; add that call at the start (before setShellState({
kind: 'canvas-home' })) in handleCreateLinkedDocument, handleCreateCanvasNote,
and handleAddShared, and ensure clearTransitionTimer is included in each
useCallback dependency array (handleCreateLinkedDocument's and handleAddShared's
dependencies currently omit it). Also verify handleReturnHome still calls
clearTransitionTimer (keep it) so all code paths that set canvas-home clear the
pending timer.

264-285: ⚠️ Potential issue | 🟠 Major

Wrap handleAddShared in try/catch to prevent unhandled rejections.

Line 267 awaits configureShareSession(...) without local error handling. If this rejects, the async handler can fail noisily and skip subsequent state updates.

🛡️ Proposed hardening
 const handleAddShared = useCallback(
   async (input: AddSharedInput) => {
-    if (input.share) {
-      await window.__xnetIpcSyncManager?.configureShareSession({
-        signalingUrl: input.share.endpoint,
-        ucanToken: input.share.token,
-        transport: input.share.transport,
-        iceServers: input.share.iceServers
-      })
-    }
-
-    if (input.docType === 'canvas') {
-      setHomeCanvasId(input.docId)
-      setShellState({ kind: 'canvas-home' })
-      setActiveNodeId(input.docId)
-      return
-    }
-
-    focusDocument(input.docId, input.docType, false)
+    try {
+      if (input.share) {
+        await window.__xnetIpcSyncManager?.configureShareSession({
+          signalingUrl: input.share.endpoint,
+          ucanToken: input.share.token,
+          transport: input.share.transport,
+          iceServers: input.share.iceServers
+        })
+      }
+
+      if (input.docType === 'canvas') {
+        setHomeCanvasId(input.docId)
+        setShellState({ kind: 'canvas-home' })
+        setActiveNodeId(input.docId)
+        return
+      }
+
+      focusDocument(input.docId, input.docType, false)
+    } catch (error) {
+      console.error('Failed to add shared document', toError(error))
+    }
   },
   [focusDocument, setActiveNodeId]
 )

Based on learnings, "Applies to **/*.{ts,tsx} : Use try-catch with type narrowing: err instanceof Error ? err : new Error(String(err))".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/App.tsx` around lines 264 - 285, Wrap the await
call to window.__xnetIpcSyncManager?.configureShareSession inside a try/catch in
the handleAddShared async callback so a rejected promise doesn't skip the rest
of the function; on catch narrow the error to an Error with the pattern (err
instanceof Error ? err : new Error(String(err))) and log it (e.g., console.error
or an existing logger), then continue to run the post-share logic
(setHomeCanvasId, setShellState, setActiveNodeId, or focusDocument) as
appropriate; update the handleAddShared function to perform this local error
handling around configureShareSession so unhandled rejections are prevented.
🧹 Nitpick comments (5)
docs/explorations/0104_[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md (2)

523-524: Add trailing commas after the year for date-parenthetical style consistency.

Consider “March 6, 2026, with …” and “March 6, 2026, confirmed …” for consistent prose style in these validation notes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
around lines 523 - 524, Update the two validation-note sentences to insert a
trailing comma after the year "2026" for consistent date-parenthetical style:
change "March 6, 2026 with `pnpm typecheck`..." to "March 6, 2026, with `pnpm
typecheck`..." and change "March 6, 2026 confirmed the first-run shell..." to
"March 6, 2026, confirmed the first-run shell..."; these edits are in the two
lines containing the automated and manual validation notes (the phrases starting
"Automated validation completed on March 6, 2026 with..." and "Manual Electron
validation on March 6, 2026 confirmed...").

608-613: Preserve prior mode when closing settings in the example reducer.

close-settings always returns canvas-home, which drops context if settings was opened from a focused view. In the sample state model, this conflicts with the stated “restore context” interaction.

♻️ Proposed refactor
 export type ShellMode =
   | { kind: 'canvas-home'; canvasId: string }
   | { kind: 'page-focus'; canvasId: string; pageId: string; returnViewport: ViewportSnapshot }
   | {
       kind: 'database-focus'
       canvasId: string
       databaseId: string
       returnViewport: ViewportSnapshot
     }
-  | { kind: 'settings'; canvasId: string }
+  | {
+      kind: 'settings'
+      canvasId: string
+      previous: Exclude<ShellMode, { kind: 'settings' }>
+    }

 ...
     case 'open-settings':
-      return { kind: 'settings', canvasId: state.canvasId }
+      if (state.kind === 'settings') return state
+      return { kind: 'settings', canvasId: state.canvasId, previous: state }

     case 'close-settings':
-      return { kind: 'canvas-home', canvasId: state.canvasId }
+      return state.kind === 'settings' ? state.previous : state
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
around lines 608 - 613, The reducer currently maps 'open-settings' to { kind:
'settings', canvasId: state.canvasId } and 'close-settings' to { kind:
'canvas-home', canvasId: state.canvasId }, which drops context; modify the state
produced by the 'open-settings' branch to preserve the prior view (e.g., include
previousKind or previousMode = state.kind) and update the 'close-settings'
branch to restore that preserved value (return { kind: state.previousKind ??
'canvas-home', canvasId: state.canvasId }) so closing settings returns the prior
mode when present and falls back to 'canvas-home' otherwise.
apps/electron/src/renderer/App.tsx (1)

5-19: Align import order with repo rules.

External runtime imports (react) should be placed before internal @xnetjs/* runtime imports after type-only imports.

As per coding guidelines, "Type-only imports must come first" and "Internal package imports should follow external packages".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/App.tsx` around lines 5 - 19, The import order in
App.tsx violates repo rules: move the external runtime import "import React, {
useCallback, useEffect, useMemo, useRef, useState } from 'react'" (and any other
external packages) to appear before internal package imports (those starting
with "@xnetjs/*"), while keeping type-only imports like "import type {
PaletteCommand }" at the very top; rearrange lines so type-only imports come
first, then external runtime imports (React), then internal `@xnetjs` imports
(e.g., PageSchema, useDevTools, useQuery, CommandPalette), and finally local
relative imports (components such as ActionDock, CanvasView, etc.).
apps/electron/src/renderer/components/CanvasView.tsx (2)

28-45: Avoid duplicating ViewportSnapshot across modules.

ViewportSnapshot is defined here and again in apps/electron/src/renderer/App.tsx (Line 22-26 there). Exporting and reusing one type will prevent silent drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/CanvasView.tsx` around lines 28 - 45,
The type ViewportSnapshot is duplicated; extract it into a single shared type
and import it where needed: create/export a shared type (e.g., export type
ViewportSnapshot = { x: number; y: number; zoom: number }) in a common types
module, replace the local declaration in CanvasView.tsx and the duplicate in
App.tsx with an import of that shared type, and ensure CanvasViewHandle still
references the imported ViewportSnapshot so the add/focus/restore signatures
remain unchanged.

5-26: Reorder imports to match repository grouping rules.

At Line 6-19, internal @xnetjs/* runtime imports appear before external runtime imports (react, lucide-react). That breaks the configured import order.

♻️ Proposed import regrouping
 import type { CanvasHandle, CanvasNode, Rect } from '@xnetjs/canvas'
-import { Canvas, createNode } from '@xnetjs/canvas'
-import { CanvasSchema } from '@xnetjs/data'
-import { useNode, useIdentity } from '@xnetjs/react'
-import { IconButton } from '@xnetjs/ui'
-import { Compass, Database, FileText, Maximize2, StickyNote } from 'lucide-react'
 import React, {
   forwardRef,
   useCallback,
   useEffect,
   useImperativeHandle,
   useMemo,
   useRef,
   useState
 } from 'react'
+import { Compass, Database, FileText, Maximize2, StickyNote } from 'lucide-react'
+import { Canvas, createNode } from '@xnetjs/canvas'
+import { CanvasSchema } from '@xnetjs/data'
+import { useNode, useIdentity } from '@xnetjs/react'
+import { IconButton } from '@xnetjs/ui'

As per coding guidelines, "Internal package imports should follow external packages" and "External package imports should follow type-only imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/components/CanvasView.tsx` around lines 5 - 26,
Reorder the import block so type-only imports come first, then external runtime
packages, then internal `@xnetjs/`* runtime imports: move React and lucide-react
imports (React, forwardRef, useCallback, useEffect, useImperativeHandle,
useMemo, useRef, useState and Compass, Database, FileText, Maximize2,
StickyNote) ahead of the `@xnetjs` runtime imports (Canvas, createNode, useNode,
useIdentity, IconButton), while keeping the type imports (CanvasHandle,
CanvasNode, Rect, CanvasSchema, LinkedDocType, LinkedDocumentItem) grouped as
type-only; update the top-level import order around symbols Canvas, createNode,
CanvasHandle, CanvasNode, Rect, CanvasSchema, useNode, useIdentity, IconButton,
Compass, Database, FileText, Maximize2, StickyNote, and React accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/electron/src/renderer/App.tsx`:
- Around line 224-285: Call clearTransitionTimer() before forcing the
canvas-home state to prevent any stale delayed overlay from opening; add that
call at the start (before setShellState({ kind: 'canvas-home' })) in
handleCreateLinkedDocument, handleCreateCanvasNote, and handleAddShared, and
ensure clearTransitionTimer is included in each useCallback dependency array
(handleCreateLinkedDocument's and handleAddShared's dependencies currently omit
it). Also verify handleReturnHome still calls clearTransitionTimer (keep it) so
all code paths that set canvas-home clear the pending timer.
- Around line 264-285: Wrap the await call to
window.__xnetIpcSyncManager?.configureShareSession inside a try/catch in the
handleAddShared async callback so a rejected promise doesn't skip the rest of
the function; on catch narrow the error to an Error with the pattern (err
instanceof Error ? err : new Error(String(err))) and log it (e.g., console.error
or an existing logger), then continue to run the post-share logic
(setHomeCanvasId, setShellState, setActiveNodeId, or focusDocument) as
appropriate; update the handleAddShared function to perform this local error
handling around configureShareSession so unhandled rejections are prevented.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md:
- Line 13: The document uses the compound "titlebar" in multiple places; update
every occurrence of the string "titlebar" to the two-word form "title bar"
(ensure exact replacement of "titlebar" in the body, headings and metadata) so
user-facing text is consistent—search for the token "titlebar" and replace with
"title bar" across the file.

---

Nitpick comments:
In `@apps/electron/src/renderer/App.tsx`:
- Around line 5-19: The import order in App.tsx violates repo rules: move the
external runtime import "import React, { useCallback, useEffect, useMemo,
useRef, useState } from 'react'" (and any other external packages) to appear
before internal package imports (those starting with "@xnetjs/*"), while keeping
type-only imports like "import type { PaletteCommand }" at the very top;
rearrange lines so type-only imports come first, then external runtime imports
(React), then internal `@xnetjs` imports (e.g., PageSchema, useDevTools, useQuery,
CommandPalette), and finally local relative imports (components such as
ActionDock, CanvasView, etc.).

In `@apps/electron/src/renderer/components/CanvasView.tsx`:
- Around line 28-45: The type ViewportSnapshot is duplicated; extract it into a
single shared type and import it where needed: create/export a shared type
(e.g., export type ViewportSnapshot = { x: number; y: number; zoom: number }) in
a common types module, replace the local declaration in CanvasView.tsx and the
duplicate in App.tsx with an import of that shared type, and ensure
CanvasViewHandle still references the imported ViewportSnapshot so the
add/focus/restore signatures remain unchanged.
- Around line 5-26: Reorder the import block so type-only imports come first,
then external runtime packages, then internal `@xnetjs/`* runtime imports: move
React and lucide-react imports (React, forwardRef, useCallback, useEffect,
useImperativeHandle, useMemo, useRef, useState and Compass, Database, FileText,
Maximize2, StickyNote) ahead of the `@xnetjs` runtime imports (Canvas, createNode,
useNode, useIdentity, IconButton), while keeping the type imports (CanvasHandle,
CanvasNode, Rect, CanvasSchema, LinkedDocType, LinkedDocumentItem) grouped as
type-only; update the top-level import order around symbols Canvas, createNode,
CanvasHandle, CanvasNode, Rect, CanvasSchema, useNode, useIdentity, IconButton,
Compass, Database, FileText, Maximize2, StickyNote, and React accordingly.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md:
- Around line 523-524: Update the two validation-note sentences to insert a
trailing comma after the year "2026" for consistent date-parenthetical style:
change "March 6, 2026 with `pnpm typecheck`..." to "March 6, 2026, with `pnpm
typecheck`..." and change "March 6, 2026 confirmed the first-run shell..." to
"March 6, 2026, confirmed the first-run shell..."; these edits are in the two
lines containing the automated and manual validation notes (the phrases starting
"Automated validation completed on March 6, 2026 with..." and "Manual Electron
validation on March 6, 2026 confirmed...").
- Around line 608-613: The reducer currently maps 'open-settings' to { kind:
'settings', canvasId: state.canvasId } and 'close-settings' to { kind:
'canvas-home', canvasId: state.canvasId }, which drops context; modify the state
produced by the 'open-settings' branch to preserve the prior view (e.g., include
previousKind or previousMode = state.kind) and update the 'close-settings'
branch to restore that preserved value (return { kind: state.previousKind ??
'canvas-home', canvasId: state.canvasId }) so closing settings returns the prior
mode when present and falls back to 'canvas-home' otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d0e4fa06-027e-4b3e-bd70-2fc5746670ef

📥 Commits

Reviewing files that changed from the base of the PR and between b37a514 and 417e799.

📒 Files selected for processing (5)
  • apps/electron/src/renderer/App.tsx
  • apps/electron/src/renderer/components/CanvasView.tsx
  • apps/electron/src/renderer/lib/canvas-shell.ts
  • docs/explorations/0104_[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
  • package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • package.json
  • apps/electron/src/renderer/lib/canvas-shell.ts

crs added 2 commits March 6, 2026 10:38
- clear delayed overlay timers before forcing canvas-home state

- catch shared-session configuration failures locally

- align import order with repo conventions

- clean up remaining exploration wording and reducer notes
- clear stale home canvas ids before re-running bootstrap

- keep the canvas-first shell recoverable after external canvas deletion

- revalidate the desktop renderer typecheck and build path
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
docs/explorations/0104_[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md (1)

523-525: Make validation notes reproducible with durable references.

Consider adding links/IDs to the exact CI runs (or committed artifacts) for these validation claims so future readers can verify them without relying on local paths or ephemeral context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md
around lines 523 - 525, Replace the ephemeral validation notes with durable
references: for the "Automated validation completed..." line, include the CI
workflow run URL or run ID and the commit SHA and link to the build/artifact
that contains the pnpm typecheck/test/build logs; for the "Manual Electron
validation..." line, upload the Playwright screenshot and any electron test
artifacts to the repository CI artifacts (or an S3/GC bucket) and replace the
local path with that permanent URL plus the test run ID and environment
variables used; for the "Manual Electron validation is still required..." line,
add a tracking issue/PR or checklist URL that governs the remaining UX
validations so readers can verify status. Ensure these edits target the exact
strings in the doc ("Automated validation completed...", "Manual Electron
validation on...", "Manual Electron validation is still required...") so the
references are durable and reproducible.
apps/electron/src/renderer/App.tsx (2)

121-417: Add one Electron Playwright E2E for bootstrap/retry and overlay transitions.

Given the async bootstrap + timer-driven overlay behavior in this range, a single E2E that covers create-failure → retry-success and settings/open/return flows would harden regressions quickly.

Based on learnings "Test UI changes in Electron with Playwright after implementing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/App.tsx` around lines 121 - 417, Add an Electron
Playwright E2E that simulates the home canvas bootstrap failure and retry
success and verifies overlay transitions: stub the create(CanvasSchema, ...)
call to first throw and then resolve, launch the app, assert the "Unable to
create your workspace canvas." UI appears, click the Retry button which should
trigger bootstrapHomeCanvas to succeed and setHomeCanvasId; next open settings
via the UI (triggering handleOpenSettings) and assert the settings overlay
appears, then return home (triggering handleReturnHome) and assert shellState
returns to 'canvas-home' and active node is the home canvas; finally exercise
opening a linked document to trigger focusDocument/overlay open (respecting
OVERLAY_OPEN_DELAY_MS) and assert the page/database overlay shows and restores
viewport when returning. Include assertions around visible overlayTitle text and
that the Retry button & settings open/close workflows complete.

6-10: Reorder imports to match repo conventions.

On Line 6-Line 9, internal @xnetjs/* value imports are placed before external react on Line 10. Please keep external package imports before internal package imports.

Suggested diff
 import type { PaletteCommand } from '@xnetjs/ui'
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
 import { PageSchema, DatabaseSchema, CanvasSchema } from '@xnetjs/data'
 import { useDevTools } from '@xnetjs/devtools'
 import { useQuery, useMutate } from '@xnetjs/react'
 import { CommandPalette, useCommandPalette, usePrefersReducedMotion } from '@xnetjs/ui'
-import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'

As per coding guidelines "Internal package imports should follow external packages" and "External package imports should follow type-only imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/electron/src/renderer/App.tsx` around lines 6 - 10, Reorder the import
block so external packages come before internal `@xnetjs` imports: move the React
import (React, useCallback, useEffect, useMemo, useRef, useState) to be above
the `@xnetjs/`* imports that bring in PageSchema, DatabaseSchema, CanvasSchema,
useDevTools, useQuery, useMutate, CommandPalette, useCommandPalette, and
usePrefersReducedMotion; also ensure any type-only imports (if present) remain
before external package imports per repo conventions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/electron/src/renderer/App.tsx`:
- Around line 147-150: The early return when canvases.length === 0 currently
skips bootstrap whenever homeCanvasId is truthy, which prevents re-bootstrap if
that id is stale; change the logic in the App component so that when canvases
becomes empty you call bootstrapHomeCanvas() unless a valid, current home canvas
exists: detect the transition by storing previous canvases length (e.g., a
useRef prevCanvasesLen or effect dependence on canvases) and if prevCanvasesLen
> 0 && canvases.length === 0 or if homeCanvasId is missing/invalid (and not when
homeCanvasBootstrapError is set) then invoke bootstrapHomeCanvas(); update the
check around homeCanvasId/homeCanvasBootstrapError and the early return so stale
homeCanvasId does not block bootstrapHomeCanvas().

---

Nitpick comments:
In `@apps/electron/src/renderer/App.tsx`:
- Around line 121-417: Add an Electron Playwright E2E that simulates the home
canvas bootstrap failure and retry success and verifies overlay transitions:
stub the create(CanvasSchema, ...) call to first throw and then resolve, launch
the app, assert the "Unable to create your workspace canvas." UI appears, click
the Retry button which should trigger bootstrapHomeCanvas to succeed and
setHomeCanvasId; next open settings via the UI (triggering handleOpenSettings)
and assert the settings overlay appears, then return home (triggering
handleReturnHome) and assert shellState returns to 'canvas-home' and active node
is the home canvas; finally exercise opening a linked document to trigger
focusDocument/overlay open (respecting OVERLAY_OPEN_DELAY_MS) and assert the
page/database overlay shows and restores viewport when returning. Include
assertions around visible overlayTitle text and that the Retry button & settings
open/close workflows complete.
- Around line 6-10: Reorder the import block so external packages come before
internal `@xnetjs` imports: move the React import (React, useCallback, useEffect,
useMemo, useRef, useState) to be above the `@xnetjs/`* imports that bring in
PageSchema, DatabaseSchema, CanvasSchema, useDevTools, useQuery, useMutate,
CommandPalette, useCommandPalette, and usePrefersReducedMotion; also ensure any
type-only imports (if present) remain before external package imports per repo
conventions.

In
`@docs/explorations/0104_`[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md:
- Around line 523-525: Replace the ephemeral validation notes with durable
references: for the "Automated validation completed..." line, include the CI
workflow run URL or run ID and the commit SHA and link to the build/artifact
that contains the pnpm typecheck/test/build logs; for the "Manual Electron
validation..." line, upload the Playwright screenshot and any electron test
artifacts to the repository CI artifacts (or an S3/GC bucket) and replace the
local path with that permanent URL plus the test run ID and environment
variables used; for the "Manual Electron validation is still required..." line,
add a tracking issue/PR or checklist URL that governs the remaining UX
validations so readers can verify status. Ensure these edits target the exact
strings in the doc ("Automated validation completed...", "Manual Electron
validation on...", "Manual Electron validation is still required...") so the
references are durable and reproducible.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 632973cd-f68a-4add-8eeb-255deb280828

📥 Commits

Reviewing files that changed from the base of the PR and between 417e799 and 3b7f0f9.

📒 Files selected for processing (2)
  • apps/electron/src/renderer/App.tsx
  • docs/explorations/0104_[_]_EXPLORE_DRAMATICALLY_SIMPLIFYING_THE_UX_AROUND_A_CANVAS_FIRST_PRIMARY_APP_INSPIRED_BY_AFFINE_MINIMIZING_BUTTONS_AND_CHROME_WITH_ZOOM_IN_DOCUMENTS_AND_DATABASES.md

Comment on lines +64 to +99
const linkedType = document?.type ?? getLinkedType(node) ?? 'canvas'
const linkedTitle = document?.title ?? (node.properties.title as string) ?? 'Untitled'
const subtitle =
linkedType === 'page'
? 'Document'
: linkedType === 'database'
? 'Database'
: isCanvasShellNote(node)
? 'Canvas note'
: 'Canvas'

const Icon = linkedType === 'page' ? FileText : linkedType === 'database' ? Database : StickyNote

return (
<div className="flex h-full flex-col justify-between rounded-[24px] border border-border/70 bg-background/95 p-4 shadow-lg shadow-black/5">
<div className="flex items-start justify-between gap-3">
<span className="inline-flex items-center gap-2 rounded-full bg-muted px-2.5 py-1 text-[11px] font-medium uppercase tracking-[0.18em] text-muted-foreground">
<Icon size={12} />
{subtitle}
</span>
{node.linkedNodeId && linkedType !== 'canvas' ? (
<span className="text-[11px] uppercase tracking-[0.18em] text-muted-foreground">
Open
</span>
) : null}
</div>

<div className="space-y-2">
<div className="text-lg font-semibold leading-tight text-foreground">{linkedTitle}</div>
<p className="text-sm leading-relaxed text-muted-foreground">
{linkedType === 'database'
? 'Open a focused database surface from the canvas.'
: linkedType === 'page'
? 'Open a focused writing surface from the canvas.'
: 'A lightweight note pinned directly to the workspace.'}
</p>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Canvas-linked nodes show wrong icon and description text

When the documents prop includes canvas entries (which it does — App.tsx passes the full documents array including canvases), a canvas-linked node resolves to a LinkedDocumentItem with type: 'canvas'. This causes renderNodeCard to:

  1. Render the StickyNote icon (semantically wrong for a canvas)
  2. Fall into the else branch of the body text at line 97, which outputs 'A lightweight note pinned directly to the workspace.' — this is the note-card description, not a canvas description

A user with a legacy canvas-linked node would see misleading copy. The linkedType === 'canvas' arm of the ternaries needs its own icon and description:

const Icon =
  linkedType === 'page'
    ? FileText
    : linkedType === 'database'
      ? Database
      : linkedType === 'canvas'
        ? LayoutTemplate   // or Layers / Grid2X2 from lucide-react
        : StickyNote

// …inside the body text ternary:
: linkedType === 'canvas'
  ? 'Open this canvas as the home workspace.'
  : 'A lightweight note pinned directly to the workspace.'

Comment on lines +198 to +202
const nodesMap = doc.getMap<CanvasNode>('nodes')
const targetNode = Array.from(nodesMap.values()).find(
(node) => node.linkedNodeId === linkedDocumentId
)
if (!targetNode) return null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Linear node scan allocates a full array copy on every focus call

Array.from(nodesMap.values()).find(...) materialises a complete array from the Y.Map on every call to focusLinkedDocument. For a canvas with many nodes this is an O(n) allocation on what is a hot path (called on every double-click-to-open interaction).

Y.Map can be iterated directly without copying:

let targetNode: CanvasNode | undefined
for (const node of nodesMap.values()) {
  if (node.linkedNodeId === linkedDocumentId) {
    targetNode = node
    break
  }
}
if (!targetNode) return null

Comment on lines +258 to +268
const handleReturnHome = useCallback(() => {
clearTransitionTimer()
if (shellState.kind === 'page-focus' || shellState.kind === 'database-focus') {
if (shellState.returnViewport) {
canvasViewRef.current?.restoreViewport(shellState.returnViewport)
}
}

setShellState({ kind: 'canvas-home' })
setActiveNodeId(homeCanvasId)
}, [clearTransitionTimer, homeCanvasId, setActiveNodeId, shellState])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

handleReturnHome reconstructed on every shell-state change

shellState is listed as a dependency so handleReturnHome gets a new reference every time any navigation happens (open document, close document, open settings…). Because handleReturnHome is passed directly as the onReturnHome prop of ActionDock, this triggers a full ActionDock re-render on each transition — during exactly the moments when smooth animation is most critical.

The shell state can be read at call-time through a ref to keep the callback stable:

const shellStateRef = useRef(shellState)
shellStateRef.current = shellState   // keep in sync, no dep

const handleReturnHome = useCallback(() => {
  clearTransitionTimer()
  const currentState = shellStateRef.current
  if (currentState.kind === 'page-focus' || currentState.kind === 'database-focus') {
    if (currentState.returnViewport) {
      canvasViewRef.current?.restoreViewport(currentState.returnViewport)
    }
  }
  setShellState({ kind: 'canvas-home' })
  setActiveNodeId(homeCanvasId)
}, [clearTransitionTimer, homeCanvasId, setActiveNodeId])

@crs48 crs48 merged commit aa44ca1 into main Mar 6, 2026
8 checks passed
@crs48 crs48 deleted the codex/canvas-first-shell branch March 6, 2026 18:59
github-actions bot added a commit that referenced this pull request Mar 6, 2026
crs48 added a commit that referenced this pull request Mar 7, 2026
* docs(exploration): add canvas-first minimal ux study

- analyze the current Electron shell, view chrome, and canvas primitives

- recommend a hybrid canvas-first shell with bottom dock and top-right system menu

- capture migration phases, tradeoffs, validation steps, and implementation example code

* docs(exploration): renumber canvas-first minimal ux study

- rename the exploration from 0103 to 0104

- update the document title to match the new numbering

* feat(electron): add canvas-first workspace shell

- replace the sidebar-first renderer shell

- keep a persistent home canvas with overlay focus surfaces

- add a bottom action dock and top-right system menu

- create linked page and database cards on the canvas

- extend canvas viewport APIs for zoom-based transitions

- update the exploration checklist for implemented items

* feat(electron): tighten focused view chrome

- add compact header controls for focused page views

- reduce database toolbar density inside the overlay shell

- wire compact mode from the new app shell

- update the exploration checklist for the page chrome item

* chore(repo): allow better-sqlite3 native builds

- add pnpm onlyBuiltDependencies entry for better-sqlite3
- unblock sqlite adapter tests in fresh worktrees where native builds were skipped
- keep the existing deps:node workflow able to rebuild the native binding before test runs

* fix(electron): preserve default canvas node rendering

- limit the canvas-first card renderer to linked documents and note cards
- keep existing non-document canvas nodes on their default rendering path
- reduce the risk of breaking older canvases that contain shapes or other rich node types

* test(electron): cover canvas shell card selection

- extract the canvas shell card-selection logic into a renderer helper
- mark shell-created notes explicitly instead of styling every card node
- add a regression test so generic canvas cards keep their default rendering

* fix(electron): harden canvas shell review follow-ups

- catch home-canvas creation failures

- catch linked document creation failures

- restore runtime canvas imports

- hide onboarding after nodes exist

- add a debug-panel action to the system menu

- skip zoom-style shell transitions for reduced motion

- sanitize restored viewport snapshots

* fix(electron): polish canvas shell review follow-ups

- add retryable home-canvas bootstrap recovery

- limit palette recents and clear transitions before settings

- hide non-actionable open affordances on canvas-linked cards

- allow electron install scripts in fresh worktrees

- record completed validation for the canvas-first exploration

* fix(electron): harden canvas-home transition paths

- clear delayed overlay timers before forcing canvas-home state

- catch shared-session configuration failures locally

- align import order with repo conventions

- clean up remaining exploration wording and reducer notes

* fix(electron): recover when the home canvas disappears

- clear stale home canvas ids before re-running bootstrap

- keep the canvas-first shell recoverable after external canvas deletion

- revalidate the desktop renderer typecheck and build path

---------

Co-authored-by: crs <crs@users.noreply.github.com>
crs48 added a commit that referenced this pull request Mar 7, 2026
* docs(exploration): add canvas-first minimal ux study

- analyze the current Electron shell, view chrome, and canvas primitives

- recommend a hybrid canvas-first shell with bottom dock and top-right system menu

- capture migration phases, tradeoffs, validation steps, and implementation example code

* docs(exploration): renumber canvas-first minimal ux study

- rename the exploration from 0103 to 0104

- update the document title to match the new numbering

* feat(electron): add canvas-first workspace shell

- replace the sidebar-first renderer shell

- keep a persistent home canvas with overlay focus surfaces

- add a bottom action dock and top-right system menu

- create linked page and database cards on the canvas

- extend canvas viewport APIs for zoom-based transitions

- update the exploration checklist for implemented items

* feat(electron): tighten focused view chrome

- add compact header controls for focused page views

- reduce database toolbar density inside the overlay shell

- wire compact mode from the new app shell

- update the exploration checklist for the page chrome item

* chore(repo): allow better-sqlite3 native builds

- add pnpm onlyBuiltDependencies entry for better-sqlite3
- unblock sqlite adapter tests in fresh worktrees where native builds were skipped
- keep the existing deps:node workflow able to rebuild the native binding before test runs

* fix(electron): preserve default canvas node rendering

- limit the canvas-first card renderer to linked documents and note cards
- keep existing non-document canvas nodes on their default rendering path
- reduce the risk of breaking older canvases that contain shapes or other rich node types

* test(electron): cover canvas shell card selection

- extract the canvas shell card-selection logic into a renderer helper
- mark shell-created notes explicitly instead of styling every card node
- add a regression test so generic canvas cards keep their default rendering

* fix(electron): harden canvas shell review follow-ups

- catch home-canvas creation failures

- catch linked document creation failures

- restore runtime canvas imports

- hide onboarding after nodes exist

- add a debug-panel action to the system menu

- skip zoom-style shell transitions for reduced motion

- sanitize restored viewport snapshots

* fix(electron): polish canvas shell review follow-ups

- add retryable home-canvas bootstrap recovery

- limit palette recents and clear transitions before settings

- hide non-actionable open affordances on canvas-linked cards

- allow electron install scripts in fresh worktrees

- record completed validation for the canvas-first exploration

* fix(electron): harden canvas-home transition paths

- clear delayed overlay timers before forcing canvas-home state

- catch shared-session configuration failures locally

- align import order with repo conventions

- clean up remaining exploration wording and reducer notes

* fix(electron): recover when the home canvas disappears

- clear stale home canvas ids before re-running bootstrap

- keep the canvas-first shell recoverable after external canvas deletion

- revalidate the desktop renderer typecheck and build path

---------

Co-authored-by: crs <crs@users.noreply.github.com>
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.

2 participants