Skip to content

feat(desktop): add independent Space tabs - #311

Open
cxxxxxn (cxxxxxn) wants to merge 2 commits into
mainfrom
feat/desktop-space-tabs
Open

cxxxxxn (cxxxxxn) wants to merge 2 commits into
mainfrom
feat/desktop-space-tabs

Conversation

@cxxxxxn

Copy link
Copy Markdown
Contributor

Summary

Add desktop-only Space Tabs so users can work in multiple Spaces without replacing the current Space's canvas, conversation or preview state. Each window continues to use one Server and one active Workspace; browser deployments retain the existing single-page experience.

  • Introduce an Electron shell with persistent Home and independent, sandboxed WebContentsViews for Spaces, immutable renderer identities, deduplicated navigation and lazy session restoration.
  • Keep the Tab strip minimal: icon-only Home, neutral Space titles and close controls, and persistent Settings/Workspace actions. Reuse the existing Home setup page for opening Workspaces.
  • Isolate Space storage and Chromium connection pools to avoid cross-Space state overwrites and SSE connection starvation; synchronize application preferences across partitions.
  • Coordinate acknowledged saves before Tab close, reload, Workspace changes, window close, application quit and update installation. Save failures retain usable pages instead of discarding them.
  • Route native menus, updater state and remote Basic Auth through the multi-view architecture; update the architecture documentation.

Independent review fixes included

  • Repeated quit/window-close requests share the same save preparation and cannot bypass pending saves, regardless of which action starts first.
  • Workspace restoration uses Server-confirmed identity, not stale client state after a successful activation followed by a failed query. An unconfirmed identity restores no Space pages; registration during a Workspace transition is rejected rather than queued.
  • A synchronous renderer input freeze covers global Canvas shortcuts, native commands and portalled controls until resume or destruction, including after an individual save acknowledgement.
  • Retained Home refreshes after Space creation/import and reactivation; request-generation and Workspace-identity checks prevent stale list responses.

Validation

  • Repository-level pnpm typecheck
  • Repository-level pnpm format
  • Repository-level pnpm lint:fix (0 errors; existing warnings remain)
  • Focused web regression suite: 113 tests passed
  • Desktop unit suite: 75 tests passed, 1 existing todo
  • Real Electron integration: five concurrent Spaces, draft/document/selection/viewport isolation and retention, undo/redo, preference synchronization, failed and delayed saves, both quit-first and close-first shutdown sequences, frozen shortcuts/portals, retained Home creation/list refresh, lazy restart restoration, Workspace menu/setup routing and Workspace switching
  • Formatting and git diff --check

Boundaries / remaining validation

  • One active Workspace per window; no split views, detached Tabs or automatic Tab suspension.
  • Unsent drafts and in-memory undo/selection survive Tab switching, not page destruction or application restart.
  • Closing a Tab does not send Agent Stop; quitting a local application still shuts down its Server.
  • Windows real-device behavior, actual update installer execution and background Agent execution with a configured model remain unverified.

Architecture reference: Desktop Space Tabs.

Add retained per-Space content views, workspace-aware session restoration, isolated storage and synchronized preferences. Coordinate save acknowledgements across tab/window/quit/update lifecycles and retain the minimal desktop tab and workspace navigation UI.

Include regression coverage for repeated shutdown requests, authoritative Workspace recovery, renderer input freezing and retained Home refreshes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

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.

🟡 Changes recommended

Save-barrier ownership and shutdown recovery remain unsafe, and preference synchronization can feed stale updates back between renderers.

5 open findings
What changed in this PR

Adds desktop-only Space tabs, keeping each Space’s canvas, conversation, and preview state independent while sharing one Server and active Workspace.

Changes:

  • Adds persistent Home, independent Space views, and lazy tab restoration.
  • Coordinates saves across closing, reloading, Workspace switching, and shutdown.
  • Integrates scoped storage, shared preferences, native menus, updates, tests, and documentation.
File Description
pnpm-lock.yaml Records desktop’s shared-package dependency.
packages/​shared/​src/​types/​desktop-tabs.ts Defines shared tab IPC contracts.
packages/​shared/​package.json Exports tab types.
docs/​README.md Indexes tab architecture documentation.
docs/​architecture/​web-architecture.md Documents independent desktop renderers.
docs/​architecture/​preview-workspace.md Explains desktop preview isolation.
docs/​architecture/​desktop-tabs.md Documents tab architecture and lifecycle.
docs/​architecture/​desktop-startup.md Updates startup and authentication flows.
docs/​architecture/​desktop-auto-update.md Documents save-aware installation.
apps/​web/​src/​utils/​desktopStorage.ts Adds scoped storage and preference synchronization.
apps/​web/​src/​utils/​desktopStorage.test.ts Tests storage isolation and migration.
apps/​web/​src/​utils/​desktopNavigation.ts Classifies cross-Space navigation.
apps/​web/​src/​utils/​desktopNavigation.test.ts Tests navigation intent preservation.
apps/​web/​src/​utils/​desktopInput.ts Freezes renderer input during saves.
apps/​web/​src/​store/​workspaceStore.ts Coordinates Workspace transitions and recovery.
apps/​web/​src/​store/​workspaceStore.test.ts Tests authoritative Workspace recovery.
apps/​web/​src/​store/​toolStore.ts Uses shared preference persistence.
apps/​web/​src/​store/​previewWorkspace/​persistence.ts Scopes preview layout storage.
apps/​web/​src/​store/​panelStore.ts Scopes panel persistence.
apps/​web/​src/​store/​conversationTitleStore.ts Scopes conversation title drafts.
apps/​web/​src/​store/​conversationPreparation.ts Scopes conversation preparation metadata.
apps/​web/​src/​store/​chatStore.ts Scopes persisted chat metadata.
apps/​web/​src/​store/​chatPreferencesStore.ts Uses synchronized preference storage.
apps/​web/​src/​store/​canvasStore.ts Scopes view state and adds flushing.
apps/​web/​src/​pages/​CanvasListPage.tsx Adapts retained Home and fences list refreshes.
apps/​web/​src/​pages/​CanvasListPage.test.tsx Tests Home refreshes and Workspace controls.
apps/​web/​src/​main.tsx Selects Shell rendering and installs input guards.
apps/​web/​src/​i18n/​resources/​zh-CN/​common.json Adds Chinese tab and Workspace labels.
apps/​web/​src/​i18n/​resources/​en/​common.json Adds English tab and Workspace labels.
apps/​web/​src/​i18n/​index.ts Synchronizes language preferences.
apps/​web/​src/​hooks/​useElectron.ts Exposes typed tab bridge access.
apps/​web/​src/​hooks/​useDesktopContent.ts Handles saves, navigation, status, and preferences.
apps/​web/​src/​hooks/​useDesktopContent.test.tsx Tests save acknowledgements and input freezing.
apps/​web/​src/​hooks/​useCanvasActions.ts Invalidates Home after creation and import.
apps/​web/​src/​hooks/​useCanvasActions.test.tsx Tests list invalidation.
apps/​web/​src/​hooks/​useAppUpdate.ts Supports inline updater feedback.
apps/​web/​src/​hooks/​useAppUpdate.test.ts Tests updater feedback and failures.
apps/​web/​src/​hooks/​shortcuts/​useCanvasShortcuts.lockKeys.test.ts Tests shortcut blocking during saves.
apps/​web/​src/​components/​Shell/​WindowChrome.tsx Suppresses duplicate content chrome.
apps/​web/​src/​components/​Shell/​UpdateButton.tsx Supports Shell-specific update presentation.
apps/​web/​src/​components/​Shell/​NativeMenuBridge.tsx Adapts menu labels and frozen-input handling.
apps/​web/​src/​components/​Shell/​NativeMenuBridge.test.tsx Tests frozen native commands.
apps/​web/​src/​components/​Shell/​DesktopTabShell.tsx Adds tab strip and persistent actions.
apps/​web/​src/​components/​Shell/​DesktopTabShell.test.tsx Tests tab presentation and controls.
apps/​web/​src/​components/​Shell/​DesktopHomeMenu.tsx Adds Home-rendered Workspace menu.
apps/​web/​src/​components/​Panels/​Header/​Header.tsx Adapts controls for tabbed desktop.
apps/​web/​src/​App.tsx Integrates navigation interception and save overlays.
apps/​desktop/​src/​updater.ts Coordinates installation saves and status broadcasts.
apps/​desktop/​src/​updater.test.ts Tests save refusal and broadcasting.
apps/​desktop/​src/​tab-session.ts Validates navigation and session records.
apps/​desktop/​src/​tab-session.test.ts Tests session and navigation validation.
apps/​desktop/​src/​remote-basic-auth.ts Reuses credentials across content renderers.
apps/​desktop/​src/​remote-basic-auth.test.ts Tests cross-renderer credential reuse.
apps/​desktop/​src/​preload.ts Exposes renderer identity and tab IPC.
apps/​desktop/​src/​main.ts Integrates tab hosting and coordinated shutdown.
apps/​desktop/​src/​mac-menu.ts Targets active content and tab closing.
apps/​desktop/​src/​desktop-tabs.ts Implements tab authority, persistence, and lifecycle.
apps/​desktop/​src/​desktop-tabs.test.ts Tests lifecycle, isolation, and IPC ownership.
apps/​desktop/​package.json Adds shared dependency and integration-test command.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

🧠 Review effort: Balanced


💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/desktop/src/desktop-tabs.ts Outdated
Comment on lines +582 to +586
const results = await Promise.allSettled(
this.tabs.map((tab) => this.drain(tab)),
);
const failed = results.find((result) => result.status === 'rejected');
if (failed?.status === 'rejected') throw failed.reason;
Comment thread apps/desktop/src/main.ts
Comment thread apps/web/src/hooks/useDesktopContent.ts Outdated
Comment thread apps/web/src/i18n/index.ts Outdated
Comment thread apps/web/src/components/Shell/DesktopTabShell.tsx Outdated
Preserve save ownership during competing quits, recover shutdown from unavailable pages, avoid preference echo loops, and retain keyboard Tab focus. Add unit and real Electron regressions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@cxxxxxn

Copy link
Copy Markdown
Contributor Author

Addressed all five Copilot findings in 06fe439.

  • Shutdown can explicitly discard unavailable pages after saving healthy pages; cancellation keeps the application open. Workspace changes remain strict.
  • A rejected competing quit no longer releases another operation's save barrier.
  • Incoming World and language preference updates hydrate local state without invoking the outbound writer; explicit Settings language selections still persist.
  • Keyboard Tab activation requests Shell-native focus and restores the selected button after IPC acknowledgement. Ordinary clicks still focus content.

Validation: 80 desktop tests passed (1 existing TODO), 118 focused web tests passed, repository typecheck/format/lint passed, and expanded isolated real-Electron smoke passed (competing quit ownership, rapid preference writes with exact outbound counts, continuous keyboard navigation, crashed Home shutdown cancellation/confirmation). Native OS-focus assertions were explicitly skipped because this automation environment did not activate any fixture window; native focus invocation is covered by unit tests. Architecture documentation updated. Please re-review the new commit.

Copilot AI left a comment

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.

🔵 Needs a closer look

Preference ordering and Workspace save-failure feedback remain unresolved.

1 open finding
4 resolved since last review
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Previously missed (2)

In code that hasn't changed since last review

Medium severity Expose workspace save-barrier failures in the Store error

apps/​web/​src/​store/​workspaceStore.ts:172

[P2] Publish save-barrier failures in the Workspace Store. This await is outside the error-handling block, so a rejected save leaves the Store's error unchanged. WorkspaceSetupPage catches activation rejections and relies on that error for feedback (lines 112–136 and 201–207). When a background Space cannot save, selecting a Workspace from /setup therefore fails without showing the reason there. Record the barrier error before rethrowing, while retaining the current Workspace identity.

Medium severity Ignore stale preference notifications after newer local writes

apps/​web/​src/​utils/​desktopStorage.ts:58

[P2] Ignore preference notifications older than an acknowledged local write. If A's zh-CN notification is queued while B synchronously commits en, this listener later replaces B's newer local value with zh-CN. Main remains on en; DesktopTabs.setPreference excludes the writer from broadcasts, so B receives no corrective update. This can leave language or input-mode settings inconsistent across Spaces. Include per-key revisions in snapshots, write acknowledgements and notifications, and ignore older revisions. Add a regression test that delivers A's notification after B's write succeeds.

🧠 Review effort: Balanced

This branch has not been deployed

No deployments
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