Skip to content

chore: sync with upstream#2

Open
SilkePilon wants to merge 209 commits intomainfrom
upstream-sync
Open

chore: sync with upstream#2
SilkePilon wants to merge 209 commits intomainfrom
upstream-sync

Conversation

@SilkePilon
Copy link
Copy Markdown
Owner

@SilkePilon SilkePilon commented Apr 1, 2026

Latest Upstream Release: Superset Desktop desktop-v1.5.6

Tag: desktop-v1.5.6
Published: 2026-04-18T12:53:39Z


Release Notes

What's Changed

Full Changelog: superset-sh/superset@desktop-v1.5.5...desktop-v1.5.6


209 new commits from upstream.

This PR is automatically created and updated daily to keep the fork in sync with superset-sh/superset.

Kitenite and others added 30 commits March 25, 2026 16:50
…me (superset-sh#2885)

* fix(desktop): prevent [gone] from being stored as workspace branch name

The porcelain status parser incorrectly extracted "[gone]" as the branch
name when git reported "No commits yet on BRANCH...origin/BRANCH [gone]".
The old code split by space and took the last element; the fix strips the
prefix and applies the same tracking-info regex used for normal branches.

Also adds syncBranch validation to reject obviously invalid names and a
one-time startup repair that reads the real branch from the worktree HEAD
file for any existing corrupted records.

* fix(desktop): remove startup branch repair

* Refactor porcelain use gh

* test(desktop): cover porcelain v2 branch handling

* fix(desktop): handle missing HEAD during branch sync

* fix(desktop): narrow unborn HEAD recovery

* lint
)

* Fix MCP auth ordering and OAuth discovery

* Handle case-insensitive bearer auth and forwarded header lists

* Sanitize auth error logs and normalize API URLs

* Fail closed on invalid verified OAuth tokens
…invalidation (superset-sh#2900)

Ensures cached proxy responses are keyed per auth token, preventing
stale data when switching orgs or users.
…superset-sh#2901)

The proxy handles ~200 req/min — performance-4x (8GB) is vastly
over-provisioned. Saves ~$120/mo.
Better-auth defaults to 10 requests per 24 hours per API key. MCP
clients burn 3 requests on init alone, causing keys to stop working
after ~7 tool calls. Disables the rate limiter entirely.
…focus on pane nav (superset-sh#2676)

* fix(desktop): skip pane-nav shortcuts when input/textarea is focused

Cmd+Shift+Left/Right should allow text selection in chat prompts, not
hijack focus to the adjacent pane.

* fix(desktop): stop modifier+arrow propagation in chat textarea

Fix pane-nav hotkey hijacking at the source — the prompt input now calls
stopPropagation for Cmd/Ctrl+Arrow keys so they perform native text
selection rather than switching panes.

* fix(desktop): focus chat textarea when pane receives focus via keyboard nav

* fix(desktop): place cursor at end of input when chat pane receives focus

* refactor(desktop): extract useFocusPromptOnPane shared hook
…t-sh#2903)

* feat(desktop): cmd-click file paths opens in external editor

Cmd/ctrl-click on files in the file tree, search results, and changes
view now opens in the configured external editor. Double-click no
longer opens externally, freeing it for future pinning behavior.

* keep existing double-click open-in-editor behavior

* remove stale workspaceRun tests with drifted mocks
…et-sh#2557)

* feat(desktop): configurable light/dark themes for system mode

When "System" theme is selected, users can now choose which specific
theme to use for light and dark modes via dropdowns on the System card,
instead of always resolving to the built-in light/dark themes.

* fix(desktop): validate stale system theme IDs and ensure terminal color fallbacks

resolveThemeId now validates that persisted system theme IDs reference existing
themes, falling back to built-in light/dark when stale. initializeTheme
normalizes stale IDs on hydration so system mode is never silently dropped.
SystemThemeCard uses getTerminalColors() for guaranteed terminal colors instead
of accessing the optional .terminal property directly.

* fix(desktop): validate themeId in setSystemThemePreference before persisting

Reject invalid or recursive "system" IDs to prevent inconsistent persisted
state from non-UI callers.

* refactor(desktop): extract fallback theme IDs into shared constants

Replace repeated "light"/"dark" literals with DEFAULT_LIGHT_THEME_ID and
DEFAULT_DARK_THEME_ID to reduce drift risk during future refactors.
…-sh#2908)

* fix(desktop): restore workspace deletion from disk

Fixes bug introduced in PR superset-sh#2573 where workspace deletion only removed
database records but left worktree files on disk.

**Root cause:**
- `listExternalWorktrees()` was misnamed and returned ALL git worktrees
- Delete logic used this for safety checks, finding the worktree being
  deleted in the "external" list
- This caused disk deletion to be skipped incorrectly

**Changes:**
1. Renamed `listExternalWorktrees` → `listAllGitWorktrees` (accurate name)
2. Created proper `listExternalWorktrees(mainRepoPath, projectId)`:
   - Queries database for tracked worktrees
   - Returns only git worktrees NOT in database
   - Provides true "external worktrees" list
3. Removed `createdBySuperset` flag gating from deletion logic:
   - Users can now delete imported external worktrees
   - Safety check via `listExternalWorktrees` prevents deleting
     worktrees that haven't been imported
4. Updated all call sites to pass `projectId` parameter
5. Extracted `normalizePath` helper to git.ts for reuse
6. Simplified git-status.ts to use new implementation

**Testing:**
- All typechecks pass
- Existing tests updated (external-worktree-import.test.ts)
- Logic verified: tracked worktrees deleted, external preserved

* fix(desktop): add error handling to prevent stuck 'deleting' status

Wraps worktree deletion logic in try-catch to ensure workspace deleting
status is properly cleared if listExternalWorktrees() or other operations
fail.

**Problem:**
- If listExternalWorktrees() throws (git error, DB error, etc.), the
  workspace remains stuck in 'deleting' status
- User cannot retry deletion or interact with workspace
- Requires manual DB cleanup or app restart to recover

**Solution:**
- Wrap deletion section in outer try-catch
- Catch any errors from listExternalWorktrees and related operations
- Always call clearWorkspaceDeletingStatus() on error
- Return proper error response instead of crashing

**Changes:**
- workspace delete procedure: added error boundary around disk deletion
- deleteWorktree procedure: added error boundary for consistency

**Impact:**
- Prevents workspace from getting stuck in deleting state
- Provides clear error messages to user
- Allows user to retry deletion after fixing issues
…t-sh#2892)

The `run` field in `.superset/config.json` was undocumented. This adds
a section explaining how run scripts work (on-demand via Run button,
restartable, dedicated pane) and how they differ from setup/teardown.
Also adds the missing `SUPERSET_WORKSPACE_PATH` environment variable.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
superset-sh#2927)

* Clean enougH

* fix: biome lint/format fixes

* chore: remove unnecessary comments, clean up ws auth middleware

* fix: biome format fix

* fix: fix flaky external worktree test, remove broken workspaceRun test

* Clean enougH
…uperset-sh#2929)

* fix(desktop): improve external worktree detection in deletion safety check

Enhance the safety mechanism that prevents deletion of external worktrees
by cross-referencing git worktrees with database-tracked worktrees.

Previously, the check relied solely on listExternalWorktrees(), which could
miss cases where a worktree was incorrectly marked as createdBySuperset.

Now the logic:
- Gets all git worktrees from the repository
- Queries all tracked worktrees from the database for the project
- Normalizes paths for accurate comparison (handles symlinks)
- Determines if a worktree is external by checking if it exists in git
  but is NOT tracked in the database

This prevents accidental deletion of external worktrees that may have been
incorrectly flagged, providing an additional layer of safety.

Applied to both workspace.delete and workspace.deleteWorktree procedures.

* fix(desktop): delete external worktrees from disk when removed from Superset

Update deletion logic to remove all worktrees from disk when deleted through
Superset, regardless of whether they were created by Superset or imported as
external worktrees.

Previously:
- Superset-created worktrees (createdBySuperset=true) → deleted from disk
- External worktrees (createdBySuperset=false) → only removed from database

Now:
- All worktrees tracked in Superset → deleted from disk when removed
- Safety check still prevents deletion of untracked worktrees

This provides consistent UX: once a worktree is managed in Superset (even if
originally created externally), deleting it removes it completely.

The safety mechanism still protects against edge cases where a worktree exists
in git but is not properly tracked in the database.

Updated both workspace.delete and workspace.deleteWorktree procedures.
…s blocking new terminals (superset-sh#2963)

* fix(desktop): exit subprocess on PTY spawn failure to prevent zombie sessions (superset-sh#2960)

When pty.spawn() fails (e.g. posix_spawnp failed), the subprocess sent
an error frame but stayed alive. This left session.isAlive returning
true for a broken session with no PTY, causing TerminalHost to store
and attach to it — blocking new terminal creation.

Two fixes:
- pty-subprocess.ts: exit with code 1 after spawn failure so the daemon
  correctly detects the session as dead
- terminal-host.ts: also check session.pid after ready timeout to catch
  edge cases where subprocess is alive but PTY never spawned

* Deslop

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kiet Ho <hoakiet98@gmail.com>
…#2966)

* Marketplace

* fix(desktop,docs): wire marketplace links through env

* fix: address marketplace PR review comments
* desktop: make chat work in skip env mode

* lint
…ion (superset-sh#2907)

* feat(desktop): add CLOSE_WORKSPACE hotkey and context menu delete option (superset-sh#2742, superset-sh#2741)

Add ⌘+Backspace hotkey to close/delete the active workspace via
DeleteWorkspaceDialog, and expose a "Close Worktree"/"Close Workspace"
option in the sidebar context menu for all workspace types using the
existing deleteDialogCoordinator pattern.

Closes superset-sh#2742
Closes superset-sh#2741

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: freeze delete target and add cross-platform conflict tests

Address CodeRabbit review feedback:
- Freeze workspace data at hotkey press time to prevent stale target
  if the active workspace changes before dialog confirmation
- Extend conflict test to also check linux platform defaults

* Naming

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Kiet Ho <hoakiet98@gmail.com>
)

* fix(cost): remove device heartbeat polling to reduce Vercel costs

Replace 30s heartbeat polling with a single device registration on app
startup. This eliminates ~2,880 requests/device/day to Vercel while
keeping MCP command routing and ownership checks intact.

- Remove heartbeat interval from desktop and mobile, register once on mount
- Rename `device.heartbeat` to `device.registerDevice` (single-fire)
- Remove `device.listOnlineDevices` (unused)
- Remove online-check gate from MCP `executeOnDevice` (timeout handles offline)
- Remove Devices settings page and sidebar entry
- Remove `devicePresence` Electric collection (no longer needed client-side)
- Simplify `list_devices` MCP tool (no online/offline status)
- Remove online indicator from DevicePicker UI

* chore: remove v2DevicePresence Electric collection

No consumers remain after removing the presence join from
useWorkspaceHostOptions. Presence will be reimplemented via WSS.

* fix: scope device registration guard to org and userId

- Replace boolean registeredRef with org-scoped ref so switching orgs
  triggers re-registration on both desktop and mobile
- Scope executeOnDevice ownership query to ctx.userId to prevent
  returning another user's device row

* fix: keep deprecated heartbeat endpoint for shipped clients

Existing desktop/mobile clients still call device.heartbeat on a 30s
interval. Keep it around as a deprecated alias so they don't error out
until users update.
…ce modal (superset-sh#2980)

* Buttons

* Fix build

* hotkey

* Single CMD enter

* Lint
* fix(desktop): honor linked PR push targets

* fix(test): remove tests that poison module cache via mock.module

Bun's mock.module() leaks across test files (oven-sh/bun#12823) and
mock.restore() does not undo module mocks. Tests that call mock.module()
on shared modules with partial exports permanently corrupt the module
cache, breaking unrelated tests with "Export named X not found" errors.

Removed tests:
- merge-pull-request.test.ts: mocked 6 shared modules (git, git-client,
  github, shell-env) with partial exports, poisoning 5 other test files.
- editorCoordinator.test.ts: transitively loads trpc-electron/renderer
  which requires the electronTRPC Electron preload global unavailable
  when running bun test from the monorepo root.
- task.test.ts: mocked 9 shared modules (@superset/db/client,
  drizzle-orm, etc.) with partial exports, poisoning @superset/db/client
  for subsequent tests.

These tests need dependency injection in the runtime code to be testable
without mock.module(). They can be re-added once the functions accept
deps as parameters.

Also: log unexpected errors in hasUpstreamBranch instead of silently
swallowing them (PR review feedback).

* fix(ci): unblock test and typecheck

* fix(ci): harden merge-ref regressions

* fix(desktop): pin @pierre/diffs and support 1.1.7 hunks

* chore: drop unrelated CI-only changes from PR

* fix(ui): pin streamdown deps and narrow types

* fix(desktop): align mastra runtime versions

* fix(ci): patch mastracode metadata for desktop

* fix(ci): preserve runtime deps for desktop build

* fix(desktop): patch runtime metadata after copy
* chore(mastra): switch to upstream packages

* fix(desktop): address mastra CI regressions

* Upgrade mastra
…uperset-sh#2989)

* fix(desktop): clean up review sidebar UI and remove redundant states

- Replace inline AlertDialogs with reusable DiscardConfirmDialog component
- Simplify ReviewPanel loading skeleton to match app-wide pattern
- Flatten comment layout: show active comments directly, Resolved as own section
- Remove redundant review label that duplicated badge text
- Remove redundant PR state text already conveyed by icon color
- Remove duplicate comment count between parent and child sections
- Align collapsible trigger styles (padding, hover, chevron sizes) with CategorySection
- PR header: compact layout with hover-to-reveal external link
- Add section dividers between PR header, checks, and comments
- Push comment age to far right, absolutely position hover actions
- Clean up unused imports

* Update apps/desktop/src/renderer/screens/main/components/WorkspaceView/RightSidebar/ChangesView/components/ReviewPanel/ReviewPanel.tsx

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Kitenite and others added 30 commits April 15, 2026 17:38
…ache (superset-sh#3495)

* fix(desktop): bump electron-updater and clear cache on errors

Bumps electron-updater from 6.7.3 to 6.8.3 and wires a defensive cache
clear into the error handler. Addresses reports of users stuck on an
old version until they manually reinstall: electron-updater's internal
cache only self-invalidates on remote sha512 mismatch, so a silently
corrupt cached download (failed install, signature error, Squirrel
ShipIt failure) would be retried indefinitely.

Non-network errors now call DownloadedUpdateHelper.clear() so the next
4-hour check re-downloads from scratch.

* chore(desktop): tighten auto-updater cache recovery
…-sh#3480)

* fix(desktop): remove viewed checkboxes from v2 diff sidebar

The sidebar file list no longer shows checkboxes for marking files as
viewed. Removes the Checkbox component, viewed/onSetViewed props, and
the partitionByViewed utility from the changes sidebar pipeline.

* feat(desktop): add icons to v2 sidebar tabs and nest Review under Changes

Match the v1 sidebar layout: top-level tabs are Changes (GitCompareArrows
icon) and Files (FileText icon). Review is now a subtab within Changes
alongside Diffs, using the same Tabs/TabsTrigger pattern as v1.

* fix(desktop): use v1 icons and styles for v2 sidebar

Use LuGitCompareArrows and LuFile from react-icons/lu (same as v1),
getSidebarHeaderTabButtonClassName for the top-level tab buttons, and
sidebarHeaderTabTriggerClassName for the Diffs/Review subtabs — matching
the v1 sidebar layout and styling exactly.

* fix(desktop): center empty review tab content vertically

Make the tab content wrapper a flex column so TabsContent can fill the
available height, allowing the "Open a pull request" message to center
vertically instead of sticking to the top.

* feat(desktop): compact icon-only tabs when v2 sidebar is narrow

When the sidebar width drops below 250px, the Changes and Files tabs
collapse to icon-only buttons with tooltips, matching v1 behavior.
Uses the same getSidebarHeaderTabButtonClassName compact mode.

* fix(desktop): lower compact sidebar breakpoint to 200px

* feat(desktop): persist v2 sidebar active tab and subtab across sessions

Store activeTab (Changes/Files) and changesSubtab (Diffs/Review) in the
workspace local state collection so the sidebar reopens to the same tab
the user last had open.

* fix(desktop): code review fixes for v2 sidebar

- Remove duplicate React key on compact tab button (SidebarHeader)
- Stabilize setActiveTab/setChangesSubtab callbacks via refs so they
  don't bust the combinedChangesTab useMemo on every collection update
- Narrow activeTab schema from z.string() to z.enum(["changes","files"])
  with a runtime guard in the setter

* refactor(desktop): remove unnecessary useMemo/useCallback/refs from WorkspaceSidebar

No memo boundaries downstream benefit from stable references, so plain
functions and plain objects are simpler and equivalent.
…#3467)

* Create doc

* docs(desktop): finalize v2 launch context plan

Replace initial draft with V2-greenfield architecture: structured
AgentLaunchSpec (system/user/attachments ContentPart[]), per-agent
contextPromptTemplate on ResolvedAgentConfig, Uint8Array over IPC,
vendor-aligned (AI SDK v3, Anthropic cache_control, Continue.dev
contributor metadata). CLI agents keep disk + path-ref pattern;
chat agents get structured passthrough with Files API as phase 6.

* feat(desktop/context): add v2 launch context types and fixtures

Step 1 of the v2 launch-context composition plan. Defines the core
discriminated types (LaunchSource, ContextSection, LaunchContext,
AgentLaunchSpec, ContentPart with Uint8Array data) and the canonical
multi-source + prompt-only fixtures that the composer and
buildLaunchSpec tests will share.

No runtime code yet — types and fixtures only.

* feat(desktop/context): add composer with dedup, ordering, failure tolerance

Step 2 of the v2 launch-context composition plan.

buildLaunchContext parallel-resolves sources via a contributor registry,
dedups URL/id-kinds (attachments never dedup), preserves input order
within a kind, applies the default kind group order at the end,
tolerates per-source failures (populated on failures[]), and enforces a
10s per-contributor timeout.

taskSlug derivation: first internal-task section wins, falling back to
first github-issue. 12 tests pass.

* feat(desktop/context): add six contributors and default registry

Step 3 of the v2 launch-context composition plan. One contributor per
LaunchSource kind, each with Continue.dev-style metadata (displayName,
description, requiresQuery), its own co-located test file, and a
consistent 404 -> null (non-fatal) pattern for fetch-based kinds:

- userPrompt        -- trims, returns null on empty
- attachment        -- file or image ContentPart by mediaType
- agentInstructions -- system-scoped, cacheControl: ephemeral
- githubIssue       -- title + body markdown, meta.taskSlug from slug
- githubPr          -- title + branch + body markdown
- internalTask      -- title + description, meta.taskSlug

Also adds composer.integration.test.ts covering the real registry
end-to-end against the multi-source fixture. 41 tests green.

* feat(shared): generic renderPromptTemplate + context prompt variables

Step 4 of the v2 launch-context composition plan.

- Extract renderPromptTemplate(template, Record<string, string>) as the
  generic primitive; existing renderTaskPromptTemplate is now a shim
  (same API, same behavior — callers unchanged).
- Add AGENT_CONTEXT_PROMPT_VARIABLES (userPrompt, tasks, issues, prs,
  attachments, agentInstructions) + getSupportedContextPromptVariables +
  validateContextPromptTemplate.
- Ship default context templates for markdown (codex/cursor/custom) and
  Claude (XML-wrapped user-request) — both for system + user scopes.
- Collapse runs of 3+ newlines to 2 so empty variables produce clean
  output. Empty-string values substitute in (not treated as missing).

16 tests green; no consumer breakage.

* feat(agents): add contextPromptTemplate {system, user} to agent configs

Step 5 of the v2 launch-context composition plan.

Extends the agent config surface so V2 launches can render structured
context into per-agent system/user templates:

- packages/shared/agent-definition: required contextPromptTemplateSystem
  and contextPromptTemplateUser fields on BaseAgentDefinition;
  createTerminalAgentDefinition fills defaults with the markdown
  templates from step 4.
- packages/shared/builtin-terminal-agents: Claude terminal ships the
  Claude-XML defaults; other builtins inherit markdown defaults.
- packages/shared/agent-catalog: BUILTIN_CHAT_AGENT (Claude-based
  superset-chat) ships the Claude-XML defaults.
- packages/local-db/schema/zod: add both fields to AGENT_PRESET_FIELDS,
  agentPresetOverrideSchema, agentCustomDefinitionSchema (optional).
- apps/desktop/shared/utils/agent-settings: thread through
  TERMINAL_OVERRIDE_FIELDS, CHAT_OVERRIDE_FIELDS, AgentPresetPatch,
  CustomAgentDefinitionPatch, resolveAgentConfig (both branches),
  applyCustomAgentDefinitionPatch, createOverrideEnvelopeWithPatch.
- apps/desktop/test-setup: update the mocked @superset/local-db schema
  (the Bun test workaround for drizzle-orm/sqlite-core) so tests see
  the same shape as runtime.

New tests: contextPromptTemplate resolution for claude terminal,
codex markdown defaults, superset-chat claude defaults, terminal and
chat override replacement, custom terminal fallback to markdown. 113
tests green across context + agent-settings suites.

* chore: biome format pass

* feat(desktop/context): user-prompt source takes ContentPart[] (multimodal)

Future-proofs the user-prompt LaunchSource for an eventual rich-editor
input (interleaved text, inline images, inline files). The rest of the
pipeline was already ContentPart[]-native; this removes the last narrow
string-only call site.

- LaunchSource["user-prompt"]: { text: string } -> { content: ContentPart[] }
- userPromptContributor: normalizes (drops empty text parts, trims bookend
  whitespace), returns null when nothing remains, passes file/image parts
  through untouched.
- Adds userPromptFromText(text) helper for plain-string callers so
  modal/cli/task flows don't repeat the [{ type: "text", text }] boilerplate.
- Three new tests: multimodal text+image+text preservation, whitespace-only
  content returns null, empty text parts dropped between non-empty ones.

* refactor(desktop/context): drop agent-instructions source — harnesses handle it

Agent harnesses (Claude CLI, Codex, Cursor Agent) discover their own
conventions files natively from the worktree — no injection needed from
our side. V1 confirms: zero references to AGENTS.md/CLAUDE.md as
injected context. Only the agent itself reads them.

Removing this also gets us closer to the "no Electron IPC in V2" rule —
the composer no longer needs to read files from disk.

- Drop {kind: "agent-instructions"} from LaunchSource union.
- Delete agentInstructionsContributor + its test.
- Remove readAgentInstructions from ResolveCtx; update the three
  contributor test stubs that referenced it.
- Drop "agent-instructions" from the composer KIND_ORDER and
  sourceIdentity switch.
- Drop the AGENTS.md sample from the multi-source fixture + the
  composer integration test.
- Remove "agentInstructions" from AGENT_CONTEXT_PROMPT_VARIABLES.
- System default templates are now empty strings (chat agents get no
  system context yet; future host-service-backed path can fill later).

54 tests green across context + agent-settings; 16 green in shared.

* feat(desktop/context): add buildLaunchSpec (LaunchContext -> AgentLaunchSpec)

Step 6 of the v2 launch-context composition plan.

buildLaunchSpec(ctx, agentConfig):
- Returns null for "none" agent or missing config (V1-parity semantics).
- Pre-renders per-kind markdown sub-blocks ({{tasks}}/{{issues}}/{{prs}}/
  {{attachments}}) and a flattened {{userPrompt}} text variable from the
  LaunchContext sections.
- Fills the agent's contextPromptTemplate{System,User} (from step 5)
  into ContentPart[] arrays.
- Collects non-text parts (attachment-kind files/images + inline
  non-text parts from user-prompt) into the structured attachments[]
  field — chat agents see them as proper content parts, terminal
  adapters will flatten to disk refs in step 7.

Also fixes the multi-source fixture to match what contributors actually
emit (`# Title\n\nBody` markdown bodies) so the new snapshot tests
exercise a realistic LaunchContext shape.

15 tests green (2 inline snapshots for Claude-XML + codex-markdown
rendering of the canonical multi-source fixture). 69 tests green total
across context + agent-settings.

* Lint

* refactor(agents): drop Claude XML default template — markdown is enough

V1 never wrapped prompts in XML; V1 has shipped with bare markdown/text
forever. Shipping an XML-only Claude default was speculative and added a
per-agent divergence without evidence.

- Remove DEFAULT_CLAUDE_CONTEXT_PROMPT_TEMPLATE_SYSTEM / _USER.
- Claude terminal + superset-chat ship the default markdown templates
  (same as codex/cursor/custom).
- Users can still override per-agent in settings if XML wrapping helps
  their use case — defaults stay neutral.

Also ships scripts/demo-launch-spec.ts for local template iteration:
run `bun run scripts/demo-launch-spec.ts [agent...]` to eyeball what
buildLaunchSpec produces for canonical inputs.

66 tests green across context + agent-settings; 14 green in shared.

* feat(desktop/context): preserve inline order for rich-editor user prompts

buildLaunchSpec used to flatten the user-prompt section's ContentPart[]
to a single text blob. That lost inline ordering when a rich editor
produces text + image + text — the image landed in spec.attachments
disconnected from its position.

Now:
- Split the agent's user template on {{userPrompt}}; render each half's
  other placeholders raw (no trim / no newline collapse) so whitespace
  around the placeholder is preserved.
- Splice the user-prompt section's ContentPart[] in at the split
  position, keeping [text, image, text] ordering intact.
- Merge adjacent text parts, then collapse 3+ newlines to 2 and trim
  document boundaries in a final pass.
- spec.attachments now carries only *explicit* attachment-kind sections;
  inline non-text parts from user-prompt stay inline in spec.user.
- Inline parts still appear in the {{attachments}} list so CLI agents
  reading just the flattened text get a file-path reference.

Chat agents: hand spec.user straight to the Anthropic/AI SDK user
message content[] — model sees the image between the text chunks.
Terminal adapters (step 7) will flatten file/image parts to
`![filename](.superset/attachments/...)` markdown refs at their inline
position, then write files to disk.

Demo script gets two new scenarios exercising the rich-editor flow:
text+image+text alone, and the same with a linked issue. 67 tests
green across context + agent-settings.

* feat(desktop/context): add buildAgentLaunchRequest — V2 spec to V1 launch bridge

Step 7 (scoped): hand V2's AgentLaunchSpec off to V1's battle-tested
terminal-adapter / chat-adapter without building new execution
infrastructure. Bytes-over-IPC / SuperJSON transformer work is deferred
to a follow-up.

buildAgentLaunchRequest(spec, agentConfig, { workspaceId, source }):
- Returns null for agentId "none" or disabled agents (V1 parity).
- Assigns collision-safe filenames across all binary parts (inline in
  spec.user + explicit spec.attachments). Uses the same sanitize +
  dedup algorithm V1 already uses so nothing drifts.
- Flattens spec.user to markdown text with file/image parts rendered
  as `![filename](.superset/attachments/filename)` at their inline
  positions — rich-editor ordering survives into the CLI prompt.
- Converts Uint8Array binary data to base64 data URLs at this boundary
  (V1 wire format). Internal plumbing stays on Uint8Array.
- Chat: initialPrompt = flattened text, taskSlug/model flow through.
- Terminal: command = buildPromptCommandFromAgentConfig(flattened text),
  or the non-prompt command when the prompt is empty.

10 new tests cover: "none" short-circuit, terminal command rendering,
chat initialPrompt/taskSlug, inline image path-ref correctness, explicit
attachment filename preservation, filename dedup across user + attachments,
base64 data-URL format, workspaceId/source passthrough.

77 tests green across context + agent-settings.

* feat(desktop): add useEnqueueAgentLaunch hook

Step 8 of the v2 launch-context composition plan.

Thin wrapper around V1's useWorkspaceInitStore.addPendingTerminalSetup
for the V2 submit flow. Called after host-service.workspaceCreation
resolves the real workspaceId. V1's terminal-adapter / chat-adapter
pick up the pending setup when the workspace mounts and execute the
launch — no new adapter code needed.

- buildPendingSetup(args) — pure function, rewrites launchRequest
  workspaceId to the real id and assembles the PendingTerminalSetup.
- useEnqueueAgentLaunch() — React hook wrapper that calls into the
  zustand action.
- Null launchRequest is a no-op (nothing to enqueue, e.g. agent "none").

Tests cover: null short-circuit, workspaceId rewrite, projectId
passthrough, initialCommands shape, non-workspaceId field preservation.
5 tests green; typecheck clean.

* feat(desktop/v2): wire v2 workspace launch into pending page

Step 9 of the v2 launch-context composition plan. Closes Gaps 4, 5 in
V2_WORKSPACE_MODAL_GAPS.md for the "fork" intent (plain prompt + local
host). Gaps 3 (AI branch name) and 6 (create-from-PR) remain as
follow-ups.

What runs now:
- Modal submit → pending row → pending page fires createWorkspace.
- On success, buildForkAgentLaunch runs the V2 pipeline:
    sources <- pending row (user-prompt, linked issues/PRs/tasks, attachments)
    buildLaunchContext → buildLaunchSpec → buildAgentLaunchRequest
- useEnqueueAgentLaunch stashes the V1-shaped AgentLaunchRequest in
  useWorkspaceInitStore. V1's terminal-adapter / chat-adapter pick it
  up when the workspace mounts and execute the launch — no new adapter
  code needed.

New file buildForkAgentLaunch.ts is a pure helper: builds sources from
a PendingWorkspaceRow, stubs ResolveCtx from the same row's metadata,
runs the pipeline, returns an AgentLaunchRequest or null.

Phase 1 gap: issue / PR / task bodies are not fetched over HTTP yet —
host-service lacks a body endpoint. The resolver returns empty bodies,
so agents see title + URL + task-slug metadata only. Full-body
injection is a follow-up once host-service grows getIssueContent /
getPullRequestContent / getInternalTaskContent.

13 new tests cover: empty sources → null, no-agent → null, prompt-only
terminal launch via default agent, taskSlug derivation, attachment
passthrough, source-kind ordering. 88 tests green across pending +
context + agent-settings suites.

* Lint

* docs(desktop): add v2 launch context reference doc

Post-phase-1 reference: what shipped, manual + automated test plan,
known gaps, prioritized follow-ups, and a file-layout map. Lives in
apps/desktop/docs/ per AGENTS.md rule 7 (architecture docs). The
original plan stays in plans/ since phases 2-6 are still unshipped.

* chore(debug): add [v2-launch] console logs across the launch pipeline

Temporary logs for manual testing:
- pending page: what buildForkAgentLaunch returned + enqueue inputs.
- useEnqueueAgentLaunch: stash / null-short-circuit.
- WorkspaceInitEffects: every handleTerminalSetup + dispatch branch,
  launchAgentViaOrchestrator invocation.

Grep devtools console on "[v2-launch]" to trace a full submit.
Remove or soften once the dispatch path is dialed in.

* docs(desktop): document pending-row-as-bus launch dispatch

V2 must own its own launch dispatch. V1's WorkspaceInitEffects →
orchestrator → terminal-adapter path writes panes into V1's useTabsStore,
which V2 doesn't render from, so launches dispatched through V1 land
invisibly for V2 workspaces.

Documents the replacement: pending-row-as-bus. Pending page produces
terminalLaunch / chatLaunch fields on the collection-backed pending row;
V2 workspace page mount-effect consumes them, opens a pane in the
@superset/panes store, and wires PTY via workspaceTrpc.

This mirrors the pattern V2 preset execution already uses
(useV2PresetExecution): live-query a record, open a pane, call
workspaceTrpc.terminal.ensureSession. Zero V1 primitives, zero new
host-service work, and leaves a clean migration path to host-owned
terminal launch when phase 5 ships.

Adds a blocking follow-up (#0) for the dispatch rewrite; marks
useEnqueueAgentLaunch + buildAgentLaunchRequest for removal.

* feat(desktop/v2): rewrite launch dispatch as pending-row-as-bus

The original step-8/9 wire-up stashed an AgentLaunchRequest in V1's
useWorkspaceInitStore, expecting V1's WorkspaceInitEffects to dispatch.
V1's orchestrator writes panes into useTabsStore — which V2 never
renders from — so launches landed invisibly for V2 workspaces.

This rewrite keeps V2 self-contained. After host-service.create
resolves, the pending page runs the composer pipeline and stashes a
terminalLaunch or chatLaunch on the pending row. The V2 workspace
page's new useConsumePendingLaunch mount-effect live-queries that row,
opens a pane in @superset/panes, and drives PTY via workspaceTrpc.
Same pattern as useV2PresetExecution.

Changes:
- Schema: pendingWorkspaceSchema gains optional terminalLaunch and
  chatLaunch fields, cleared to null once consumed.
- buildForkAgentLaunch returns a PendingLaunchBuild union (terminal
  with attachmentsToWrite / chat with inline initialFiles) instead of
  the V1 AgentLaunchRequest shape.
- dispatchForkLaunch: new pending-page helper that runs the composer,
  writes attachments to .superset/attachments/ via workspaceTrpc
  .filesystem.writeFile for the terminal path, and applies the launch
  field to the pending row.
- useConsumePendingLaunch: new V2-workspace-page mount effect. Reads
  row by workspaceId, opens pane in V2 store, calls workspaceTrpc
  .terminal.ensureSession with initialCommand for terminal launches,
  clears the field.
- ChatPaneData gains a transient launchConfig slot. ChatPane and
  WorkspaceChatInterface thread initialLaunchConfig +
  onConsumeLaunchConfig through. After the V2 chat runtime auto-sends
  the initial message, it clears the pane's launchConfig.
- Rip out useEnqueueAgentLaunch hook, buildAgentLaunchRequest, and
  the debug logs in WorkspaceInitEffects.

23 tests green for buildForkAgentLaunch / buildLaunchSourcesFromPending;
type-check clean in the touched surface area.

See apps/desktop/docs/V2_LAUNCH_CONTEXT.md "Dispatch architecture".

* docs(desktop): add V2_LAUNCH_TEST_PLAN.md

Structured manual test checklist for the V2 launch dispatch pipeline:
terminal + chat happy paths, pending-row lifecycle, failure paths,
source-mapping edge cases, custom agents, cross-pane behavior, V1
regression.

Paired with copy-pasteable fixtures on ~/Desktop/v2-launch-test-artifacts/
(trace.log, notes.md, sample.png, prompts.txt, README) for drag-and-
drop testing.

* chore(debug): add url probe + submit logs for v2 attachment flow

Logs the blob/data URLs we get from the PromptInput provider at
submit time, then does a fetch() probe on each URL before
storeAttachments runs. Lets us see whether the URL is already dead
when useSubmitWorkspace fires — which would confirm a pre-submit
revocation (as opposed to a race inside storeAttachments itself).

Not a fix. Remove once the root cause is nailed down.

* fix(desktop/v2): pass converted files through PromptInput onSubmit

Root cause of the "Failed to fetch" attachment toast: the
PromptInput library calls clearComposer() before invoking onSubmit,
which revokes all blob: URLs stored in the provider. Our
useSubmitWorkspace was reading attachments back from the provider
via takeFiles() after that — so it got file entries whose URLs had
just been invalidated.

The library already does the blob→data-URL conversion itself and
passes the converted files into onSubmit's message arg. Use them
directly:

- useSubmitWorkspace now takes `files: SubmitAttachment[]` as an
  explicit argument. Drops the `useProviderAttachments()` dependency.
- handlePromptSubmit receives `{text, files}` from PromptInput and
  forwards the files.
- The existing Cmd+Enter keyboard fallback calls handleCreate()
  without files (unchanged behavior for the no-attachments path; the
  PromptInput's own Enter handler takes the file-carrying path).

* refactor(desktop): use dexie for the pending-attachment store

The prior hand-rolled IDB wrapper had two transaction-lifecycle bugs:

1. storeAttachments opened a readwrite transaction, then awaited
   fetch() on each file before calling store.put() — IDB auto-commits
   when the event loop yields with no pending requests, so the first
   put() fired against a finished transaction ("The transaction has
   finished.").
2. The same file (150+ lines of raw IDB callback plumbing) is exactly
   the shape of code where this class of bug keeps reappearing as
   the flow evolves.

Swap to Dexie 4 — the de-facto IndexedDB wrapper for apps (~11.9k⭐,
actively maintained, typed, handles transaction lifecycle correctly).

- storeAttachments: resolve blobs async outside any tx, then
  bulkPut() in one shot.
- loadAttachments / clearAttachments: where("key").startsWith(prefix).
- File collapses from ~150 to ~90 lines, no raw transactions, no
  cursor dance.

Behavior is identical from the caller's side. Schema version 1;
Dexie will open the existing database transparently (same DB name).

* chore(debug): add verbose [v2-launch] logs to dispatch + consume paths

Traces:
- dispatchForkLaunch start / built / chatLaunch-applied /
  terminalLaunch-applied
- useConsumePendingLaunch tick (live-query fires) + whether
  terminalKey / chatKey are already consumed
- consumeTerminalLaunch ensureSession + addTab + clear
- consumeChatLaunch addTab + clear

Grep devtools on "[v2-launch]" through the full submit -> open-workspace
flow. Lets us pin where dispatch stalls when no pane appears.

Temporary — remove once the end-to-end flow is nailed down.

* fix(desktop/v2): replace Buffer with browser-native base64 in renderer

Electron renderer doesn't expose Node's `Buffer` global (nodeIntegration
off). The fork-launch dispatch path and buildForkAgentLaunch were both
using `Buffer.from(...).toString("base64")` / `Buffer.from(base64, "base64")`
for binary <-> base64 conversion, which ReferenceError'd at runtime.

Swap to standards-based `btoa` / `atob` + a small byte <-> binary-string
helper. Works in renderer and Bun alike.

Applies to:
- dataUrlAttachmentToBytes (buildForkAgentLaunch.ts) — decode
  attachment data URL into Uint8Array.
- toBase64DataUrl (buildForkAgentLaunch.ts) — encode chat-bound files
  for ChatLaunchConfig.initialFiles.
- writeAttachmentsToWorktree (dispatchForkLaunch.ts) — encode bytes
  for host-service filesystem.writeFile's base64 content variant.

* docs(desktop): capture v2-launch footgun backlog

Seven items we caught during manual testing and intentionally deferred:

1. Deep solve for binary transport (blob URL / base64 fragility)
2. Reload-mid-launch spawns duplicate PTY (key terminalId off pending row)
3. Silent failure in consume hook — add toast
4. joinPath assumes POSIX — breaks for Windows hosts (phase 5)
5. Dexie schema coupling with pre-existing IDB store
6. PendingTerminalLaunch.attachmentNames unused by consumer
7. Remove [v2-launch] debug logs once flow is stable

Tracked in V2_LAUNCH_CONTEXT.md "Known footguns to revisit". None
are blocking phase-1 behavior; all have notes on the proper fix.

* feat(desktop/v2): toast on silent launch-dispatch failures

Seven silent swallow points across the launch path now surface a
toast so the user knows why the agent didn't auto-launch instead of
seeing "nothing happened":

- dispatchForkLaunch: buildForkAgentLaunch throw -> "Couldn't prepare
  agent launch" (description = error message).
- dispatchForkLaunch: buildForkAgentLaunch returned null AND user
  gave meaningful input -> warning "Workspace created but no agent
  launched" with hint to enable one in settings. Silent for the
  "fresh empty workspace, no agent configured" case (expected).
- dispatchForkLaunch: host-service URL not resolved -> "Couldn't
  reach host service".
- dispatchForkLaunch: writeAttachmentsToWorktree throw -> warning
  "Attachments didn't save to the workspace; agent will launch
  without files".
- writeAttachmentsToWorktree: missing worktreePath -> throw instead
  of silent return so the outer catch's toast fires.
- consumeTerminalLaunch: defensive bail -> "Couldn't open agent
  pane" (shouldn't happen, but defensive).
- consumeTerminalLaunch: ensureSession throw -> "Couldn't start
  agent terminal" with error message.
- pending page: loadAttachments throw in fork intent -> warning
  "Couldn't load saved attachments" (non-fatal, workspace still
  creates).

All keep their [v2-launch] console.warn/log so trace survives alongside
the toast.

* lint

* fix(desktop): address PR review — real issues only

Addresses the non-stale, non-debatable feedback from review bots:

- Prototype-chain substitution in prompt templates (agent-prompt-
  template.ts + buildLaunchSpec.ts): {{toString}} and similar now
  stay intact. Use Object.hasOwn() instead of `variables[key] ??`.
- renderTaskPromptTemplate no longer picks up generic 3+-newline
  collapsing — task-flow output matches V1 exactly: own-property
  substitution + trim only.
- buildLaunchSpec.renderUserTemplate tolerates whitespace in the
  placeholder: {{ userPrompt }} / {{userPrompt}} / {{  userPrompt  }}
  all match.
- Pending page's fork dispatch fetches agent configs imperatively
  via trpcUtils.settings.getAgentPresets.fetch() instead of reading
  from a useQuery hook — eliminates the race where a not-yet-
  resolved query silently skipped the dispatch and lost the launch
  for a successful workspace create.
- Drop ContextSection.scope field. It was never read (buildLaunchSpec
  ignored it); no contributor populated anything but "user" after we
  removed agent-instructions. Cleaner type + future re-introduction
  when a real system-scope consumer lands (phase 6 host-side
  instructions injection).

Tests: 54 context-suite passing, 14 shared-suite passing; desktop
typecheck clean in touched areas.

* docs(desktop): capture body-fetching gaps observed in manual test

Claude currently sees title-only for linked issues / PRs / tasks —
no bodies. Documents the gap, what V1 did (Electron IPC to
projects.getIssueContent), why we can't reuse it for V2 (no Electron
in V2 rule), and proposes the host-service procedures + stub swap.

Also covers:
- Empty `Branch:` in PR block — pending-row schema doesn't carry
  branch; fix via getPullRequestContent body fetch.
- Sanitization helpers to extract from V1 into a shared util.
- Attach-as-file vs inline-in-prompt decision (V1 attached,
  current V2 inlines — keeping inline for phase 1).

Ordered work plan at the bottom: getIssueContent first, then PR,
then internal-task (requires scoping). Acceptance criteria shows
the expected prompt shape after the fixes land.

* Update PR notes

* lint

* feat(desktop/v2): attachment framing + PR checkout hint + gap doc rewrite

Prompt refinements from manual testing:

- buildLaunchSpec {{attachments}} block now includes a short framing
  header: "Attached files — read them to understand the request."
  Cues the agent to actually use the files rather than treating them
  as passive metadata. Only appears when there are files.

- githubPr contributor says "Branch `X` is checked out in this
  workspace — commits you make continue this PR." Confirms to the
  agent that the worktree is on the PR's branch, so it shouldn't
  create a new branch or open a new PR.

- V2_LAUNCH_CONTEXT_GAPS.md rewritten with locked design decisions:
  bodies inline in prompt (no file writes for linked context), no
  truncation, no sanitization, PR checkout is true. Work plan:
  host-service getIssueContent → getPullRequestContent → task body
  API → swap stubs. Target prompt shape included.

54 tests green; 2 snapshots updated for new PR format.

* feat(desktop/context): explicit kind labels in contributor headers

Agents shouldn't guess whether a section is a task, issue, or PR from
context clues. Each contributor now prefixes its heading with the kind:

- `# GitHub Issue superset-sh#123 — Auth middleware stores tokens in plaintext`
- `# Task TASK-42 — Refactor auth middleware`
- `# PR superset-sh#200 — Rewrite auth middleware`

PR phrasing also clarified: "This PR is checked out in this workspace
on branch `fix/auth-encryption`. Commits you make here will be added
to this PR."

54 tests green; 2 snapshots updated.

* feat(desktop/v2): fetch issue + PR bodies via host-service, task via cloud API

The launch prompt now includes full bodies for linked GitHub issues,
PRs, and internal tasks instead of title-only stubs.

Host-service (packages/host-service):
- getGitHubPullRequestContent: new procedure wrapping octokit.pulls.get.
  Returns body, branch, baseBranch, author, isDraft, timestamps.
  (getGitHubIssueContent already existed.)

Client (apps/desktop pending page):
- buildForkAgentLaunch accepts an optional hostServiceClient. When
  provided, the issue + PR resolvers call getGitHubIssueContent /
  getGitHubPullRequestContent for full bodies. Falls back to
  pending-row title-only if the call fails (non-fatal).
- Task resolver calls apiTrpcClient.task.byId (Superset cloud API,
  same source as the task view) for description. Falls back to
  title-only on failure.
- dispatchForkLaunch threads the host-service client through.

Contributors (already landed earlier this session):
- GitHub issue header: `# GitHub Issue #N — Title`
- PR header: `# PR #N — Title` + "This PR is checked out in this
  workspace on branch `X`. Commits you make here will be added to
  this PR."
- Task header: `# Task ID — Title`
- Attachments block: framing header cueing the agent to read the
  files.

77 tests green. Typecheck clean.

* chore(desktop): fix biome warning + stale doc comment in fork launch

- internalTask.test.ts: replace `TASK.description!` non-null
  assertion with `if (TASK.description)` guard (biome
  lint/style/noNonNullAssertion).
- buildForkAgentLaunch.ts: update stale docstring that claimed
  bodies aren't fetched yet — they are, via host-service and the
  cloud task API.

77 tests green, biome clean, typecheck clean.

* fix(host-service): shell out to gh CLI for issue/PR content (V1 parity)

host-service's octokit path needs a GitHub token from
providers.credentials.getToken("github.com") — which most users don't
have set up (requires GITHUB_TOKEN env or git credential helper config
for github.com). Result: getGitHubIssueContent / getGitHubPullRequestContent
silently 500'd, buildForkAgentLaunch fell back to title-only, and the
agent received empty bodies for linked issues/PRs.

V1's projects.getIssueContent shells out to `gh issue view` via the
user's `gh auth login` — that works out of the box.

Port the same approach:

- New packages/host-service/src/trpc/router/workspace-creation/utils/exec-gh.ts
  — promisified execFile("gh", ...) with user shell env so PATH
  resolves on macOS GUI contexts.
- getGitHubIssueContent now calls `gh issue view <n> --repo owner/name
  --json number,title,body,url,state,author,createdAt,updatedAt`.
- getGitHubPullRequestContent calls `gh pr view <n> --repo owner/name
  --json number,title,body,url,state,author,headRefName,baseRefName,isDraft,...`.
- Zod-validate the JSON output before returning.
- Normalize state to lowercase (gh returns "OPEN"/"CLOSED" uppercase).

Drops the Octokit dependency on these two procedures. Other host-service
paths that still use ctx.github() unchanged.

* fix typecheck

* clean up
…set-sh#3511)

OPEN_IN_APP was registered both in OpenInMenuButton and in the v1
workspace page, so pressing Cmd+O ran two separate openInApp mutations
(one per component's own useMutation instance) while clicking the
button only ran one. Remove the page-level registration so the shortcut
goes through the same handler as the click.
…ch (superset-sh#3509)

* chore(deps): upgrade tanstack/db + electric, drop durable-streams patch

- @electric-sql/client 1.5.13 → 1.5.14 (api, desktop, mobile)
- @tanstack/db 0.5.33 → 0.6.4 (desktop, mobile)
- @tanstack/electric-db-collection 0.2.41 → 0.3.2 (desktop, mobile)
- @tanstack/react-db 0.1.77 → 0.1.82 (desktop, mobile)
- @durable-streams/client ^0.2.1 → ^0.2.3 (api, desktop)
- Remove @durable-streams/state@0.2.1 patch — state package isn't imported anywhere; the patch only stripped console.logs

Type fixes for @tanstack/db 0.6 row virtual props ($synced/$origin/$key/$collectionId): cast pending rows back to PendingWorkspaceRow and use SelectTaskStatus directly in handleStatusChange so discriminated-union narrowing and callback variance keep working.

* chore(deps): drop 7-day release-age policy, bump to latest patches

- Remove `minimumReleaseAge` from bunfig.toml
- @electric-sql/client 1.5.14 → 1.5.15 (api, desktop, mobile)
- @tanstack/db 0.6.4 → 0.6.5 (desktop, mobile)
- @tanstack/electric-db-collection 0.3.2 → 0.3.3 (desktop, mobile)
- @tanstack/react-db 0.1.82 → 0.1.83 (desktop, mobile)
…superset-sh#3526)

* docs(desktop): add v2 file editor audit + implementation plans

* feat(desktop): v2 file editor foundation — shared document store + registry + CodeView

* feat(desktop): v2 file editor state machine + chrome — pendingSave/saveError/conflict/orphaned/hasExternalChange + banners + conflict dialog

* feat(desktop): v2 file editor views + toggle — MarkdownPreviewView, ImageView, BinaryWarningView, FileViewToggle

* feat(desktop): v2 file editor fs:events — orphan detection, rename tracking, external-change reload

* feat(desktop): v2 file editor close-pane save guard — wire Save action via non-hook fileDocumentStore.getDocument

* chore(desktop): remove superseded v2 file editor code — old renderers + host-service useFileDocument

* feat(desktop): move v2 file pane view toggle into the tab header via renderHeaderExtras

Also inline FileIcon inside renderTitle so the file icon keeps rendering —
the default pane header uses `titleContent ?? (icon + title)`, and upstream
3dd1de2 introduced the custom renderTitle for italic name + dirty dot
without including the icon, suppressing it.

* feat(desktop): v2 file editor visual polish — Lucide fold chevrons/placeholder, contour selection, palette-native highlight

- Fold markers now render as Lucide chevron SVGs via foldGutter's markerDOM, aligned per-line-box regardless of font metrics. Hidden at rest, fades in on gutter hover.
- Fold placeholder (collapsed block) renders as a Lucide MoreHorizontal button via codeFolding's placeholderDOM, with rounded corners + subtle hover background.
- Custom selection layer (contourSelectionLayer): per-line RectangleMarkers snug to each line's actual text width, extending 4px past the last character, with a half-line-height stub for empty middle lines. Consecutive rects abut exactly. CM's default .cm-selectionBackground hidden — our layer is the only painter.
- Selection background and active-line highlight share one palette-derived token (foreground at ~3% alpha), so they read as the same visual weight. Active-line hidden while a selection is active (selectionClassTogglePlugin toggles .cm-hasSelection on the editor root).
- Dropped the gutter/content separator border. Line numbers get more left padding, less right padding now that there's no vertical rule.
- @replit/codemirror-css-color-picker adds inline swatches on CSS color literals.
- Exported withAlpha from shared/themes so editor theme can derive low-alpha variants without hardcoded rgba.

* refactor(desktop): organize v2 CodeEditor into folder-per-module + dedupe view resolution

Per AGENTS.md "one folder per module with barrel index.ts":
- Extract inline helpers from CodeEditor.tsx into sibling extension folders:
  extensions/foldChevron, foldPlaceholder, contourSelectionLayer,
  selectionClassTogglePlugin. Hoists TRAILING_PAD / EMPTY_LINE_WIDTH_RATIO to
  module scope alongside their layer.
- Move loose CodeMirror utility modules into their own folders:
  CodeEditorAdapter/, createCodeMirrorTheme/, loadLanguageSupport/ (with
  streamLanguages co-located as its only consumer), syntax-highlighting/.
- Add registry/resolveActivePaneView/ — shared helper consumed by FilePane
  and FilePaneHeaderExtras to compute views + active view identically. Kills
  ~12 lines of duplication and removes one class of desync bugs.

CodeEditor.tsx drops from 425 → 256 lines, focused purely on the React component.

* refactor(desktop): move resolveActivePaneView under registry/utils/

AGENTS.md groups shared utility functions under utils/; relocate so
the registry matches the convention.

* feat(desktop): v2 file editor stability pass — rename tracking, save guards, conflict UX

- Store passes trpcClient per-entry at acquire time so there's no global
  init race on hard reload; removes activeTrpcClient + requireClient.
- Rename-safe acquire/release: iterate entries via snapshot to avoid
  mid-iteration map-insert loops; detect atomic-save renames (target
  matches open entry) and treat as content update; reuse handle when
  the entry was already migrated to the new path to avoid refcount leaks.
- Preview-pane retarget: useSharedFileDocument now swaps handles on
  workspaceId/absolutePath prop changes via setState-during-render so
  the view never shows a stale file.
- FilePane auto-follows renames by reconciling data.filePath from
  document.absolutePath, and auto-pins on first dirty transition.
- Dirty state no longer mirrored into pane data; the tab title reads
  document.dirty live via a small FilePaneTabTitle component, which
  eliminates the store-cascade on first keystroke. Drop hasChanges from
  FilePaneData, and switch onBeforeClose / onBeforeCloseTab to read
  getDocument(ws, path)?.dirty.
- Stable entry.id exposed on SharedFileDocument; CodeEditor keys on it
  so rename doesn't remount the editor and undo history is preserved.
- Rename no longer flags hasExternalChange — a path change isn't a
  content change; disk conflicts continue to surface at save time.
- Replace ExternalChangeBar + MultiFileDiff ConflictDialog with a
  VS Code-style alert (Save / Don't Save / Cancel) fired from FilePane.
- CLOSE_TERMINAL usages in v2 swapped to CLOSE_PANE; the hotkey handler
  now runs the pane registry's onBeforeClose before closing.
- Copy Path switches from electronTrpc to navigator.clipboard so it
  works when the host service is remote.
- Active-line highlight bumped and switched to accent token; Cmd+Shift+/
  vacated for the editor's built-in comment toggle on Cmd+/.

* fix(desktop): v2 "Don't Save" now discards the dirty buffer

Dirty document entries never dispose (refCount <= 0 && !dirty rule), so
choosing "Don't Save" on close left the entry floating in the store.
Reopening the same file reattached to that stale dirty buffer, which
looked like dirty state bleeding between files.

Both close prompts now reload the buffer to disk before resolving, and
the multi-file tab close's "Save All" actually saves each dirty pane
via doc.save() instead of the previous TODO no-op.

* feat(desktop): v2 image + binary + large-file handling

- Images load via binary encoding (readAsBinary = isImageFile) so PNGs
  and friends don't go through a corrupting utf-8 decode path.
- ImageView renders on a 1:1 checkerboard underlay (10% foreground mix)
  so transparency reads correctly against either theme.
- Non-image binaries stay as utf-8 text so "Open Anyway" can actually
  render them in the code view. The isBinary flag is still used for
  view resolution.
- binaryWarningView demoted from exclusive → default priority, and
  codeView no longer matches binary files, so an image's view toggle
  shows [Image] only rather than [Binary, Image], and unknown binaries
  default to the BinaryWarning with code as a toggle fallback.
- Default load cap bumped 2 MB → 10 MB. too-large now offers an
  "Open anyway" button via a new document.loadUnlimited() method.

* feat(desktop): make v2 markdown preview read-only

Matches VS Code's model — preview is a view of the source, not an
editor. Avoids TipTap's markdown round-trip drift (parse → ProseMirror
→ serialize) that would spuriously mark files dirty and reformat on
save. Edits continue to happen in the code view; preview re-renders
from the shared document.

* fix(desktop): address PR feedback from coderabbit review

- save(): preserve live buffer when keystrokes arrive during an
  in-flight write — only bump revision/savedContentText on success.
- loadEntry: distinguish ENOENT (not-found) from transient/permission
  errors; new { kind: "error" } content state + load-failed ErrorState
  with a Retry button.
- resetForLoad helper shared by reload() and loadUnlimited() so both
  clear conflict/hasExternalChange/saveError consistently.
- useCopyToClipboard: navigator.clipboard primary + offscreen-textarea
  + execCommand fallback (matches VS Code's browserClipboardService);
  preserves caller's selection range.
- PathActionsMenuItems uses toast.promise for copy feedback.
- CLOSE_PANE hotkey guarded with isClosingPaneRef against re-entrant
  prompts when Cmd+W is pressed rapidly.
- ImageView switches from btoa(base64) data URLs to
  URL.createObjectURL(Blob) with cleanup; ~3× lower peak memory.
- CodeEditorAdapter cut/paste async callbacks no-op after dispose.
- MarkdownPreviewView drops the now-dead onSave prop.
- Active-line and selection backgrounds share a theme.ui.accent @
  0.5 alpha so the selection is actually visible against the theme.
- setup/steps.sh: Electric container now uses --restart on-failure:5
  so a container stuck on a Neon session orphan doesn't loop
  indefinitely without the setup-script cleanup path running again.
…t-sh#3525)

* docs(desktop): plan for v2 PR checkout via widened checkout procedure

Design doc for wiring up linkedPR → worktree materialization in the v2
new workspace modal. Extends workspaceCreation.checkout with an optional
`pr` field (shells to `gh pr checkout`) rather than adding a new tRPC
procedure; client keeps a distinct `pr-checkout` pending intent for
progress labels and payload construction.

* docs(desktop): refine v2 PR checkout plan after walkthrough

- Drop attach-time PR fetch; reuse existing getGitHubPullRequestContent call
  at pending-page time (zero net new fetches)
- LinkedPR + pending-row schema stay narrow; PR content fetched on demand
- Share branch.<name>.base config write across branch + PR paths via
  composer.baseBranch; fixes current gap where only create records base
- Confirm forkOwner/headRefName naming over gh default (collision safety
  for our workspace-manager use case)

* feat(host-service): PR-checkout mode in workspaceCreation.checkout

Widen the checkout procedure with an optional `pr` field (structured PR
metadata) so the modal's linkedPR can materialize the PR branch into a
worktree via `gh pr checkout`. Exactly one of `branch` or `pr` enforced
at the zod layer.

- `getGitHubPullRequestContent` surfaces `headRepositoryOwner` and
  `isCrossRepository` (already returned by `gh pr view`, now mapped)
- `derivePrLocalBranchName` pure helper: `<forkOwner>/<headRefName>` for
  cross-repo PRs, head ref as-is for same-repo
- New `finishCheckout` local helper: `branch.<name>.base` config write +
  cloud register + rollback + local insert + setup terminal. Called from
  both the new PR path and the existing branch path — regular checkouts
  now also record their base (previously a gap where only `create` did)
- PR path: detached worktree → `gh pr checkout --branch <derived> --force`
  → push.autoSetupRemote; returns `alreadyExists: true` on idempotent
  re-checkout of an existing workspace's branch
- `composer.baseBranch` added; client fills from `pr.baseRefName` (PR mode)
  or picker selection (branch mode)
- Warning surfaced when PR state is not "open"
- `execGh` accepts cwd/timeout options; falls back to raw stdout when
  output isn't JSON (for `gh pr checkout`, which doesn't return JSON)

* feat(desktop): wire pr-checkout intent through pending page + agent launch

Modal routes to `intent: "pr-checkout"` whenever a linkedPR is attached
(replaces the old fork-with-PR behavior that never checked out the PR's
branch). The pending page fetches `getGitHubPullRequestContent` once,
feeds the result into both the `checkout` mutation's pr payload and the
agent-launch resolver — zero net new fetches vs the previous flow, which
fetched the same data later for the prompt body.

- pendingWorkspaceSchema.intent: "pr-checkout" added
- useSubmitWorkspace: selects intent + placeholder name/branch from
  linkedPR when present
- useCheckoutDashboardWorkspace: CheckoutWorkspaceInput widened with
  optional pr, composer.baseBranch
- buildIntentPayload: buildPrCheckoutPayload pure builder + unit tests;
  buildCheckoutPayload plumbs composer.baseBranch
- page.tsx useFireIntent: new pr-checkout case — imperative
  getGitHubPullRequestContent → buildPrCheckoutPayload → checkout.mutate
  → resolvedPr passed to dispatchForkLaunch so the agent-launch resolver
  skips a re-fetch
- buildForkAgentLaunch: accepts resolvedPr; fetchPullRequest resolver
  returns it directly when URL matches
- dispatchForkLaunch: threads resolvedPr through

* fix(host-service): warn user when pr-checkout clobbers existing local branch

Before running \`gh pr checkout --force\`, probe for a pre-existing local
branch with the derived name via \`git show-ref --verify\`. If found,
surface a warning pointing at \`git reflog\` for recovery — the user's
point-and-click flow still completes, but they're informed and can
recover any unpushed commits that got reset.

Addresses PR review feedback (coderabbit/greptile/cubic-dev-ai): the
idempotency check rules out Superset-managed workspaces but not stray
branches created by a user's prior manual \`gh pr checkout\` in the
primary working tree. Blocking would force CLI-level recovery, which is
poor UX for a modal-driven flow; warning + reflog recovery is the
right balance.

* fix(host-service): harden pr-checkout against whitespace refs + deleted forks

Two remaining PR review items:

1. `derivePrLocalBranchName` now trims `headRefName` before the empty
   check — previously a whitespace-only input (e.g. "   ") slipped past
   the guard and produced a garbage branch name like "owner/   ".

2. `PrSchema.headRepositoryOwner` is now nullable — `gh pr view` returns
   null when the PR's head fork repository has been deleted, which
   previously crashed the zod parse. The getGitHubPullRequestContent
   return mapping uses `?.login ?? null`; buildPrCheckoutPayload fails
   fast on cross-repo PRs with null owner with a clear user-facing
   message ("head fork repository has been deleted") rather than letting
   the opaque server error bubble up. Same-repo PRs pass through an
   empty owner string (unused by the derivation).
…ce menu (superset-sh#3537)

* feat(desktop): promote "Create Section Below" to top-level on workspace menu

The workspace context menu previously hid section creation inside the
"Move to Section" submenu, and the new section always appended to the
end of the project — making "create" and "move" feel like the same
action. Promote section creation to a top-level item, inserting an
empty section directly below the right-clicked workspace so it matches
the label, and leave "Move to Section" as a list of existing sections
(hidden entirely when none exist).

* refactor(desktop): separate "Create Section Below" from "Move to Section" with divider
… reveal (superset-sh#3512)

* feat(desktop): v2 terminal honors terminalLinkBehavior setting

When the user's "Terminal file links" setting is "file-viewer", Cmd/Ctrl-clicking a file path in a v2 workspace terminal now opens the file in an in-app FilePane instead of the external editor — matching the v1 behavior the setting already controls.

Directories and the "external-editor" setting continue to fall through to the existing openFileInEditor path.

* feat(desktop): modifier-keyed v2 terminal file links + folder sidebar reveal

Replaces the settings-based branch with a modifier-key pattern:

- Cmd/Ctrl-click a file path → opens in an in-app FilePane.
- Cmd/Ctrl+Shift-click a file or directory → opens in the external editor (with an upfront toast for remote workspaces, same pattern as FilesTab's Open in editor guard).
- Cmd/Ctrl-click a directory path → force-opens the sidebar, reveals the folder in the file tree (ancestors expand, row scrolls into view and highlights).

Implementation reuses the existing selectedFilePath → fileTree.reveal machinery in FilesTab by promoting selectedFilePath from a pane-store derivation to a useState, synced from the active file pane via useEffect. Folder focus is just a direct setSelectedFilePath — the existing sidebar code path handles reveal + scroll + highlight without changes. Folder paths now also flow through getParentForCreation, so the "New File" toolbar button creates inside the focused folder.

Three callbacks (onOpenFile / onRevealPath / onOpenExternal) are plumbed from page.tsx through usePaneRegistry to TerminalPane. The shift-modifier path goes through openExternal, which checks workspaceHost.hostMachineId !== machineId and toasts for remote workspaces instead of firing a mutation the remote won't satisfy.

v1 code untouched; DB schema untouched; v2 settings UI untouched (terminalLinkBehavior still honored by v1).

* refactor(desktop): drop callback refs in v2 TerminalPane, use effect deps directly

* refactor(desktop): move v2 pane actions into a PaneActionsProvider context

Removes the terminal-specific callback plumbing from usePaneRegistry (which should only care about how to render each pane kind) and moves onOpenFile / onRevealPath / onOpenExternal into a React context scoped to the workspace page. TerminalPane consumes via usePaneActions() instead of taking them as props.

* refactor(desktop): drop PaneActionsProvider, pass actions through usePaneRegistry

The context indirection wasn't worth it for a single consumer (TerminalPane). Passing the three callbacks through usePaneRegistry options is simpler and has no actual downside since usePaneRegistry is only called from one place anyway.

* fix(desktop): v2 terminal folder reveal switches sidebar to Files tab

Previously revealing a directory only toggled rightSidebarOpen + setSelectedFilePath, but the sidebar would stay on whichever tab the user had last (e.g., Changes/Review), leaving FilesTab unmounted so the reveal effect never fired. Update the same v2WorkspaceLocalState transaction to also force sidebarState.activeTab back to "files".

* fix(desktop): auto-expand revealed directory + extract useOpenInExternalEditor

- useFileTree.reveal now also expands the target itself when it's a directory,
  using stateRef so there's no staleness concern. All reveal call sites benefit.
- Extract useOpenInExternalEditor hook (remote check + toast + mutate) so
  TerminalPane can consume it directly instead of through a callback. Drops one
  prop from usePaneRegistry and removes the local workspaceHost liveQuery from
  page.tsx. FilesTab's handleOpenInEditor could migrate to the same hook in a
  follow-up to dedupe the pattern.

* feat(desktop): v2 terminal URL links open in internal browser by default

Cmd/Ctrl-click a URL in a v2 terminal now opens a BrowserPane in the current
tab. Cmd/Ctrl+Shift-click still opens in the external browser.

Widens TerminalLinkHandlers.onUrlClick to receive the MouseEvent (v1's helper
just threads it through unused — behavior unchanged).

* feat(desktop): v2 terminal shows hover tooltip describing cmd-click action

Adds onLinkHover/onLinkLeave callbacks to TerminalLinkHandlers, wired through
LinkDetectorAdapter, UrlLinkProvider, and WordLinkDetector so every detected
link participates.

In v2 TerminalPane, a new LinkHoverTooltip component tracks hover + live
modifier state (global keydown/keyup listeners scoped to hover duration) and
portals a positioned tooltip to document.body when meta/ctrl is held. Content
flips on shift:
- File:   Open in editor       | shift: Open externally
- Folder: Reveal in sidebar    | shift: Open externally
- URL:    Open in browser      | shift: Open in external browser

v1's helpers.ts doesn't opt into the new callbacks, so v1 hover behavior is
unchanged.

* refactor(desktop): match tooltip styling, surface configured editor name

- Tooltip now uses the same bg-foreground/text-background/rounded-md/px-3/py-1.5/text-xs
  tokens as the project's TooltipContent, so it visually matches the rest of
  the app (was a custom border/popover style before).
- Shift-modifier tooltip text now says "Open in Cursor" / "Open in VS Code" /
  etc. based on the user's configured defaultEditor setting, resolved via
  electronTrpcClient.settings.getDefaultEditor + getAppOption display label.
  Falls back to "Open externally" if no editor is configured.

* refactor(desktop): split LinkHoverTooltip hook/component, tighten modifier listener

- Move useLinkHoverState into its own hooks/ folder (was exported alongside
  the component in violation of the one-component-per-file rule).
- Effect now re-subscribes on hover start/end only (deps: hovering boolean),
  not on every modifier change.
- Filter to Meta/Control/Shift/Alt key events so typing a letter while
  hovering doesn't churn state.
- Skip setState when modifier/shift values didn't actually change, avoiding
  identity-change re-renders on repeat keydowns.
- Extract tooltip offset constant.

* fix(desktop): block external open while host data loads, surface editor-query failures

- useOpenInExternalEditor now treats an unloaded workspaceHost as non-local
  (workspaceHost?.hostMachineId !== machineId) so Cmd+Shift-click doesn't
  fire the mutation against a potentially remote workspace before locality
  is confirmed.
- LinkHoverTooltip's getDefaultEditor catch now console.warn's the error
  before falling back to null, so settings RPC failures stay observable.
…uperset-sh#3517)

* remove 7 day rule

* Upgrade mastra

* upgrade ai

* Ad mastra

* refactor(desktop): remove dead provider-diagnostics plumbing

The provider-diagnostics store was fed by callSmallModel's per-attempt
reporting, which was removed when small-model tasks moved to direct AI-SDK
+ mastracode's AuthStorage. Nothing writes to the issue map anymore, so the
clearIssue mutation, getStatuses query, and diagnosticStatus plumbing in
ModelsSettings were all no-ops.

Settings still surfaces "Session expired / Reconnect" via auth-status alone.
ProviderIssue type collapsed from 8 codes to just "expired" to match.

* fix(auth): auto-refresh expired Anthropic OAuth tokens

Anthropic credentials were read via authStorage.get() everywhere, so
mastracode's built-in refresh flow never ran. Once the 1-hour access
token expired, status flipped to "Reconnect" and users had to do a
full PKCE re-auth, even though a valid refresh token was already
stored.

Resolvers now call authStorage.getApiKey() for oauth creds on expiry,
which triggers refreshToken() and persists the refreshed credential.
getAnthropicAuthStatus does the same before declaring issue: "expired".
Mirrors the pattern already used for OpenAI small-model auth.

* review: address PR feedback from cubic + coderabbit + greptile

- host-service ai-branch-name: run trailing-trim after slice so a
  100-char truncation can't re-introduce a bare "." or "-" that git
  rejects as an invalid ref (coderabbit / cubic #2, superset-sh#7).
- host-service workspace-creation.generateBranchName: reuse the
  existing listBranchNames helper instead of the inline git walk,
  which classified off the short refname and could conflate a local
  "origin/foo" with refs/remotes/origin/foo (coderabbit superset-sh#3).
- packages/chat shared/small-model: drop the unused
  hasSmallModelCredentials export; only a test mock consumed it
  (greptile superset-sh#4).
- resolveAnthropicCredential: on refresh failure, return null instead
  of kind:"oauth" with a stale expiresAt so callers fall back cleanly
  (cubic superset-sh#8).
- chat-service.getAnthropicAuthStatus: log context when refresh throws
  instead of silently swallowing (cubic superset-sh#9).

* fix(chat): read auth.json directly instead of importing mastracode

Importing createAuthStorage from mastracode loads the entire CLI tree
(fastembed → onnxruntime-node's 208 MB native binary) via eager
top-level requires in mastracode's CJS entry. This crashed
electron-vite bundling and bloated the get-small-model chunk.

getSmallModel now reads mastracode's auth.json file directly using
the same path resolution logic (~/Library/Application Support/mastracode/
on macOS). Zero mastracode import, zero bundle impact. The chunk stays
at 1.2 MB (just @ai-sdk/anthropic + @ai-sdk/openai).

Production build verified: compile:app succeeds, Electron main process
boots with no onnxruntime error.

* docs(desktop): add manual testing plan for PR superset-sh#3517

* fix api key storage slot

* fix(auth): store API keys in dedicated slot so OAuth doesn't clobber them

setApiKeyForProvider and setStoredAnthropicApiKeyFromEnvVariables now
use authStorage.setStoredApiKey() (writes to "apikey:<provider>")
instead of authStorage.set() (writes to the main "<provider>" slot
shared with OAuth). This way connecting/disconnecting OAuth doesn't
overwrite or delete a stored API key.

resolveAuthMethodForProvider falls back to hasStoredApiKey() after
checking the main slot, so status correctly reports authenticated
when only an API key is stored.

* fix(auth): backup/restore API keys across OAuth connect/disconnect

mastracode's resolveModel only reads API keys from the main
authStorage slot (authStorage.get("anthropic")). OAuth login
overwrites this slot, and disconnect removes it — losing any
previously saved API key.

Fix: backup the API key to the dedicated apikey: slot before OAuth
connect, restore it after disconnect. setApiKeyForProvider now writes
to both slots (main for resolveModel compatibility, apikey: for
backup). resolveAuthMethodForProvider checks both.

Applies to both Anthropic and OpenAI providers.

* chore: add upstream PR reference to auth workaround

Point to mastra-ai/mastra#15483 so the backup/restore code can be
removed once upstream lands and we bump mastracode.

* refactor(desktop): derive settings provider action from status

Replace the cascade of if/else + canDisconnect flag with a single
getProviderAction(status) → connect | reconnect | logout | null.
Fixes "Active" badge + "Connect" button showing simultaneously
when authenticated via API key.

* fix(desktop): always show Logout when provider is active

Active providers now always show a Logout button. Clears OAuth or
API key depending on authMethod — no more "Active" badge with no
way to disconnect.

* fix(desktop): simplify OpenAI OAuth dialog + auto-open browser

Match Anthropic dialog's layout: remove the raw OAuth URL display
and "Tip" block, auto-open the browser on OAuth start. Change
"Back" to "Cancel" for consistency.

* refactor(desktop): unify OAuth dialogs into shared OAuthDialog

Extract shared OAuthDialog component with provider config object.
AnthropicOAuthDialog and OpenAIOAuthDialog become thin wrappers
that pass provider-specific labels and options.

* fix(desktop): show 'Copied!' feedback on Copy URL button

* refactor(desktop): merge provider account + API key into single card

Each provider section now renders AccountCard + ConfigRow inside
one rounded card with a divider, instead of two separate cards.
Removes the standalone "API Keys" collapsible section.

* refactor(desktop): compact OAuth row in provider settings card

OAuth row is now a single inline row (label + status + action)
instead of a stacked AccountCard. Both providers share the same
2-row card layout: OAuth row + API key row with divider.

* fix(desktop): contextual buttons in provider settings

Connect is now primary (filled). Save only shows when there's input.
Clear only shows when a key is saved. Removes visual noise from
empty-state provider cards.

* ui(desktop): add provider icons to settings section headers

* ui(desktop): show 'Not connected' badge instead of subtitle for disconnected providers

* ui: remove redundant disconnected subtitle

* ui: remove subtitle text from OAuth rows

* chore: remove dead AccountCard + getProviderSubtitle

* docs: update test plan to match current UI

* chore: move shipped plans to done/

---------

Co-authored-by: AviPeltz <aj.peltz@gmail.com>
…rset-sh#3542)

* fix(desktop): wire drag-and-drop for files in v2 terminal panes

Drops from Finder or the file tree now focus the terminal, shell-escape
the path, and paste it at the cursor. A dashed overlay indicates an
active drag.

* fix(desktop): handle multi-file terminal drops, align priority with v1

Check dataTransfer.files before text/plain (native Finder drags win over
internal drags) and escape every dropped path, not just the first.
…spaces (superset-sh#3544)

Ungrouped workspaces that render after a section header are already
visually grouped with that section (shared accent color, collapse
together) and get committed into it on next drag. The count, however,
was reading section.workspaces which only included workspaces whose DB
sectionId already matched — so a freshly created section showed "(0)"
while the user sees N workspaces beneath it.

Reparent those workspaces into section.workspaces in the data layer so
the count matches the visual grouping and DnD commit behavior.
…rset-sh#3545)

When panes mount during new-workspace / preset flows, attachToContainer's
fit() can run before flex layout resolves, leaving the backend PTY spawned
at stale defaults (shell prompt wraps wrong until a manual resize).

Mirror v2's sendResize-on-open pattern: once createOrAttach succeeds,
re-fit against the now-settled container and push the real dims.
superset-sh#3548)

v1's createWorktree appended ^{commit} to the start point to prevent
implicit upstream tracking. This fails with "fatal: invalid reference"
when the ref isn't locally resolvable with that suffix (e.g. stale or
missing remote-tracking ref, branches with slashes like
feat/workstreams-view).

Replace ^{commit} with --no-track, which has the same effect without
fragile ref suffix manipulation. Matches v2's host-service approach.

Closes superset-sh#3448
…erset-sh#3549)

* fix(desktop): guard installUpdate against repeat clicks

MacUpdater.quitAndInstall() registers a fresh native-updater
`update-downloaded` listener each call when Squirrel.Mac hasn't finished
staging. Repeat clicks on the update button stacked listeners, then fanned
out into parallel nativeUpdater.quitAndInstall() calls once Squirrel
fired — racing to swap the binary and leaving the app on the old version.
Matches the reporter's symptom (app quits, reopens on same version).

Add an `isInstalling` guard + `status === READY` precondition so repeat
clicks collapse to a single quitAndInstall, and clear the flag in the
error handler so the user can retry if Squirrel surfaces an error instead
of actually quitting.

Closes superset-sh#3507

* test(desktop): make auto-updater tests portable + non-destructive reset

Greptile flagged that setupAutoUpdater() short-circuits on non-mac/linux
hosts, so the suite would silently fail on a Windows CI runner (handlers
never register, guard never resets). Mock shared/constants to pin the
platform.

CodeRabbit flagged that the beforeEach reset emitted a non-network error,
triggering the real ERROR path (which also clears the cached update).
Use a network-shaped error so the handler maps back to IDLE without the
extra side effect.
…licks (superset-sh#3552)

* fix(desktop): refresh v2 terminal link tooltip editor label + nudge plain clicks

- LinkHoverTooltip fetched the default editor in a mount-only useEffect, so
  changing the default editor in settings left the modifier-shift label
  ("Open in Cursor", etc.) stale until the terminal pane unmounted. Refetch on
  every hover-start instead.
- Plain (no-modifier) clicks on a detected file path in the v2 terminal were
  silent, which made the modifier-key affordance undiscoverable. On a plain
  file-link click, show a transient tooltip at the click position
  ("Hold ⌘ to open · ⌘⇧ for external", or Ctrl/Ctrl+Shift off-mac). Capped at
  two shows per renderer session via a module-level counter, and suppressed
  while the modifier-hover tooltip is already visible. Uses framer-motion's
  AnimatePresence for fade in/out.

* refactor(desktop): simpler v2 terminal link tooltip labels

- "Open in editor" → "Open in pane" for the ⌘-click file case (native in-app
  file pane is what actually happens).
- Shift variant always says "Open in external editor" instead of interpolating
  the configured editor name. openFileInEditor uses the global settings
  defaultEditor (non-editor apps like Finder can't be set there), so the
  interpolated name could disagree with a user's per-project preference — the
  generic label never lies.
- Drops the getDefaultEditor fetch, defaultEditor state, and getAppOption/
  getAppLabel plumbing that went with it.

* refactor(desktop): URL ⌘-click tooltip says "Open in pane" for consistency
…rset-sh#3551)

requestLocalNetworkAccess was defined in local-network-permission.ts but
never called, so the Info.plist keys (NSLocalNetworkUsageDescription,
NSBonjourServices) wired up in electron-builder never had a trigger to
prompt the user. On macOS 15+ this causes outbound connections to
local-network IPs from the app and its spawned child processes (node,
python in the terminal) to be silently blocked, while system binaries
like curl escape the same TCC attribution.

Call it alongside requestAppleEventsAccess in app ready.

Refs superset-sh#3474
…h#3553)

Adds a Tasks nav entry (collapsed + expanded) alongside Workspaces,
mirroring v1: paywall gate, last-used filter restoration, and
active-route highlight.
…set-sh#3478) (superset-sh#3550)

The v1 terminal host buffered all stdin writes — user keystrokes
included — until the shell emitted OSC 133;A. When a user's `.zshrc`
hook (e.g. fnm's `use-on-cd`) opened an interactive prompt during
init, the marker never fired and typed y/N answers sat in the queue
for the full 15s timeout, making the workspace look frozen.

Pass writes straight through instead, keeping only the escape-sequence
drop for stale DA/DSR replies from the renderer's xterm. Mirrors the
v2 host-service behavior, which has always written user input directly.
…t-sh#3372) (superset-sh#3547)

* fix(desktop): stop excessive lsof spawning from port scanner (superset-sh#3372)

PortManager was spawning `lsof` every 2.5s via a module-level `setInterval`
with no shutdown path, stacking hint-triggered scans on top, and wrapping
each call in `sh -c` so timeouts left orphaned children that outlived the
app.

Three fixes:

1. Lifecycle: the interval now starts on first `registerSession` /
   `upsertDaemonSession` and stops on the last unregister. No sessions,
   no scans.

2. Concurrency: hint-triggered scans coalesce via a single debounced
   timer plus a `scanRequested` follow-up flag, so at most one scan is
   ever in flight.

3. No orphans: swapped `exec("sh -c 'lsof ...'")` for `execFile` and
   threaded an `AbortSignal` through so `stopPeriodicScan` can cancel
   any in-flight child instead of leaking it to `launchd`/`init`.

Also narrowed `containsPortHint` by dropping two over-broad patterns
(`/port\s+(\d+)/i`, `/:(\d{4,5})\s*$/`) that matched routine log noise
and forced spurious scans.

Includes 13 regression tests in `port-manager.test.ts`, 8 of which fail
on `main` — they directly measure the three classes of bug.

* update doc

* fix(desktop): forward AbortSignal to Windows process-name lookups; add ready-on regex test

Addresses PR superset-sh#3547 review feedback:

- Forward `signal` from `getListeningPortsWindows` through to the `wmic`
  and `powershell` child lookups in `getProcessNameWindows`. Previously,
  on Windows with many unique PIDs, the per-PID process-name lookups
  were not covered by the abort and could run up to 5s after teardown.

- Add a positive-case regression test for the `/ready on .../` hint
  regex (Vite-style dev server output) — the third retained pattern
  previously had no positive test.

Skipping the proposed "snapshot signal at top of scanAllSessions" race
fix: without the shell wrapper, OS process-group cleanup on Electron
exit already handles child termination, so the narrow race between
`stopPeriodicScan` and a read of `this.scanAbort?.signal` mid-scan can
only waste one in-flight lsof call (~100ms) and cannot produce an orphan.
Not worth the extra indirection.

* fix(desktop): tighten runTolerant abort handling + exact coalesce assertion

Addresses PR superset-sh#3547 review feedback:

- runTolerant: rethrow on AbortError/ABORT_ERR/killed/signal so partial
  stdout from a mid-execution kill is never parsed as success. Only
  plain non-zero exits (e.g. lsof exit 1 when no PIDs match) are
  tolerated. The outer catch in getListeningPortsLsof turns any
  rethrown abort/timeout into an empty result — same upstream behavior
  as before, but explicit about intent.

- port-manager.test.ts: tighten the coalescing assertion from
  toBeLessThanOrEqual(2) to toBe(2). The regression guarantee is
  "exactly one initial scan + one coalesced follow-up" — the loose
  version also passed if follow-ups were silently dropped.
…perset-sh#3561)

Disabled state alone didn't feel responsive — click registered without
obvious acknowledgement. Adds a spinning icon alongside the existing
"Installing..." label so users see the action was received.
… chat input (superset-sh#3520)

* fix(desktop): prevent default on hotkeys to stop character leak into inputs

* fix(desktop): make hotkey preventDefault opt-out-able
Rename TOGGLE_EXPAND_SIDEBAR to OPEN_DIFF_VIEWER (same binding).
In v2, focus any existing diff pane or open one in a new tab, and
flip the workspace sidebar to the Changes tab. V1 keeps its existing
expand-sidebar behavior under the new ID.
…t-sh#3513) (superset-sh#3554)

* fix(desktop): recover terminal from non-monospace font crash (superset-sh#3513)

Setting the terminal font to a proportional family like "Inter" blanked
the app on next launch — the bad value persisted in SQLite and xterm
couldn't lay out cells on reload, leaving no way back into settings.

- Sanitize the stored family on read: if the primary family isn't
  monospace (per canvas measurement), fall back to the default terminal
  font so a poisoned DB value can never blank the renderer.
- Hide the "Other" group and custom free-form entry in the terminal
  font picker so new selections are restricted to monospace candidates.

* fix(desktop): reject all-proportional generic terminal font stacks

Follow-up on superset-sh#3554 review. sanitizeTerminalFontFamily previously passed
any all-generic CSS value through untouched (e.g. "cursive", "sans-serif",
"monospace, sans-serif") because parsePrimaryFontFamily returns null when
no concrete family is present — same blank-window crash class as the
"Inter" report.

Refactor the sanitizer to inspect the full family list: when no concrete
primary exists, only trust the value if every entry is a monospace
generic; otherwise fall back to the default. Add regression tests.

* refactor(desktop): append ", monospace" fallback + cleaner font preview

- Always append "monospace" to the sanitized terminal font stack when it
  doesn't already end with one. Mirrors VS Code's behavior in
  src/vs/workbench/contrib/terminal/browser/terminalConfigurationService.ts
  so that if the configured primary isn't installed on this machine, the
  browser falls back to the OS monospace generic instead of a proportional
  default.
- Swap the terminal font preview from a box-drawing layout (which rendered
  as broken in proportional fonts and used tofu glyphs) to a shell session
  that demonstrates column alignment naturally.
- Drop a couple of narrating comments flagged in simplify review.

* refactor(desktop): show Nerd Fonts in the editor picker too

Nerd Fonts are monospace — the terminal-only gate was pre-existing
special-casing and reviewers pointed out it hides a widely-used class
of fonts from users picking an editor font. Drop the gate.

* fix(desktop): validate the actual CSS primary font, not the first concrete entry

Addresses the coderabbit review on b2e6a04. The sanitizer previously
skipped leading generics when picking the primary to measure, so a value
like `sans-serif, "JetBrains Mono"` passed validation because the later
concrete entry was monospace — but CSS resolves the first generic
(sans-serif) and the terminal still renders proportional.

Switch to validating families[0] (the actual CSS primary): if it's a
monospace generic, trust the stack; if it's a proportional generic, fall
back; if it's concrete, canvas-measure it. Add regression tests.
…ts (superset-sh#3562)

Memory leak and CPU spiral root-caused to `staleTime: 0, gcTime: 0` +
60fps polling: React Query can't dedupe or GC anything, and the render
path churns allocations every 16ms.

Restoring the React Query defaults (5min gcTime) fixes the leak. Server
poll rate is independent of perceived stream smoothness — StreamingMessageText
already reveals text client-side at 60fps from whatever buffer the server
delivers. 4fps polling keeps that buffer fed with plenty of headroom.

Also removes the `isRunning` invalidation effect — redundant when the
query is polling.

Builds on and supersedes superset-sh#3170 by @thepathmakerz, which diagnosed the
same root cause. This version takes the subtractive path (-21 lines)
instead of adaptive polling (+36).

Closes superset-sh#3049
…superset-sh#3563)

* Remove video section

* Rband

* More brand

* Move pills

* CTA

* Color

* Grid line

* polish marketing hero, CTA, and testimonials

- Hero title: both segments at weight 500; "AI Agents." uses lo-res-21-ot-serif with Pixelify Sans fallback.
- Subtitle: "Orchestrate 100+ coding agents in parallel" (keep rest).
- Remove vertical grid lines from <main>.
- Move ProductDemo pills back under the mockup (no scroll transform).
- CTA heading matches FAQ styling; copy now "Try Superset now.".
- Add `role` field to Iven's testimonial (Engineer at Paraflow).
- TypewriterText: optional per-segment `render` for custom glyph rendering.
- Simplify DownloadButton classes (unify with buttonClasses).

* revert hero 'AI Agents.' styling to main version

* swap feature-demo background to paper-design Dithering shader

Replaces the canvas-based Bayer dither with @paper-design/shaders-react's
<Dithering> shader (shape="warp", type="4x4"), lazy-loaded via React.lazy.
Rendered at opacity 30% with mix-blend-screen over each card's palette.

* slow feature-demo dither shader from 0.3 to 0.15

* subtle hover on trusted-by logo tiles (brighter border + bg)

* soften Download button: ghost-style over solid foreground

* recolor Download button ghost style to brand orange

* match header CTA to brand ghost style

* push CTA button text to a more saturated orange

* restore cursor:pointer on <button> (Tailwind v4 preflight default is default)

* unify header CTA with DownloadButton

* update Chris Laupama role to TS Lead at Webflow

* match TrustedBy heading style to WallOfLove

* shrink TrustedBy heading one step

* use original compact size on TrustedBy heading, keep semibold

* update Elias role to Founder at Cleanroom

* update Chase role to Founding Engineer at Decoda Health

* update Felipe role to Codex at OpenAI

* drop unused Adobe Fonts kit scaffolding and lo-res-22 font-family

* restore mobile horizontal scroll on ProductDemo pills
* feat(chat): render subagent activity inline as collapsible tool wrapper

Remove SubagentExecutionMessage from the bottom-pinned section so subagent
tool calls render inline within AssistantMessage via the existing
SubagentToolCall collapsible component, consistent with all other tool calls.

* fix(chat): add gradient fade-out and bottom padding to input footer

Removes top padding from ChatInputDropZone so content is flush with the
bottom of the scroll area, and adds a CSS pseudo-element gradient that
fades the conversation content into the background above the input bar.

* feat(ui): redesign ToolInput/ToolOutput/ToolHeader — compact, monospaced

- ToolInput/ToolOutput: replace CodeBlock with plain <pre>, remove p-4
  padding, switch to bg-muted/30 backgrounds, rename labels to
  "Input"/"Output", apply font-mono throughout
- ToolHeader: reduce px-2.5 → px-1, add rounded-b-md, swap chevron
  direction on hover (right=collapsed, down=expanded), add open prop
- Tool: add font-mono to outer Collapsible wrapper

* fix(ui): stop scroll anchor when clicking tool call triggers

Expanding a tool call was causing the scroll container to jump. Detect
clicks on [data-tool-trigger] elements and call stopScroll() so the
stick-to-bottom behaviour doesn't fight the user interaction.

* feat(ui): add ToolCallRow and refactor tool components

Introduces a shared ToolCallRow component that encapsulates the common
collapsible row pattern: icon/chevron, ShimmerLabel title, muted
description, status slot, left-border content area, and an optional
headerExtra element for out-of-trigger action buttons.

Refactors BashTool, WebSearchTool, WebFetchTool, and FileDiffTool to use
ToolCallRow, removing duplicated hover/chevron/collapsible logic from each.
All tools now have consistent: font-mono, px-1 header padding, rounded-b-md
on hover, ml-2.5 border-l content alignment, and chevron-right/down hover
behaviour.

* feat(desktop): refactor tool call components to use shared ToolCallRow

Migrates GenericToolCall, SupersetToolCall, SubagentToolCall, and
ReadOnlyToolCall to use the new ToolCallRow component, eliminating
per-component chevron/hover/collapsible boilerplate.

Also updates ToolCallBlock dispatch: web_search tool variants without
parsed results now fall through to GenericToolCall with a globe icon
instead of WebSearchTool (which requires results to be expandable).
Renames "Subagent" label to "Agent" with the agent type as a muted
description.

* feat(chat): replace inline question UI with footer overlay

- Add QuestionInputOverlay component: numbered options, "Something else"
  free-text row with pencil icon, Skip button, cross-fade to submit on type
- Refactor AskUserQuestionToolCall to plain ToolCallRow (no inline answer UI)
- Wire pendingQuestion/handleQuestionResponse/stopActiveResponse to footer
  in both ChatPane variants instead of ChatMessageList
- Remove PendingQuestionMessage from ChatMessageList and clean up its props

* feat(ui): replace tool call header indicators with braille spinner and left-side icons

- Add BrailleSpinner component (braille chars, amber, matches sidenav style)
- Show spinner in icon slot while pending, red X on error, icon on complete
- Remove right-side status slot (no spinner, checkmark, or X on the right)
- Remove title shimmer animation

* feat(chat): hide Question tool row while active, show with description after answered/interrupted

- Thread isStreaming through ToolCallBlock to AskUserQuestionToolCall
- Return null only when isPending && still streaming (overlay is active)
- Show collapsed row with question text as description once answered or interrupted

* feat(chat): improve tool call UX and styling

- Align tool call icon with chat text using -mx-1 negative margin
- Remove overflow-hidden from MessageContent to avoid clipping
- Hide description in tool call header when expanded
- Show query field as plain text with Query/Response labels instead of raw JSON
- Add subtle focus-visible ring to collapsible trigger button
- Adjust content padding (pl-3 py-1) to align with heading text
- Use "Type your answer..." placeholder when question has no options

* fix(chat): keep answered question messages visible during active turn

Extract hasAnsweredQuestionToolCall to a shared utility and use it in
withoutActiveTurnAssistantHistory / getVisibleMessages so that assistant
messages containing an answered ask_user_question are kept visible in the
message list while a session is still running. Previously all assistant
messages from the active turn were hidden, causing questions and their
answers to disappear from chat history until the turn completed.

* fix(chat): refactor question tool call — answer bubble, skip badge, plain-string fallback

Replace the inline Q&A markdown block with a right-aligned answer bubble
(styled like a user message) shown after the question is answered, and a
"Question skipped" badge when the result carries no answers. Add a fallback
for backends that return a plain string result (result.text / result.answer)
rather than a structured answers map. Remove the isPending spinner from the
ToolCallRow since the overlay in the footer already indicates active state.

* fix(chat): overlay freezes in place on submit, skip sends answer, scroll on question changes

- Fire onRespond without awaiting so the overlay never shows a separate
  "Waiting for response..." state. The overlay stays frozen (same size and
  content) until pendingQuestion updates from the server, at which point
  React remounts the component via key={pendingQuestion.questionId}.
- Highlight the chosen option and replace its number badge with a spinner;
  show a spinner on the pencil icon when the answer came from the text input.
- Skip now sends "skip" as the answer instead of aborting the agent, so the
  LLM can continue. The X button still stops the session.
- Fix isQuestionSubmitting hardcoded to false — now passes questionResponsePending.
- Add footerScrollTrigger so the chat scrolls to the bottom whenever the
  question overlay appears, updates, or disappears.

* feat(chat): question overlay max-height with scrollable options and pinned header/footer

* fix(chat): stabilize useFocusPromptOnPane effect dependency

* feat(chat): show question tool call with status and collapsible answer

Rewrites AskUserQuestionToolCall to use the shared ToolCallRow component.

- Supports both ask_user (singular question/options) and ask_user_question
  (array of questions) tool schemas
- Shows AWAITING RESPONSE / ANSWERED / CANCELLED inline status description
- Expands to reveal the question text + submitted answer when answered
- Extracts answer from result.content "User answered: <x>" format
- ToolCallRow now uses cursor-text when the row has no expandable content

* fix(chat): keep question tool calls visible during active assistant turn

getVisibleMessages() filtered out all assistant messages while a turn was
in progress. Added hasPendingQuestionToolCall() to also pass through any
assistant message that contains an unanswered question tool call, so the
"AWAITING RESPONSE" tool call row remains visible in the message list.

* feat(chat): optimistically hide question overlay on answer submit

Tracks the most recently answered question ID in ChatPaneInterface and
passes it to ChatUploadFooter so the overlay disappears immediately on
submit without waiting for the server round-trip. Also threads
pendingQuestion and answeredQuestionId down to ChatMessageList to suppress
the ThinkingMessage spinner while a question is awaiting a response.

* feat(chat): scroll to bottom on message send, question arrival, and answer

Adds a ScrollAnchor component inside the Conversation (StickToBottom)
context that handles three cases:

- isAwaitingAssistant becomes true: re-pins scroll so Thinking and the
  streaming response are always visible after sending any message
- pendingQuestion.questionId changes: scrolls to bottom when a new
  question arrives so the overlay doesn't cover streaming content
- answeredQuestionId changes (10ms delay): the overlay hide causes the
  footer to shrink and the scroll container to grow; the library
  interprets the resulting scrollTop clamp as "user scrolled up" via a
  1ms setTimeout, so we run after it with a 10ms delay to restore the pin

* feat(chat): show cancelled status when question is aborted

- Question tool call now shows CANCELLED status in the header instead of
  nothing when aborted (output-error state or Mastracode isError: true)
- Error result content is no longer mistaken for an answer, fixing the
  ANSWERED status shown on revisit after an abort
- Expanded view shows the question text and an "Aborted by the user"
  label with a red CircleX icon
- INTERRUPTED / Response stopped footer is suppressed when the
  interruption was caused by an aborted question

* fix(chat): keep bottom-pinned scroll when expanding a tool call

When the chat is pinned to the bottom and the user opens a collapsible
tool call, skip stopScroll() so stick-to-bottom's resize handler
auto-scrolls to reveal the expanded content instead of hiding it behind
the prompt input.

* fix(chat): exclude scrollbar column from input footer gradient

* fix(chat): stop scroll jump when expanding any tool call

Remove the overly-broad "scroll to bottom if last trigger" heuristic that
used a DOM query to find the last [data-tool-trigger] in the container.
This was wrong — it would fire even when there were messages below the
tool call being expanded.

Now ConversationContent simply unpins from bottom on any tool trigger
click, preventing the resize handler from jumping the scroll position.
Nothing more.

* feat(chat): always use ask_user tool for questions in Superset

Two-pronged approach to ensure the LLM never asks questions as plain text
(which bypasses the question overlay) and always uses the ask_user tool:

1. AGENTS.md — adds a project-level override rule that loads into the
   mastracode system prompt for any session in the Superset workspace.

2. host-service — writes a managed ~/.mastracode/AGENTS.md with the same
   rule, applied globally to every workspace opened in the desktop app.
   Uses a managed-by marker to avoid overwriting user-authored files.

Root cause: the default mastracode tool guidance says "Don't use this for
simple yes/no — just ask in your text response." These rules override that
by being appended to the system prompt after the base instructions.

* feat(chat): pending question drives workspace nav status and native notification

- When ask_user tool fires, emit PendingQuestion lifecycle event from the
  harness subscriber (same pipeline as PermissionRequest/Start/Stop)
- useAgentHookListener maps PendingQuestion → "permission" pane status,
  showing the orange dot in the workspace nav immediately, even when the
  tab is not focused
- NotificationManager plays sound and shows "Awaiting Response" native
  toast for PendingQuestion events with visibility suppression
- Cancel tooltip added to QuestionInputOverlay X button
- Cancelled question tool calls now show question text + "Aborted by the
  user" immediately on stop, without requiring a page reload
- isInterrupted prop threaded through MessagePartsRenderer → ToolCallBlock
  → AskUserQuestionToolCall so pending questions show CANCELLED status
  when the run is interrupted
- INTERRUPTED badge is always shown alongside cancelled question state

* fix(chat): clear orange dot on answer submit, focus prompt on dismiss, no green dot when tab is focused

- Clear pane status to idle immediately when user submits a question answer
- Focus prompt textarea when question overlay dismisses (rAF to let overlay unmount and browser focus settle)
- Fix Stop event idle/review determination: read URL from hash (not pathname, which is always the file path in hash-routed Electron app), and add focusedPaneIds as a reliable fallback so panes the user is actively interacting with don't receive a spurious green dot
- Remove MarkdownToggleContent in favour of always-on MessageResponse for subagent output

* feat(chat): render subagent task prompt with markdown via MessageResponse

Render the subagent task text through MessageResponse so markdown
formatting (lists, bold, code spans) is applied consistently with
the response text below it.

* fix(chat): scale down headings and fix list layout in subagent output

Headings were rendering at full browser size (text-2xl/3xl) inside the
compact xs subagent block. Override h1-h6 to text-sm/xs with tighter
margins, and remove the top margin on first-child paragraphs inside list
items to fix ordered list numbers appearing on a separate line.

* feat(chat): render read file tool output with syntax-highlighted code viewer

- ReadOnlyToolCall fetches file content directly via tRPC filesystem.readFile
  (same path as the file pane) instead of parsing MCP tool output, eliminating
  metadata artifacts like line-number prefixes and byte-count headers
- Uses shared detectLanguage() for syntax highlighting language detection
- Renders with CodeBlock (Shiki) with a filename + line-range header styled
  like table headers (bg-muted/50), showLineNumbers, and colorize=false for
  plain white text
- Adds colorize prop to CodeBlock to suppress syntax colors while keeping
  line numbers at reduced opacity
- Passes workspaceId/workspaceCwd from ToolCallBlock to ReadOnlyToolCall
- Fixes withoutActiveTurnAssistantHistory to preserve completed prior-phase
  assistant messages (e.g. read-file before a question answer) by keeping
  messages that have a stopReason and a different id from currentMessage,
  preventing tool calls from disappearing after answering a question

* feat(chat): improve tool call error and task_write UX

- ToolCallRow: replace XIcon with "ERROR" label + XCircleIcon in description slot on error
- SupersetToolCall: add subtitle prop, render output content via MessageResponse instead of raw JSON
- TaskWriteToolCall: new component for task_write — "Update Tasks" title with ListTodoIcon and semantic description (task count + status breakdown)

* fix(chat): add vertical padding to read file tool content area

* feat(chat): add LspInspectToolCall with ActivityIcon and file subtitle

* fix(chat): handle mastra_workspace_lsp_inspect tool name alias

* fix(chat): use FileSearchIcon for LSP Inspect tool call

* feat(chat): show input/output content in LSP Inspect tool call

* fix(chat): use SearchCheckIcon for LSP Inspect tool call

* fix(chat): extract TOOL_CALL_MD_CLASSNAME for global compact markdown in tool calls

Adds inline code (text-xs) fix alongside existing heading overrides.
SubagentToolCall and SupersetToolCall now share the same constant so
future patches only need to happen in one place.

* feat(chat): improve subagent tool call display and share read-file component

- Filter empty messages (step-start/source-only) from chat history
- Add expandable content with subtitles to subagent inner tool calls (Read, List Files, Search, Write, Edit, Web)
- Extract shared ReadFileTool component to packages/ui for reuse across main and subagent tool calls
- Subagent read tool now shows styled CodeBlock with syntax highlighting, matching main agent display
- Thread workspaceId/workspaceCwd/onOpenFileInPane through SubagentToolCall → SubagentInnerToolCall so subagent read calls show the open-in-pane button

* fix(chat): forward workspace props to ReadOnlyToolCall in MessagePartsRenderer

workspaceId and workspaceCwd were available in MessagePartsRenderer but not
forwarded to ReadOnlyToolCall, silently disabling the disk-read feature for
read_file tool calls rendered via that path.

* fix(chat): remove dead code from MessageList

- Remove interruptedByAbortedQuestion which was computed but never
  referenced in JSX or logic
- Move hasRenderableParts to after imports (was inserted between them)
- Remove unused getToolName, normalizeToolName, ToolPart imports
- Add comment explaining the runtime-only "error" part type cast

* fix(chat): fix type safety, path normalization, and memoize in SubagentInnerToolCall

- Replace as never with as BundledLanguage for language prop type safety
- Replace naive path concatenation with normalizeWorkspaceFilePath to handle
  ./, ../, file:// and workspace boundary validation (matches ReadOnlyToolCall)
- Rename shadowed normalized variable to resolvedPath in openInPane closure
- Memoize parseReadFileResult call to avoid re-parsing on every render

* fix(chat): add stale time and loading state to ReadOnlyToolCall file query

- Add staleTime: Infinity so completed read-file tool calls don't refetch
  on remount (prevents IPC burst when scrolling long conversations)
- Show a spinner row while the disk read is in flight instead of flashing
  the raw ToolInput/ToolOutput view

* feat(chat): replace text input with Tiptap editor for slash commands and file mentions

- Add TiptapPromptEditor with ProseMirror-based rich text input
- Slash command chips (/command) as inline atom nodes, insertable anywhere in message
- File mention chips (@path) anchored to cursor position via virtual float
- SlashCommandMenu width matches prompt input via --radix-popover-trigger-width
- Selecting a command inserts a chip node instead of immediately submitting
- Popover closes on editor blur, reopens on focus
- Tab no longer auto-selects commands (only Enter selects)
- serializeEditorToText serializes chip nodes to /name and @path for submission

* feat(chat): add skill preload — /command chips trigger skill tool calls before LLM

- Add SkillToolCall component (ZapIcon, Skill(name) title, success/error state)
- Register SkillToolCall in ToolCallBlock for tool names 'skill' and 'load_skill'
- In ChatPaneInterface.handleSend: extract custom command chip names from content,
  strip leading / from message text, pass names as metadata.skills to sendMessage
- Add skills?: string[] to sendMessageInput metadata schema (zod.ts)
- Pass preloadSkills to harness.sendMessage in service.ts
- Add ChatSendMessageInput.metadata.skills type field
- Add docs/skill-preload-feature.md with implementation state and setup instructions

Requires superset-sh/mastra#9 for harness.sendMessage preloadSkills support
and .claude/commands/ being included in skillPaths.

* feat(ui): update shared AI element components for chat UX

- braille-spinner: improve animation timing
- code-block: add copy button and syntax highlight tweaks
- message: simplify prose class handling
- prompt-input: add focusShortcutText prop support
- tool-call-row: tighten collapsible layout and spacing
- input-group: support rounded-full variant
- globals.css: add chat-specific scrollbar and prose overrides

* fix(chat): improve tool call display components

- AskUserQuestionToolCall: redesign option layout with better button styling
- SupersetToolCall: render markdown content in tool output
- SubagentToolCall/SubagentInnerToolCall: tighten display, fix edge cases
- TaskWriteToolCall: simplify status rendering
- ReadOnlyToolCall: add workspace prop forwarding and memoize file query
- QuestionInputOverlay: improve option button layout
- MessageList: remove unused prop

* fix(chat): update message list and subagent execution display

- ChatMessageList: update subagent message grouping and rendering
- SubagentExecutionMessage: improve tool call display during subagent runs
- messageListHelpers: refine pending/streaming message detection
- use-chat-display: minor hook cleanup
- screens/main ChatPaneInterface: propagate workspace props

* fix(chat): suppress empty assistant message wrappers

When an assistant message has no renderable content (e.g. redacted_thinking
or unrecognized AI SDK step markers), return null instead of rendering
empty Message/MessageContent divs that cause blank gaps between messages.

* feat(chat): show styled "Not Configured" state for LSP inspect when LSP is absent

Detect the "LSP is not configured for this workspace" error and surface it
as a red "Not Configured" badge in the tool call row rather than triggering
the generic error styling.

* feat(chat): universal "not configured" warning on tool call rows

Detect "not configured" errors in getGenericToolCallState and surface
them as a filled amber warning triangle with a "Not configured" tooltip
in the ToolCallRow status area. File name description is preserved.

GenericToolCall passes isNotConfigured through so the treatment applies
to all tool calls, not just LSP Inspect.

* fix(chat): move not-configured warning icon inline after description

Show the outlined amber TriangleAlertIcon next to the file name in the
description area instead of in the right-side status slot.

* feat(chat): clickable file names on file-related tool call rows

Replace the standalone open-in-pane icon button with a hover-underline
treatment directly on the filename. Applies to Read, Check file, Write,
Edit, Delete, and Smart Edit tool call rows.

Extracts a shared ClickableFilePath component (span[role=button]) that
nests safely inside CollapsibleTrigger without invalid nested-button HTML.

* feat(ui): add ShowCode component with expand/collapse, copy, and startLine support

Adds a new ShowCode component that unifies all code display surfaces — tool
call file views and markdown code fences — into a single block with:

- Filename/language header with optional clickable file path and line range
- Expand/collapse toggle (appears when content exceeds ~15 lines)
- Copy and open-in-pane action buttons in the header
- startLine offset for partial-file display (line numbers count from the
  correct offset rather than always starting at 1)
- Language fallback in highlightCode: unknown Shiki languages silently
  retry as "text" instead of throwing

* refactor: replace legacy syntax highlighters with ShowCode

- ReadFileTool: swap inline CodeBlock + duplicated header/button JSX for
  a single <ShowCode> — removes the fragile [&>div>div]:max-h-[300px]
  deep selector and the duplicated open-in-pane button pattern flagged in
  code review
- MarkdownRenderer/CodeBlock (desktop): replace react-syntax-highlighter
  (Prism) with ShowCode, aligning markdown code fences with the shared
  Shiki-based highlighter used throughout the chat UI

* fix(chat): address PR review feedback

- Move useMemo hooks before early returns in AskUserQuestionToolCall and
  SubagentInnerToolCall to fix React Rules of Hooks violations
- Use hasFileContent (content !== undefined) guard in ReadOnlyToolCall
  so empty files render in the code viewer instead of falling back
- Tighten @mention regex to require word-boundary before @ so email
  addresses and decorators are not rewritten as file mentions on round-trip
- Add trigger to ScrollAnchor useEffect deps so footerScrollTrigger bumps
  actually retrigger the scroll effect
- Add pendingQuestion to bumpFooterScroll useEffect deps in ChatPaneInterface
  so the chat scrolls when the question overlay appears or disappears
- Roll back optimistic answeredQuestionId and pane status if
  respondToQuestion RPC fails in legacy ChatPaneInterface
- Guard Shiki highlightCode fallback so it does not recurse infinitely
  when language === "text" already
- Add aria-label to icon-only buttons (expand/collapse, open, copy) in ShowCode
- Remove dead _interruptedByAbortedQuestion useMemo from legacy ChatMessageList

* fix(chat): address second round of PR review feedback

- Add hasPendingQuestionToolCall to workspace path messageListHelpers so
  assistant messages with active ask_user calls stay visible during a run
- Reset QuestionInputOverlay state (customText, submittedLabel) when the
  question prop changes identity, not just on mount
- Fix Enter/Tab in file-mention mode only consuming the event when a file
  is actually selected; falls through to normal submit when results are empty
- Keep isError indicator visible in ToolCallRow status slot when the row is
  expanded (previously the error icon disappeared on open)

* fix(chat): address third round of PR review feedback

- Fix colorize=false fading first code token when line numbers are
  disabled: add a shiki-line-number class to gutter spans and target
  that class instead of span:first-child in the CSS selector
- Add e.preventDefault() to Space key handler in ClickableFilePath
  so activating via keyboard does not also scroll the container
- Fix file mention round-trip for paths containing spaces: serializer
  now emits @"path with spaces" and parser handles both quoted and
  unquoted forms
- Add null guard in FileMentionNode so malformed/pasted content with
  a missing path attr does not crash rendering

* fix(chat): address fourth round of PR review feedback

- Fix ReadOnlyToolCall lineRange: disk read always returns the whole
  file so always display 1–N (trimming trailing newline before counting)
- Fix Shiki fallback to render escaped plain text instead of empty
  strings when codeToHtml fails for the "text" language itself
- Trim trailing newline before computing lineCount in ShowCode so files
  ending with \n do not trigger isOverflowing one line early

* chore: formatting and cleanup

* fix(chat): close mention popup before falling through Enter when results empty

* fix(chat): address fifth round of PR review feedback

- Re-add inputValue to SlashCommandPreviewPopover anchor effect deps so
  the virtual anchor re-measures when typing shifts the chip's position
- Clamp slash menu selectedIndex in onUpdate when filtered results shrink,
  matching the existing mention-menu clamping behavior
- Return true (consume event) when Enter/Tab closes empty mention popup so
  the event does not propagate to insert a paragraph break
- Mirror focus-on-dismiss effect in v2 workspace ChatInputFooter so the
  editor regains focus after the question overlay unmounts
- Fix trailing-slash paths rendering empty label in ClickableFilePath
  by using || instead of ?? for the basename fallback

* chore: rebuild bun.lock after rebase

* Fix typecheck

* refactor(chat): align skill handling with upstream mastra

Removes the fork-dependent preload wiring (metadata.skills →
preloadSkills pass-through) that was a silent no-op on upstream
mastracode. Keeps the SkillToolCall renderer so load_skill tool
calls emitted by upstream's native skills system render with their
own UI.

Rewrites docs/skill-preload-feature.md to describe the upstream
agent-autonomous model (SKILL.md discovery in .claude/skills,
.agents/skills, .mastracode/skills).

* chore(deps): bump mastra to 0.15.0-alpha.3 / 1.26.0-alpha.3

Brings in upstream mastra's native skills system (search_skills +
load_skill tools, SKILL.md discovery via skillPaths) which the
SkillToolCall renderer in this PR now consumes for free.

- mastracode:     0.14.0   → 0.15.0-alpha.3
- @mastra/core:   1.25.0   → 1.26.0-alpha.3
- @mastra/mcp:    1.3.1    → 1.5.1-alpha.1

Applied in apps/desktop, packages/chat, packages/host-service.

* chore: alphabetize @tiptap/pm in desktop package.json

Auto-applied by biome/sherif.

* fix(chat): cut display polling to 4fps and restore query cache defaults (superset-sh#3562)

Memory leak and CPU spiral root-caused to `staleTime: 0, gcTime: 0` +
60fps polling: React Query can't dedupe or GC anything, and the render
path churns allocations every 16ms.

Restoring the React Query defaults (5min gcTime) fixes the leak. Server
poll rate is independent of perceived stream smoothness — StreamingMessageText
already reveals text client-side at 60fps from whatever buffer the server
delivers. 4fps polling keeps that buffer fed with plenty of headroom.

Also removes the `isRunning` invalidation effect — redundant when the
query is polling.

Builds on and supersedes superset-sh#3170 by @thepathmakerz, which diagnosed the
same root cause. This version takes the subtractive path (-21 lines)
instead of adaptive polling (+36).

Closes superset-sh#3049

* feat(chat): slash command chip UX enhancements

- Argument editing inline in chip: auto-focus on insert, right-arrow to exit, double-click to re-enter
- Commands without argumentHint hide the colon/input entirely
- Model command shows a dropdown of available models (no free-form text)
- Chip input auto-sizes as user types (shrinks to content width)
- Dropdown positioned above chip (side="top"), ArrowUp/Down navigate options, Tab/Enter commit selection
- Menu reopens automatically when deleting value back to empty
- Preview popover and select dropdown are mutually exclusive (preview only on hover/node-select, never while arg input is focused)
- Focus shortcut hint moved inside TiptapPromptEditor (accepts focusShortcutText prop)

* chore: refresh bun.lock after pull

* test(chat): drop shallow ChatMessageList snapshot tests

These tests replace every child component with a mock placeholder
and assert on literal strings appearing in the rendered HTML. That
tested mock plumbing, not behavior — every SUT import change broke
them regardless of whether the actual render output changed, and
the 'SUBAGENT_EXECUTION_MESSAGE' assertion was checking for a
component this PR intentionally inlined.

The useful bit (filter/ordering logic in messageListHelpers) is
better covered by a direct unit test — leaving as a follow-up.
…uperset-sh#3565)

Observability was enabled in superset-sh#1464 but dropped when the proxy was
re-created from scratch in superset-sh#1867. Without it, every wrangler deploy
reconciles Cloudflare back to logs/traces off, which is why the
dashboard toggles kept reverting after each production deploy.

Pin invocation_logs explicitly so future config drift can't silently
disable it again. Audit logs are an account-level setting and still
need to be re-enabled in the Cloudflare dashboard separately.
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.