Desktop users can receive new sessions from IM adapters or scheduled tasks while the app stays open. The sidebar now refreshes on mount, visible focus, and a low-frequency visible-only interval, with a manual refresh control and in-flight request dedupe so the fix does not create avoidable polling pressure.
Constraint: Sessions can be created outside the desktop process by IM and scheduler entrypoints
Rejected: WebSocket push for this patch | broader server contract change than needed for the reported stale list
Confidence: high
Scope-risk: narrow
Directive: Keep session-list refresh visible-only and deduped before lowering intervals or adding more triggers
Tested: cd desktop && bunx vitest run src/components/layout/Sidebar.test.tsx src/i18n/index.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: Browser smoke on isolated desktop backend/frontend with refresh button click
Not-tested: Full coverage gate is blocked by unrelated root test port/timeouts; changed lines coverage reported 100% (59/59)
The previous batch selection state let adjacent selected sessions visually merge into a single block. This keeps the existing row structure but adds per-row spacing, a transparent baseline border, and a subtle selected-row shadow so the selection is easier to read without changing behavior.
Constraint: Keep the change local to the desktop sidebar and avoid new dependencies.
Rejected: Add global sidebar CSS tokens | unnecessary for this small component-level state and harder for changed-line coverage.
Confidence: high
Scope-risk: narrow
Directive: Keep batch selection spacing on the row wrapper so adjacent selected rows remain visually separated.
Tested: bun run verify
The age-based quick cleanup buttons overlapped poorly with the sidebar time groups and created ambiguity around what would be deleted. Keeping batch mode focused on all/selected sessions makes the destructive path easier to understand while preserving the existing confirmation and server deletion behavior.
Constraint: Desktop batch deletion should stay routed through the existing selected-session confirmation flow.
Rejected: Rename the age-based cleanup buttons | still leaves a low-value destructive shortcut in the primary batch panel.
Confidence: high
Scope-risk: narrow
Directive: Add future destructive cleanup shortcuts only when their target set is unambiguous in the current filtered session list.
Tested: cd desktop && bun run test src/components/layout/Sidebar.test.tsx
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Manual desktop click-through after removing the buttons.
The sidebar had only single-session deletion, which made stale or noisy
session lists expensive to maintain. This adds a batch-management lane in
the desktop UI and a server endpoint that deletes multiple sessions while
preserving per-session failure reporting and cleanup behavior.
Constraint: Session deletion must preserve existing transcript and adapter cleanup semantics
Constraint: Desktop UI should reuse the shared confirmation dialog instead of introducing a second modal surface
Rejected: Delete all selected sessions through repeated client DELETE calls | weaker partial-failure handling and duplicated cleanup orchestration
Rejected: Use a generic checkmark entry icon | it did not communicate batch deletion clearly enough
Confidence: high
Scope-risk: moderate
Directive: Keep batch deletion routed through the server batch endpoint so rollback and adapter cleanup stay centralized
Tested: bun test src/server/__tests__/sessions.test.ts -t batch-delete
Tested: bun run check:server
Tested: cd desktop && bun run check:desktop
Tested: browser UI smoke for group select, shift range select, delete confirmation, 7/30 day cleanup, Cmd+A filtered selection, and Escape exit
Not-tested: Live provider-backed session generation; this change covers session list management after sessions already exist
H5 browser use needs to keep local desktop WebUI testing open while giving remote phones a usable chat-first surface. This change scopes browser auth by origin and target host, hides non-chat desktop chrome on H5 mobile, compacts the empty-session composer, and unifies mobile selectors behind a full-width bottom sheet with an explicit close action.
Constraint: Local browser development may bypass H5 auth only for loopback or private LAN server hosts; public or remote hosts still require an H5 token.
Constraint: Mobile H5 scope is chat-first; settings and scheduled-task surfaces stay hidden instead of fully redesigned.
Rejected: Reuse desktop popovers on mobile | hover/anchored dropdown behavior leaves key actions hard to dismiss and cramped on narrow screens.
Rejected: Let any localhost origin bypass remote auth | it would let arbitrary local pages access public H5 servers without a token.
Confidence: medium
Scope-risk: moderate
Directive: Keep future H5 selector popups on MobileBottomSheet unless a selector needs a reviewed custom mobile interaction.
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- src/pages/EmptySession.test.tsx src/components/chat/ChatInput.test.tsx src/lib/desktopRuntime.test.ts src/components/shared/RepositoryLaunchControls.test.tsx src/components/controls/PermissionModeSelector.test.tsx src/components/controls/ModelSelector.test.tsx src/components/shared/DirectoryPicker.test.tsx src/__tests__/pages.test.tsx
Tested: bun test src/server/__tests__/h5-access-auth.test.ts src/server/middleware/cors.test.ts
Tested: Chrome mobile viewport smoke for context, model, permission, project, branch, and worktree bottom sheets with no horizontal overflow.
Not-tested: Full bun run verify quality gate.
The browser H5 surface now switches to a phone-oriented shell: the sidebar becomes a closed-by-default drawer, chat stays primary, workspace and terminal panels stay off the mobile chat surface, composer controls use larger touch targets, and mobile menus avoid desktop-only widths and keyboard hints. Desktop and Tauri sidebar behavior remain on the existing store-driven path.
Constraint: H5 is personal/team browser access layered on the existing desktop web UI, so the normal desktop app must keep its current layout behavior.
Rejected: Share the global sidebarOpen default for mobile first paint | it can flash the drawer open before effects run on a phone.
Confidence: high
Scope-risk: moderate
Directive: Keep mobile-only layout branching behind useMobileViewport() && !isTauriRuntime() unless a future task explicitly redesigns the native desktop shell.
Tested: cd desktop && bunx vitest run src/hooks/useMobileViewport.test.tsx src/components/layout/AppShell.test.tsx src/components/layout/Sidebar.test.tsx src/pages/ActiveSession.test.tsx src/components/chat/ChatInput.test.tsx src/components/controls/PermissionModeSelector.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Contributors and coding agents need one local command that both reports and enforces the quality contract. This change turns the PR gate into the shared verification entrypoint, adds path-selected local lanes, tightens coverage accounting around changed lines, and documents the repair loop in contributor and agent-facing guidance.
Constraint: Ordinary PR verification must stay non-live and runnable without provider credentials
Constraint: Coverage policy updates in this commit require maintainer approval before push/merge
Rejected: Keep quality guidance only in docs | agents need executable scripts and AGENTS.md instructions to follow the loop consistently
Confidence: high
Scope-risk: broad
Directive: Do not bypass `bun run verify` for production changes; fix failed lanes and coverage reports instead of lowering thresholds
Tested: bun run check:policy
Tested: ALLOW_CLI_CORE_CHANGE=1 ALLOW_COVERAGE_BASELINE_CHANGE=1 bun run verify
Not-tested: live provider baseline; no provider credentials were required for this non-live PR gate
Large desktop histories were making session discovery parse every JSONL
before pagination, while automatic title updates could still overwrite
manual names during resumed sessions. This keeps listing bounded to the
requested page, preserves custom titles, and blocks deleted placeholder
sessions from being recreated by prewarm startup.
Constraint: Desktop session storage remains JSONL-compatible with the CLI
Rejected: Virtualize the sidebar in this patch | does not fix backend JSONL parsing cost
Rejected: Disable title generation globally | would regress useful titles for unnamed sessions
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce all-file JSONL parsing on /api/sessions list paths without a heavy-session benchmark
Tested: ANTHROPIC_API_KEY=test-key bun test src/server/__tests__/title-service.test.ts src/server/__tests__/sessions.test.ts
Tested: ANTHROPIC_API_KEY=test-key bun test src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: heavy local preview with 240 sessions, 320 messages each, and 40 restored tabs
Not-tested: native packaged desktop runtime under Windows with the same heavy fixture
Related: https://github.com/NanmiCoder/cc-haha/issues/237
Related: https://github.com/NanmiCoder/cc-haha/issues/248
Workspace file preview is a local desktop surface, so image files should not be blocked by the text preview byte cap. Large text files now return a bounded preview instead of an unusable too-large state, while binary files remain explicitly unsupported.
Constraint: Avoid unbounded text payloads that can freeze the desktop renderer
Rejected: Remove all read limits | large generated files can still overload JSON transport and syntax rendering
Confidence: high
Scope-risk: moderate
Directive: Keep image preview detection before text-size limiting so local images remain renderable
Tested: bun test src/server/__tests__/workspace-service.test.ts
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx
Tested: cd desktop && bun run build
Not-tested: Full packaged macOS smoke after this commit
The desktop terminal already supported independent PTY sessions, but it only lived inside settings. This change promotes it to a first-class tab workflow so users can open multiple host terminals without leaving the chat-oriented desktop layout.
Constraint: Tauri terminal sessions are process-backed and must stay mounted while switching tabs.
Rejected: Reuse the settings terminal as a navigated page only | it cannot support multiple independent terminal tabs.
Rejected: Hide inactive xterm panes with display none | xterm lost visible output after tab switches during E2E.
Confidence: high
Scope-risk: moderate
Directive: Keep inactive terminal panes mounted and avoid display none unless xterm repaint behavior is reverified.
Tested: bun run test src/components/layout/ContentRouter.test.tsx src/components/layout/Sidebar.test.tsx src/components/layout/TabBar.test.tsx src/pages/TerminalSettings.test.tsx
Tested: bun run lint
Tested: bun run build
Tested: ./scripts/build-macos-arm64.sh
Tested: Computer Use E2E against build-artifacts/macos-arm64 app for multiple terminals, command output retention, tab switching, and terminal cleanup
Not-tested: Intel macOS package
The desktop settings and sidebar still had deletion flows that either used browser-native confirms or deleted immediately. This change consolidates destructive confirmations behind a shared dialog component, applies it to provider deletion, plugin uninstall, adapter unbind, and sidebar session deletion, and adds regression coverage so delete actions require an explicit second confirmation before mutating state.
Constraint: Other in-progress desktop work in the tree had to stay out of this commit
Constraint: Existing MCP and task confirmations needed to keep their current behavior
Rejected: Leave confirmations embedded per-page with browser dialogs | inconsistent UX and easy to regress
Rejected: Add confirmations only to provider deletion | leaves other destructive desktop flows unsafe
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any new desktop delete, uninstall, or unbind action should use the shared ConfirmDialog instead of browser-native dialogs or one-click deletion
Tested: bun run test src/components/layout/Sidebar.test.tsx src/__tests__/generalSettings.test.tsx; bun run lint
Not-tested: Manual desktop click-through of every destructive action after this refactor
The sidebar project filter had regressed to lossy projectPath strings, so the
UI showed broken folder-name fragments and the dropdown could not match the
richer project chooser used in session creation. This switches the sidebar back
to recent-project metadata, renders the filter menu through a portal, and keeps
session filtering behavior unchanged while restoring readable labels and paths.
Constraint: Sidebar filtering still needs to target existing session projectPath values without migrating store shape
Constraint: The desktop sidebar dropdown must escape local stacking and overflow constraints to render reliably
Rejected: Keep deriving labels from sanitized projectPath strings | lost repo names and full paths, causing misleading project choices
Rejected: Rebuild sessionStore around recent-project objects in this fix | broader state migration than needed for the regression
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep sidebar project displays aligned with /api/sessions/recent-projects metadata instead of reintroducing string-splitting fallbacks
Tested: cd desktop && bunx vitest run src/components/layout/ProjectFilter.test.tsx src/components/layout/Sidebar.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: Manual desktop visual pass of the repaired dropdown in a live Tauri window
The desktop shell now owns sidebar collapse state, the collapsed rail keeps a
visible recovery affordance, and keyboard search expands the sidebar before
focusing. The UI work also smooths the width transition so the main chat area
can take over without the previous abrupt cutover.
Constraint: The desktop app must keep a recovery path visible in narrow layouts on macOS and Windows
Constraint: Existing sidebar behavior needed regression coverage before further UI work
Rejected: Fully hide the sidebar at 0 width | recovery affordance became too fragile for this iteration
Rejected: Keep boxed restore controls in the collapsed rail | visually heavy and easy to misplace
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep the collapsed rail width and the inner sidebar panel width in sync or expand controls will be clipped again
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test Sidebar.test.tsx
Not-tested: Full desktop manual pass across every app screen after the final icon/layout tweaks
The desktop app previously lost project-level skills in two places: the
settings browser only queried user skills, and the slash-command picker for a
fresh session depended on CLI init state that does not exist before the first
turn. This change makes the skills APIs cwd-aware, falls back to project skill
loading before CLI init, syncs sidebar session deletion with open tabs, and
aligns the empty-session composer styling so the pre-message session UI stays
consistent.
Constraint: Fresh desktop sessions need slash-command discovery before the CLI websocket emits system/init
Constraint: Only repository code should be committed; build artifacts, installs, and /tmp smoke fixtures stay out of git
Rejected: Rebuild slash-command listings from full getCommands() in the sessions API | introduced auth-gated command dependencies unrelated to local skill discovery
Rejected: Unify EmptySession and ChatInput into one full component now | higher regression risk for normal chat interactions than a visual-only hero variant
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep pre-message skill discovery keyed to session workDir so project-level .claude/skills remain visible before the first turn
Tested: bun test src/server/__tests__/sessions.test.ts; cd desktop && bun run test -- --run src/__tests__/skillsSettings.test.tsx src/components/layout/Sidebar.test.tsx src/__tests__/pages.test.tsx src/pages/ActiveSession.test.tsx; cd desktop && bun run lint
Not-tested: Manual desktop smoke test against the rebuilt .app bundle in the local GUI