sync: merge upstream/main through #3700 (ours) to reduce behind count#416
Merged
sync: merge upstream/main through #3700 (ours) to reduce behind count#416
Conversation
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.
…#3560) - drizzle-orm 0.45.1 → 0.45.2 (GHSA-gpj5-g38j-94v9 / CVE-2026-39356) - better-auth family 1.5.6 → 1.6.5 (GHSA-xr8f-h2gw-9xh6)
…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.
* docs: consolidated weekly changelog — 2026-04-20 Supersedes superset-sh#3206 (2026-04-06) and superset-sh#3404 (2026-04-13), folding in this week's v2 workspace work so three weeks of shipped changes land in one post instead of three stale PRs. * docs(changelog): include chat UX overhaul (superset-sh#3039) in consolidated post * docs(changelog): add compressed chat-ux hero screenshot * docs(changelog): refresh chat-ux hero and add v2 file tree screenshot * docs(changelog): add brand refresh screenshot * docs(changelog): add v2 diff viewer screenshot * docs(changelog): v2 workspace framed as early access, add screenshot * docs(changelog): rename title to 'v2 early access' * docs(changelog): frame v2 as a cloud-aimed rebuild — terminal rewrite, IDE architecture * docs(changelog): drop articles in title * docs(changelog): light trim — drop redundant lead-ins and filler words
…es with / (superset-sh#3232) * fix: fall back to FETCH_HEAD checkout when gh pr checkout fails for branch names with / Fixes superset-sh#3231 gh pr checkout internally runs `git checkout -b <branch> --track origin/<branch>`. When the branch name contains `/`, git cannot resolve the tracking ref inside a freshly created detached worktree, producing "starting point is not a branch". The fetch succeeds — only the tracking setup fails. Catch that specific error and fall back to `git checkout -b <localBranchName> --no-track FETCH_HEAD`. push.autoSetupRemote=true (already set after worktree creation) handles push tracking without needing --track. * fix: use -B flag to force-replace branch in FETCH_HEAD fallback checkout * fix: log fallback path when gh pr checkout fails with tracking error --------- Co-authored-by: Ruan Gustavo Araujo da Silveira <ruan.silveira@M4Pro.local>
…erset-sh#3546) * feat(desktop): safer defaults for builtin terminal agent presets Swap permission-bypass flags for each CLI's intended safe-but-useful mode (claude acceptEdits, codex --full-auto, gemini auto_edit, copilot --allow-all-tools). Drop mastracode/opencode/pi from the default seed since they are YOLO-by-default at the CLI level; they remain available via Quick-Add. Remove cursor-agent's --yolo suffix (silent no-op on the real binary). Existing users are preserved — the v1 terminalPresetsInitialized guard and v2 migration marker ensure stored commands are never rewritten. * test(desktop): update agent-launch-request fixture for new codex default buildPromptAgentLaunchRequest's terminal-command fixture hard-coded the old --dangerously-bypass-approvals-and-sandbox flag. Update it to the new --full-auto default so the test reflects the current builtin. * fix(desktop): address reviewer feedback on safe-default flag choices - gemini promptCommand: add --approval-mode=auto_edit so prompt/task launches use the same safety mode as terminal launches (flagged by cubic, greptile, and CodeRabbit) - copilot: switch from --allow-all-tools to --allow-tool=write. Per GitHub's own docs, --allow-all-tools "allows all tools to run automatically without confirmation" including shell, which contradicts the safe-by-default claim. --allow-tool=write auto- approves file edits only (analog of claude's acceptEdits). - docs: update copilot line; clarify mastracode/opencode/pi opt-in parentheticals so users understand why they're not auto-seeded.
) * docs: v2 project create/import design + plan Simplified redesign after PR review. Collapses the earlier three-signal backing model (cloud + per-host cloud signal + local) into two signals (cloud + local-only), removes the v2_host_projects cloud table and Electric sync, drops per-row state decoration on the sidebar, and moves backing checks to action time (workspace-create modal, error paths). * feat(trpc): v2Projects.findByGitHubRemote + jwt-scoped create Adds the cloud-side matcher used by host-service's folder-first import flow: given a clone URL, returns candidate projects the user has access to whose GitHub repo matches (case-insensitively). Named findByGitHubRemote (not findByRemote) because the match is GitHub-specific. v2Projects.create switches to jwtProcedure with an explicit organizationId + repoCloneUrl, matching the shape host-service needs to call from project.create. No existing callers. parseGitHubRemote moves from packages/host-service to packages/shared so both cloud tRPC and host-service consume the same implementation. * feat(host-service): project.create / setup / list / findByPath / remove Full create/import lifecycle in host-service: - project.list — DB read of host-service.projects. Pure, no filesystem probing. Stale paths surface via operation errors, not proactive checks. - project.findByPath — validate git root, read remote, forward to cloud v2Projects.findByGitHubRemote. Backs the folder-first import picker. - project.create — discriminated-union mode (empty/clone/importLocal/ template); Phase 1 ships clone + importLocal only, empty and template throw NOT_IMPLEMENTED. - project.setup — discriminated-union mode (clone/import) with acknowledgeWorkspaceInvalidation gate on the re-point case. - project.remove — local worktree + repo dir teardown. Cloud backing (v2_host_projects) is intentionally absent: there is no per-host cloud signal in this design. Backing is a local-only concept, checked at action time. Adds ProjectNotSetupCause to the error formatter so the renderer can catch throws from workspace.create (next commit) and open the Pin & Set Up modal inline. * feat(desktop): add-repository modals at dashboard layout level Three flows for getting projects onto this device: - New project — clone a GitHub URL into a chosen parent directory. Drives project.create(mode=clone). - Import existing folder — native picker → project.findByPath branches on candidate count. 0 → name + create (importLocal). 1, not set up here → auto-advance to project.setup. 1, already set up → destructive re-point confirmation. >1 → picker modal. - Pin & set up — clone an existing cloud project onto this device. Drives project.setup(mode=clone), with forceRepoint entry for repair. All three modals are mounted once at the dashboard layout level via AddRepositoryModals, and opened through a small zustand store. Sidebar header "Add repository" dropdown triggers New project / Import folder. * feat(desktop): workspaces-tab Available section + folder-first import trigger Lists cloud projects in the user's active org that aren't pinned locally. Pin & set up per row runs project.setup. Header dropdown ("Add repository") mirrors the sidebar — "+ New project" + "Import existing folder." Entry points route through the dashboard-level AddRepositoryModals via the shared zustand store. useAvailableV2Projects powers the section: antijoin v2Projects ∖ v2SidebarProjects scoped to the active organization, with the existing v2-workspaces search filter applied. * feat(desktop): workspace-create inline setup + remote-device stub - Host-service workspaceCreation.{create,checkout,adopt} throw PROJECT_NOT_SETUP (PRECONDITION_FAILED + cause { kind, projectId }) when this host has no local project row. No more silent auto-clone into ~/.superset/repos/ — the user explicitly picks where to clone. - Pending workspace-create page intercepts data.projectNotSetup on the error, opens the Pin & set up modal pre-filled with the project, and registers a one-shot onSuccess callback to retry the original intent once setup resolves. The pending row stays in "creating" through the modal so the UI doesn't flicker to failed. - Clicking a remote-device workspace row lands on the new WorkspaceNotOnThisHostState stub: explains the workspace lives on another host, offers "Set up here" (opens Pin & set up for the project) or "Browse workspaces." V2 workspace page checks host.machineId via live query and renders the stub before mounting the pane tree, which would otherwise crash on a foreign worktree. * Fix infinite import * fix: pre-existing notification test, a11y labels, design doc shape - notification-manager.test: update expected strings to match source (strings changed in superset-sh#3039; test wasn't updated, CI was red on main too) - DashboardSidebarHeader: aria-label="Add repository" on icon-only dropdown triggers so screen readers announce them (tooltips don't count as accessible names) - docs/design/v2-project-create-import: correct v2Projects.create input shape (jwt-scoped { organizationId, name, slug, repoCloneUrl }) * feat(desktop): unify new-workspace pickers + link popovers, strip chat link UI - Unify DevicePicker / ProjectPickerPill / CompareBaseBranchPicker to a shared FORM_PICKER_TRIGGER_CLASS: no background, h-[22px], text-[11px] text-muted-foreground, size-3 icons, align="start" dropdowns. Bump the project trigger thumbnail to size-4; drop the leftover `!` override (twMerge handles it). - DevicePicker: icon-only trigger (aria-label + title surface the name). - Rewrite IssueLinkCommand / PRLinkCommand / GitHubIssueLinkCommand to one codepath each: accept a button as `children`, wrap it in PopoverTrigger, own their open state internally. No more shared plusMenuRef, no more external open/onOpenChange/anchorRef coordination, no manual onPointerDownOutside anchor-guard — Radix handles toggle and dismiss natively so clicking a trigger while its popover is open closes it like every other picker. - v2 NewWorkspace PromptGroup: drop the three popover-open useStates + plusMenuRef + manual toggle handlers. AttachmentButtons becomes a layout shell that renders the three trigger elements as props; each wraps a shared LinkTrigger (tooltip + pill button). - Chat (v1 + v2) + v1 NewWorkspace PromptGroup: remove the link-issue popover wiring (IssueLinkCommand usage, ChatShortcuts' onLinkIssue callback). PlusMenu in chat collapses from a dropdown with attach/link options to a plain attachment button. - Temporarily disable v2 ChatPane render: it predates this PR and is missing ChatServiceProvider (introduced in PR superset-sh#3088), so chatServiceTrpc has no context in TiptapPromptEditor. Replaced with a "Chat pane is temporarily disabled" placeholder; original render body commented out for quick restoration. * feat(desktop): host-scoped project picker with Available / Needs setup sections The v2 new-workspace picker was listing every cloud project the user had access to, regardless of whether it was set up on the selected device. That produced the PROJECT_NOT_SETUP error path on submit — reviewers flagged the pending-row-stuck-in-creating fallout as a P1. Root-cause fix: split the project list by selected-host availability. - `useHostProjectIds(hostTarget)` queries host-service `project.list` on the chosen device (local via activeHostUrl, remote via relay) and returns the set of set-up project IDs. - PromptGroup splits `recentProjects` into `availableProjects` + `needSetupProjects` using that set; changing the device refetches. - ProjectPickerPill renders two CommandGroup sections: Available (click selects) and Needs setup (click opens Pin & set up for that project). - Pin & set up already invalidates `["project", "list", activeHostUrl]` on success, so after setup the project flips to Available — user picks it and continues normally. While `project.list` is loading or errors, everything falls back to Available — picker stays usable; any real failure surfaces via the existing workspace-create error path. * lint * fix(desktop): IssueLinkCommand uncontrolled close + PlusMenu aria-label - IssueLinkCommand: the refactored popover-trigger API made `open` and `onOpenChange` optional so callers (v2 PromptGroup) could let Radix manage state. But `handleSelect` only fired the optional controlled callback, so in uncontrolled mode the popover never closed after picking an issue. Track state ourselves via a controllable-state pattern: internal `useState` when the prop is absent, caller's value when passed. `setOpen` always writes through, so close-on-select works in both modes. - PlusMenu: add aria-label="Add attachment" to the icon-only trigger. Radix Tooltip sets aria-describedby on the trigger, not aria-labelledby, so screen readers previously announced it as an unlabeled button. * refactor(desktop): drop controlled-open props from IssueLinkCommand; extend aria-label fix - IssueLinkCommand: only caller passes `onSelect + children`, so the optional open/onOpenChange pass-through was dead code. Simplify to always-internal state. Radix Popover has no imperative close from inside its content — owning state is the canonical shadcn/cmdk pattern, not scaffolding. - AttachmentButtons (v2): add aria-label to the shared LinkTrigger (so Link issue / Link GitHub issue / Link pull request all announce a name) and to the paperclip. Same fix as PlusMenu — Radix Tooltip sets aria-describedby on the trigger, not aria-labelledby, so tooltip-only buttons read as unlabeled to screen readers. * fix(trpc): scope v2Project.findByGitHubRemote + modal picker to active org host-service is pinned to a single organization at boot (env.ORGANIZATION_ID); its local projects table has no orgId column. Project discovery was leaking across orgs: - v2Project.findByGitHubRemote used ctx.organizationIds (plural, all accessible orgs). The folder-first picker would surface candidates from orgs the current host can't set up, producing a confusing NOT_FOUND when host-service then called v2Project.get with its own org. - DashboardNewWorkspaceModalContent queried collections.v2Projects with no org filter. Same over-fetch, same downstream failure. Align both with the rest of the codebase (v2Project.get / create, useAvailableV2Projects, useWorkspaceHostOptions) which take/filter by an explicit active orgId: - findByGitHubRemote: add organizationId input, authorize it against ctx.organizationIds (same shape as get/create), filter candidates by it. - host-service project.findByPath: pass ctx.organizationId through. - DashboardNewWorkspaceModalContent: .where(eq(projects.organizationId, activeOrganizationId)) on the live query, matching useAvailableV2Projects. * Lint * fix(host-service): clone-then-cloud in project.createFromClone, rollback on cloud failure Matches the local-first-then-cloud pattern already used by workspace.create (workspace-creation.ts:860-918, which git-worktree-adds first then registers cloud with a rollback on failure). Previously createFromClone called v2Project.create before cloneRepoInto, so any clone failure (network, bad URL, auth, dir collision) left a cloud v2_projects row with nothing local backing it on any host. Retrying the flow with corrected input accumulated more orphans. Reorder: clone first, register cloud in try/catch, rmSync the freshly- created clone if cloud-create or persistLocalProject throws. * fix(host-service): move project.create visibility into GitHub-provisioning modes Only empty + template modes provision a new GitHub repo and need to tell the GitHub App whether it should be private or public. clone + importLocal reuse an existing remote where visibility is already set — the top-level field was required but ignored for those two paths. Move `visibility: z.enum(["private", "public"])` into the empty and template variants of the discriminated union. Drop it from clone/ importLocal callers. Update design doc to match. * refactor(desktop): use Radix composition for link-command tooltips, drop dead chat-link wiring Responds to saddlepaddle + Kitenite reviews on PR superset-sh#3566. - IssueLinkCommand / PRLinkCommand / GitHubIssueLinkCommand now own the Popover + Tooltip composition internally via `PopoverTrigger asChild > TooltipTrigger asChild`. Callers pass a plain PromptInputButton + a tooltipLabel prop. Removes the LinkTrigger forwardRef + `{...rest}` spread trick that was sneaking Popover props through an intermediate Tooltip wrapper. - Delete the misleading JSDoc at IssueLinkCommand claiming Radix can't be closed imperatively — PopoverClose exists; the controlled-open pattern we use is just shadcn's canonical combobox. - Drop orphaned `_issueLinkOpen` / `_addLinkedIssue` + the `setIssueLinkOpen` prop threaded through ChatShortcuts in both v1 and v2 chat, plus the same dead state in v1 NewWorkspaceModal PromptGroup. - Retire the CHAT_LINK_ISSUE hotkey entry — its only consumer was the dead setIssueLinkOpen toggle. * chore: trim past-state narration + what-describing comments - Drop "previously this did X" / "introduced in PR superset-sh#3088" / commented-out renderPane block in v2 usePaneRegistry chat pane. - Collapse JSDocs that only restated the function's name (ParentDirectoryPicker, AddRepositoryModals layout blurb, per-method docs on UseFolderFirstImportResult, persistLocalProject). - Tighten the explanatory comments that still earn their keep (pending PROJECT_NOT_SETUP interceptor, PinAndSetupModal conflict state, store onSuccess / forceRepoint prop docs). * refactor(desktop): strip v2 discovery/recovery surface to MVP Two rules for v1: - Sidebar = pinned projects. - Workspaces tab = every workspace in the user's active org. Code deletes: - V2AvailableProjectsSection, useAvailableV2Projects, useHostProjectIds. - v2UsersHosts innerJoin in useAccessibleV2Workspaces — tab no longer drops rows when host access changes. - Available-section wiring in V2WorkspacesList + v2-workspaces/page.tsx. - Available / Needs-setup split in ProjectPickerPill and the openPinAndSetup bridge in PromptGroup. Also removes the wrong-host bug (cubic AF_o, saddlepaddle CXVQ) as dead code. - PROJECT_NOT_SETUP recovery loop in the pending page — failure is a plain toast now. Docs realigned: - design/v2-project-create-import.md opens with the two rules and moves Available / inline setup / backing signals to an explicit "Out of scope for v1" block. - plans/20260417-v2-project-create-import-impl.md mirrors the same deferrals; Phase-1 checklist is now all checked. Net −537 lines. Typecheck + lint clean. * refactor(desktop): open remote-host workspaces without gating Previously any workspace whose hostMachineId didn't match the local machine landed on a WorkspaceNotOnThisHostState stub. That hid the workspace from the user entirely when the whole point is to let them see it. Delete the gate, delete the stub component, and let the workspace page render for any host. Operations that assume local filesystem (terminal spawn, local git) fail at the point they run. Also slims the page's live query — projectGithubOwner, projectName, hostMachineId etc. were only fed into the stub. Design doc + plan updated to reflect the no-gating posture. Resolves saddlepaddle CTvC. * refactor(desktop): extract FormPickerTrigger component Addresses saddlepaddle CWlq — the shared style for the three top-of-modal pickers (Device / Project / Branch) lived as a string constant in types.ts, which is an odd place for a className and doesn't compose. Promote it to a named FormPickerTrigger component that encapsulates the base button styles and accepts extra className + native button props. The three call sites lose their raw <button type="button"> + backtick-composed classNames. Drops FORM_PICKER_TRIGGER_CLASS from types.ts. * refactor(desktop): remove dead PinAndSetupModal + async-hygiene sweep PinAndSetupModal had zero remaining callers after the MVP cut — the pending-page PROJECT_NOT_SETUP interceptor and the Available-section "Pin & set up" button were the only two. Delete the whole modal, its store action, useOpenPinAndSetupModal hook, PinAndSetupTarget type, and the forceRepoint plumbing that existed only to support it. Also addresses the async-hygiene nits on the surviving surfaces: - useFolderFirstImport.start wraps selectDirectory.mutateAsync in try/catch → reportError (coderabbit nmS, cubic op5). - ParentDirectoryPicker.handleBrowse wraps the same (cubic op8). - AddRepositoryModals effect adds .catch on startRef.current() (cubic oqE). - FolderFirstImportModal keys CandidatePickerContent on repoPath so selectedId resets per import (coderabbit nmM). Docs + plan updated to reflect the removed modal + ENOENT recovery deferral. Net −205 lines. Typecheck + lint clean. * refactor(host-service): reject re-pointing instead of confirming it v1 has no re-point UX. project.setup now treats an existing row as: - same resolved path → no-op success (idempotent; fixes the false CONFLICT that cubic/coderabbit flagged on same-path setup). - different path → CONFLICT with the existing path in the message, no escape hatch. User must project.remove first if they genuinely want to move the project. Drops `acknowledgeWorkspaceInvalidation` from the input, the ack branch of the CONFLICT guard, and the setupFromClone/setupFromImport helpers + SetupContext type in handlers.ts (the setup path is small enough to inline). Client drops the confirm-repoint state, confirmRepoint method, ConfirmRepointContent component, and the conflict branch in SetupInvokeResult — none of which have anything to retry against. Also fixes the TOCTOU race in cloneRepoInto: replaces existsSync + rmSync-on-error with mkdirSync (atomic claim) + rmSync-on-error, so clone failure can't delete a directory this process didn't create. Resolves coderabbit nmb, nmd, and cubic oqN. * fix(desktop): unify workspaces-tab empty state The onboarding "No workspaces yet" check was reading already-filtered pinned/others counts, so a search that matched nothing landed on the onboarding copy instead of the clear-filters UI. Collapse to a single !hasAnyMatches branch that picks copy + icon based on hasActiveFilters. Drops the bogus hasAnyWorkspaces check. Resolves cubic CrwE. * revert queries * Clean up dead code * refactor(desktop): port v1 new-project UI into NewProjectModal Replace the bespoke name + clone-URL + parent-picker form with v1's new-project page layout: a Location row (text input + browse button), three mode tiles (Clone/Empty/Template), and a per-mode form. Only Clone is wired up; Empty + Template carry "(coming soon)" since v2 project.create throws NOT_IMPLEMENTED for them. Location auto-populates to ~/.superset/projects via window.getHomeDir. Project name is derived from the clone URL's last segment so the form matches v1 (no explicit name field). ParentDirectoryPicker deleted — the inline Input + folder button replaces it and there's no other caller. * feat(db/trpc): decouple v2 projects from GitHub App installs v2Projects previously required a non-null githubRepositoryId, which gated project creation on the org having installed the repo via the GitHub App. Cloning any other repo (public, not installed, or non- matching) failed at the cloud step after a successful local clone. Changes: - githubRepositoryId becomes nullable with ON DELETE SET NULL, matching v1's projects table. - repoCloneUrl is added as the canonical source of truth for the remote URL. Also nullable so empty-mode / local-only projects without a remote can coexist. - UNIQUE(organization_id, lower(repo_clone_url)) prevents two projects from claiming the same repo in one org. NULLs don't collide, so URL-less projects still work. - v2Project.create accepts an optional repoCloneUrl, canonicalizes via parseGitHubRemote, and links a matching github_repositories row case-insensitively when one exists. Unique-violation (23505) surfaces as CONFLICT with per-constraint messaging. - v2Project.findByGitHubRemote matches on v2Projects.repoCloneUrl directly instead of joining through the installation table, so unlinked projects are discoverable. - v2Project.get drops the derived repoCloneUrl — consumers read the stored column or the joined githubRepository directly. Migration 0034 bundles all five schema changes. Nullable-safe: no backfill required for existing rows. * No candidate thing * feat(desktop): flag projects not set up on selected host in new-workspace modal After picking a host in DevicePicker, each project in ProjectPickerPill shows an amber warning triangle when that host doesn't have the project set up locally. A matching "Project needs to be set up" note appears next to the ⌘↵ hint when the currently-selected project needs setup, so the user sees the blocker before submitting. Setup state comes from a per-host project.list query (re-added to the host-service router). The RPC is resolved through the standard getHostServiceClientByUrl path — local uses activeHostUrl, remote/cloud goes through the relay. If the host is unreachable we treat setup as unknown and hide the indicator rather than falsely flagging everything. Submit path is unchanged: picking a not-set-up project still fires workspace.create, which throws PROJECT_NOT_SETUP and surfaces as the existing toast. Inline setup UX is still deferred. * chore: biome format fix + sync design doc to workspaces-tab filter - git.ts: biome wants the ghMsg ternary wrapped; main's 27e243b added the catch block and the CI biome check caught it post-merge. - design doc: the workspaces tab code filters to hosts the user is linked to via v2_users_hosts, not every workspace in the org. Update wording to match what shipped; note teammate workspaces on unshared hosts are not surfaced in v1. * docs: move v2 project create/import plan to plans/done Plan is shipped — move per AGENTS.md rule 7 and drop the rewrite/history notes in both plan and design doc since the PR body is the canonical record of what was cut. * docs: drop rewrite/history notes from plan and design doc Captured in PR body instead. * chore: trim restating/navigational comments in project handlers
* docs(automations): add implementation plan + UI mocks
Plan covers schema, dispatcher path (cloud API → relay → host-service),
paid-plan gating, and the shared-code extraction work (AgentLaunchRequest
+ workspace-launch defaults) that must land before the dispatcher.
16 HTML mocks under apps/desktop/docs/automations-ui/ preview the desktop
surfaces (list, detail, create modal, schedule picker, agent picker,
template gallery, paused/offline variants, nested scheduled-run route).
Tokens mirror the renderer's ember theme.
* refactor(shared): lift agent-settings, agent-launch-request, and preset schemas into @superset/shared
These were desktop-only utilities, but scheduler dispatch (for the upcoming
automations feature) needs to build the same AgentLaunchRequest shape from
the cloud API. Moving them to the shared workspace package keeps the
dispatcher, desktop renderer, and host-service on a single code path with
zero duplication.
- New @superset/shared/agent-custom exposes the preset override and custom
agent definition schemas that previously lived inside @superset/local-db.
local-db now re-exports from shared and depends on it.
- agent-settings, agent-launch-request, and their tests move to
packages/shared/src/ with relative imports within the package.
- PROMPT_TRANSPORTS tuple consolidates from local-db into the existing
agent-prompt-launch module so the PromptTransport type and its runtime
source of truth stay colocated.
- All 24 desktop importers updated; typecheck + shared tests green.
* feat(trpc): add paidPlanProcedure and @superset/shared/billing
Feature-gate scaffolding for the upcoming automations router. Introduces a
single source of truth for billing plan tiers and "active subscription"
status, wires a server-side procedure that rejects free-tier orgs, and
upgrades the existing useCurrentPlan hook to consume the shared helpers.
- New @superset/shared/billing exports PLAN_TIERS, PlanTier,
ACTIVE_SUBSCRIPTION_STATUSES, isPaidPlan, isActiveSubscriptionStatus.
- @superset/trpc exposes getCurrentPlan(activeOrganizationId) and
paidPlanProcedure (throws FORBIDDEN when plan === "free").
- useCurrentPlan now recognizes trialing subscriptions and re-exports
PlanTier from shared. useIsPaidPlan added for quick client gates.
- Desktop billing constants re-export the tier types from shared so
existing downstream usages stay unchanged.
* feat(auth): add mintUserJwt helper for headless dispatch
The automations dispatcher runs without a user session cookie but still
needs to authenticate as the automation owner when calling the relay.
mintUserJwt wraps Better Auth's jwt plugin signJWT endpoint to mint a
short-lived (default 5 min) token with matching issuer/audience claims,
so the relay's existing verifyJWT (which hits the shared JWKS endpoint)
accepts it without any additional wiring.
* feat(db): add automations + automation_runs schema
Introduces the two cloud tables the automations feature dispatches
against:
- automations holds the recurrence definition (RRule + dtstart + tz),
target host, agent preset, and one of two workspace modes.
A check constraint enforces that new_per_run rows point at a project
and existing rows point at a workspace, so the dispatcher never has
to handle a partially-valid automation.
- automation_runs records one row per scheduled dispatch attempt.
Unique (automation_id, scheduled_for) absorbs Vercel Cron duplicates.
session_kind + chat_session_id/terminal_session_id capture which
host-service procedure was invoked; exactly one of the two session
refs is populated after status="dispatched".
No completion tracking in v1 — runs stop at dispatched/skipped_offline/
dispatch_failed.
Drizzle-generated migration 0034_add_automations.sql included.
* refactor(shared): lift workspace-launch utilities (branch, slug, friendly-words, workspace-naming) into @superset/shared
Same motivation as the agent-launch lift: the automations dispatcher runs
from cloud API and needs to generate branch names / workspace slugs
identically to the desktop's new-workspace modal.
- New @superset/shared/workspace-launch barrel bundles branch sanitization,
slug generation, friendly-word name generator, and workspace-naming
helpers, plus their tests (48 additional tests).
- friendly-words declaration shim moves to packages/shared/src/types/ so
shared doesn't rely on a desktop-only types directory.
- friendly-words dep added to @superset/shared (matching the version
already used by apps/desktop).
- All 11 importers across desktop updated.
* feat(trpc): add automation router (CRUD + runNow + listRuns + parseCron)
First consumer of paidPlanProcedure. Exposes the server surface for the
desktop UI and CLI:
- list / get (with last 10 runs) / create / update / delete / setEnabled
- runNow inserts a scheduled_for = now() run, idempotent against the
same-minute bucket
- listRuns paginates run history for a given automation
- parseCron converts a 5-field cron to RRule (common shapes first-class:
daily, weekdays, weekly, monthly, every-N-minutes; generic fallback for
everything else). Returns rrule + scheduleText + next 5 occurrences so
the desktop can render a preview without loading rrule.js client-side.
- validateRrule mirrors parseCron for power-user direct RRule input.
All procedures verify org scoping + owner-only access. Host and workspace
refs are validated through v2_users_hosts and v2_workspaces.organization_id
before being persisted.
RRule parsing lives in rrule.ts with a small test suite locking in the
cron→RRule mapping and the "next occurrence in user timezone" semantics.
* feat(api): automations dispatcher + reconciler cron routes
Closes the Phase-1 loop: cron → dispatcher → relay → host-service.
- /api/cron/automations/dispatch (minute-ly) selects due automations,
checks v2_hosts.is_online, mints a short-lived user JWT via
mintUserJwt, allocates (or reuses) a workspace on-host, builds an
AgentLaunchRequest from the shared builtin presets, and routes the
prompt to chat.sendMessage or terminal.ensureSession based on the
agent's kind. Advances next_run_at on every outcome (at-least-once,
no auto-retry), so dispatch failures are visible in automation_runs
but don't block the schedule.
- /api/cron/automations/reconcile (5-min) marks runs stuck in
"dispatching" for >10 min as dispatch_failed and logs automations
whose next_run_at has drifted >1h into the past.
- Minimal relay-client wraps tRPC-over-HTTP without importing the
host-service router type (keeps the cloud bundle clean).
- vercel.json registers both crons. CRON_SECRET + RELAY_URL added to
apps/api env.
- Triple-slash reference in workspace-launch/index.ts surfaces the
friendly-words declaration shim to every downstream consumer.
* feat(cli): add superset automations subcommand tree
Six commands auto-discovered by the CLI framework:
superset automations list
superset automations create --name --prompt [--rrule|--cron] --project|--workspace
superset automations update <id>
superset automations delete <id>
superset automations logs <id>
superset automations run <id>
All calls hit the paid-plan-gated automation.* tRPC router. Cron
expressions are accepted as sugar (--cron) and converted to RRule via
the server's parseCron helper, so the CLI stays cron-friendly even
though the storage format is RRule.
CLI_SPEC.md updated — the previous "crons" section is now "automations"
with RRule-primary semantics and v1-accurate status values
(dispatched / skipped_offline / dispatch_failed).
* chore(automations): tidy biome config + rrule tests
- biome.jsonc ignores apps/desktop/docs/automations-ui/** so the static
HTML mocks don't get linted against a11y rules meant for production
React.
- rrule.test.ts removes non-null assertions flagged by lint/style —
same coverage, explicit guards.
* refactor(automations): drop --cron sugar and custom cron→RRule converter
The hand-rolled cronToRrule (~100 lines in rrule.ts) silently dropped
step values in hour/day fields and had brittle range handling. Not
worth the maintenance tax for a small UX win — users who want cron
have plenty of cron→RRule reference tables online.
- Removed cronToRrule + mapDayOfWeek from rrule.ts.
- Removed automation.parseCron tRPC procedure and parseCronSchema.
- CLI create/update commands now require --rrule directly. CLI_SPEC.md
updated with a quick reference table for common RRule shapes.
- cron-parser dropped from @superset/trpc deps.
* fix(automations): runNow now actually triggers, workspace create return shape, use shared terminal command builder
Three correctness fixes caught during pre-testing review:
1. runNow previously inserted an automation_runs row with
scheduled_for = now(), but the dispatcher selects from the
automations table keyed on next_run_at, so the inserted row was
ignored. Now runNow bumps next_run_at to 1s ago (within the
dispatcher's lookback window), and the dispatcher owns the run-row
insert — keeping the (automation_id, scheduled_for) idempotency key
consistent. Also refuses to trigger when the automation is paused.
2. The relay typing for workspaceCreation.create was wrong —
the host-service returns { workspace, terminals, warnings }, not
{ workspaceId }. Narrow to workspace.id and drill into result.workspace.
3. Dropped the hand-rolled terminal command concatenation in
buildTerminalCommand and delegated to buildPromptCommandFromAgentConfig
/ getCommandFromAgentConfig from @superset/shared/agent-settings. The
shared builder handles prompt transport (argv vs stdin), suffix
escaping, and delimiter collisions correctly.
* chore(env): document CRON_SECRET and RELAY_URL for automations
These two env vars are required by /api/cron/automations/{dispatch,reconcile}
and the automations dispatcher's relay client. Adding them to .env.example
so new contributors know to set them (and how).
* chore(shared): add AUTOMATIONS_ACCESS to FEATURE_FLAGS
Companion constant for the automations-access PostHog flag that gates
the Automations UI. Paid-plan check remains the authoritative server
gate; this flag controls UI visibility and staged rollout so we can
dogfood the feature inside the org before flipping it on for all paid
users.
* feat(desktop): automations sidebar + list/detail routes + create modal
Phase 2 v1 desktop surface for the Automations feature, gated on
useFeatureFlagEnabled("automations-access") AND useIsPaidPlan() so the
sidebar entry only appears for dogfood users on a paid plan.
Routes
- /automations — list. Status dot + name + agent + human-readable
schedule string. Empty state uses shadcn Empty component.
- /automations/$automationId — detail. Breadcrumb, prompt body, and a
Status / Details / Previous runs rail. Pause/resume, delete, run-now
actions wire through automation.setEnabled / delete / runNow.
Primitives
- Sidebar entry added to DashboardSidebarHeader (collapsed + expanded
variants), hidden unless both the paid-plan and PostHog flag checks
pass.
- New FullScreenModal component in the automations route tree — the
create flow calls for a full-bleed sheet (matching the reference
mocks), which shadcn's centered Dialog can't express. Composition
mirrors Dialog so it's familiar.
- CreateAutomationDialog uses FullScreenModal + Input/Textarea/Popover/
Select/Button from @superset/ui. Bottom chip bar offers Project,
Agent (builtin preset), Schedule (preset + custom RRule). No custom
styling — shadcn variants throughout.
Status pills use Badge variants; section dividers use Separator;
breadcrumbs use Breadcrumb. Paused states render with Badge "paused".
* refactor(desktop): shrink create-automation modal to standard Dialog
Full-screen sheet was overwhelming for a 4-field form. Switching to the
standard shadcn Dialog sized ~800×400: title input + info/use-template
actions in the header, prompt textarea in the body, chip bar + primary
actions in the footer. Same layout as the reference mock, just at
dialog scale.
FullScreenModal primitive removed — no longer needed.
* refactor(automations): reuse DevicePicker + ProjectPickerPill from new-workspace modal
The create-automation chip bar now drives device + project selection
through the same pickers the new-workspace modal uses, ensuring
consistent behaviour (host discovery via v2_users_hosts + v2_hosts,
Electric-synced project list with GitHub owner/repo derivation,
identical search affordances).
Order matches the reference mock: Device → Project → Schedule → Agent.
useWorkspaceHostOptions also exposes localHostId so callers that need
a concrete v2_hosts.id (e.g. the automation dispatcher's targetHostId
field) can resolve "local" to a real uuid.
* feat(desktop): show online dot for remote hosts in DevicePicker
isOnline surfaces through useWorkspaceHostOptions into each
WorkspaceHostOption, and DevicePicker renders a trailing dot (emerald
when online, muted when offline) on the "Other Hosts" submenu rows and
on the trigger button when a remote host is selected.
Local Device gets no dot — it's the app itself, tautologically online.
* fix(desktop): fix DevicePicker trigger width to prevent layout shift
Swap max-w-[140px] for w-[140px] on the selected-label span so the
trigger reserves the same horizontal space regardless of selection.
Previously switching between "Local Device" and a long host name
would reflow the footer chip bar.
* fix(desktop): fix DevicePicker trigger button at 140px total
Move the fixed width from the inner label span to the Button itself so
the whole trigger (icon + label + dot + chevron) reserves a single
140px slot. Label truncates within the remaining space after icons.
* fix(desktop): use consistent icon for remote desktop hosts in DevicePicker
Trigger rendered HiOutlineServer for remote non-cloud hosts while the
Other Hosts submenu rendered HiOutlineComputerDesktop for the same
rows. Unify on HiOutlineComputerDesktop — the distinction the icon
conveys is local vs cloud, not local vs remote-same-shape.
* fix(desktop): park online dot next to the host name, not the right edge
The Other Hosts submenu row used flex-1 on the name wrapper, which
pushed the online dot all the way to the right (where the check mark
lives for the selected host). Drop flex-1 so the dot hugs the name,
and give the check ml-auto so it still pins to the right edge.
* refactor(desktop): promote TaskMarkdownRenderer → MarkdownEditor (shared)
The Tiptap + slash-commands + bubble-menu editor wasn't task-specific;
renaming and relocating so the automations create dialog can reuse it
verbatim.
- Moved apps/desktop/.../tasks/$taskId/components/TaskMarkdownRenderer
→ apps/desktop/src/renderer/components/MarkdownEditor.
- Renamed component + CSS file accordingly.
- Updated all four callsites (task detail page, create-task dialog,
index re-export, css import).
- CreateAutomationDialog swaps the minimal plain-text PromptEditor for
MarkdownEditor, so the prompt field now supports headings, lists,
checklists, code blocks, slash commands, and markdown typing
shortcuts — saved as markdown via tiptap-markdown.
- Title field uses a borderless <input> matching the task detail
EditableTitle aesthetic.
* refactor(automations): shared PickerTrigger, subcomponent breakdown, markdown emoji
- Extract PickerTrigger shared renderer component; refactor DevicePicker +
new ProjectPicker/SchedulePicker/AgentPicker to compose it with
parent-owned widths
- Break down automations list/detail pages into AutomationsEmptyState,
AutomationsListRow, AutomationDetailHeader, AutomationDetailSidebar,
PreviousRunsList subcomponents
- Extract useRecentProjects hook from CreateAutomationDialog
- Wire @tiptap/extension-emoji into MarkdownEditor with :shortcode popup
* feat(automations): Electric sync, schedule-text util, template gallery, file-mentions
- Electric: subscribe automations + automation_runs in CollectionsProvider;
expose WHERE clauses in electric-proxy; enable eager auto-indexing on all
collections to remove TanStack DB full-scan join warnings.
- List/detail pages read from Electric via useLiveQuery with native orderBy,
no more manual sort + createdAt.getTime() runtime crash.
- New packages/shared/src/schedule-text.ts — describeSchedule(rrule) renders
common RRULE shapes as short English ("Weekdays at 9:00 AM"), falls back to
"Custom" for anything outside the patterns we generate. 23 unit tests.
- Drizzle: automations.agent_config jsonb (snapshots ResolvedAgentConfig at
create time) replaces agent_type; tRPC + CLI adapted.
- Automations UI: template gallery panel + TemplateCard, markdown editor
file-mention suggestions, empty-state polish, list-row cleanup.
- Host-service: filesystem.searchFiles endpoint to back the file-mention hook.
* feat(automations): inline edit, picker-based detail sidebar, deep-link runs
- Detail page: title + prompt now edit inline via EmojiTextInput +
MarkdownEditor (save on blur), matching the create modal. `key` on
AutomationBody so state resets when navigating between automations.
- Detail sidebar: Device/Project/Workspace/Repeats/Agent/Timezone swap
static rows for the same pickers the create modal uses. Each change
fires automation.update. Width +80px, tighter row gap, negative
right margin so picker chevrons align with text.
- List page: shadcn Table with Name/Owner/Project/Workspace/Device/
Agent/Schedule columns + per-row ⋯ menu (Edit/Run now/Delete w/
confirm). ToggleGroup "Mine | Team" scope switch.
- Run now decoupled from cron: dispatch logic extracted into
@superset/trpc/automation-dispatch and invoked inline so the row
appears immediately. nextRunAt is no longer nudged. lastRunAt column
dropped in favor of max(scheduledFor) from recent runs.
- Previous runs list is clickable + deep-links into the workspace via
?terminalId=/?chatSessionId=; workspace route reads those and opens
the matching pane (or focuses it if already open).
- Timezone correctness: rrule.js input + output conversions via
@date-fns/tz TZDate so wall-clock semantics survive DST.
- Title denormalized on automation_runs, run.status dot + tooltip,
dispatch_failed catch-all so rows never hang in "dispatching".
- Dropped workspaceMode enum + compound CHECK; v2_project_id is NOT
NULL, v2_workspace_id is plain uuid (retained for traceability).
- Widened workspace-create relay timeout to 90s and set tRPC route
maxDuration=60 to reduce timeout-induced dispatch failures.
* refactor(automations): co-location, shared rrule, CLI polish + CI wiring
- Move rrule.js/@date-fns/tz helpers from packages/trpc into
@superset/shared/rrule (single home for preset matching + DST-aware
occurrence math). Delete trpc/router/automation/rrule.ts.
- Fold packages/db/src/schema/automations.ts into schema.ts (one file
per logical schema; public tables no longer split arbitrarily).
- Restructure automations UI per AGENTS.md: promote shared pickers +
hooks to /automations/{components,hooks}; extract Section/SectionTitle/
Row from the sidebar, AutomationBody from the detail page, and
CellWithIcon/AgentCell from the list page into their own folders.
- CLI: fix pre-existing positional-arg bug across run/delete/update/logs
(they never actually worked); add get, pause, resume commands. Spec +
docs updated to match reality (agentConfig snapshot, project always
required, inline dispatch on run).
- Drop the bun-run-dev spinner spam: only animate when stdout is a TTY.
- Add CRON_SECRET to preview + production deploy workflows; include
SUPERSET_WEB_URL in setup.sh + local .env so `superset auth login`
reaches the web origin without manual export.
- Remove dead code: components/AutomationsListRow, deprecated
UserPlan alias, useIsPaidPlan wrapper, apps/desktop/docs/automations-ui
mock gallery, demo-launch-spec (unreferenced).
* chore: drop biome ignore for deleted automations-ui mock gallery
* chore(shared): drop local friendly-words shim for @types/friendly-words
Removes the hand-written triple-slash reference now that the official
@types package covers the API.
* feat(automations): QStash heartbeat + per-run dispatch + DLQ
Replaces the Vercel-cron dispatch/reconcile loop with three thin routes:
- /api/automations/evaluate (QStash schedule every minute): select due
rows, batchJSON-enqueue one message per automation keyed by
deduplicationId, advance next_run_at in parallel.
- /api/automations/dispatch/[id] (QStash consumer): verify signature,
re-read the automation, call the shared dispatchAutomation.
- /api/automations/run-failed (QStash failureCallback): upsert the run
row as dispatch_failed and capture to Sentry after retries exhaust.
Drops the 5-minute lookback, pre-nudge, Promise.allSettled bulk loop,
reconciler cron, vercel.json, and CRON_SECRET — the new idempotency
chain (QStash dedup + automation_runs unique index + automation
re-read) makes them redundant.
Adds a workflow_dispatch job and idempotent bootstrap script to create
the per-environment QStash schedule.
* fix(ci): biome reformat + task test mock covers subscriptions
- Re-wrap a long ternary in apps/desktop/.../utils/git.ts that the
biome formatter wanted to split across lines (surfaced post-merge).
- Extend the @superset/db/schema mock in task.test.ts with a
subscriptions stub; trpc.ts now imports it for getCurrentPlan and
the missing entry tripped Bun's named-export check.
* test(desktop): drop stale permission-request notification content test
Expected copy drifted from the implementation months ago; the test
was already failing on main.
* test(desktop): drop stale hotkey modifier assertion
Implementation accepts shift-only / alt-only non-F-key combos now;
the test's toBeNull() expectation drifted from reality.
* test(desktop): drop agent-preset-router test that errors on CI bun
Test-setup mock of @superset/local-db stops intercepting the package
on bun 1.3.11+; the real source re-exports PROMPT_TRANSPORTS through
a file that imports drizzle-orm/sqlite-core (unavailable under bun),
so the module load fails before any assertion runs. Covered by e2e.
* ci(deploy): wire RELAY_URL to API, DATABASE_URL to marketing + docs
RELAY_URL is required by apps/api's env schema now (automations
dispatch). Marketing + docs started pulling the Drizzle/neon schema
transitively during build; give them DATABASE_URL + the unpooled
variant and the database-status artifact in preview so next build
can connect.
* fix(automations): guard JSON.parse in run-failed, surface evaluate advance failures
- run-failed route: wrap JSON.parse for both the outer body and the
base64 sourceBody so malformed input returns 400 instead of 500.
- evaluate route: count advance-next-run failures from the allSettled
result and log them, then include the count in the response so a
persistent DB issue is observable instead of silently dropped. The
next tick still re-selects + re-enqueues (dedup absorbs).
Addresses cubic review feedback.
* feat(db): regenerate add_automations as 0035 after main 0034
main added a v2_projects migration that took the 0034 slot. Regen
from the merged schema places the automations tables/enums at 0035.
…uperset-sh#3584) Upstash's global endpoint routed us to eu-central-1 even though our prod QStash project lives in us-east-1, so schedules.list() 404'd with "user not found in this region." Pass an explicit baseUrl from a new QSTASH_URL env var so every call hits the right region. - apps/api/src/env.ts: add QSTASH_URL. - evaluate route + setup script: pass baseUrl to new Client({...}). - deploy-{preview,production} + setup-automations-schedule workflows: plumb the secret through.
…perset-sh#3581) * fix(desktop): restore terminal buffer after Unicode 11 activation The persisted xterm buffer was being replayed before the Unicode11Addon was loaded, so CJK, emoji, and ZWJ sequences got parsed with Unicode 6 cell widths. The wrong widths baked into the buffer, producing garbled glyphs on repaint — especially visible with many Claude Code tabs open and Chinese content (superset-sh#3572). Mirrors VS Code's pattern: load Unicode11Addon during terminal construction, before the first write. Also bumps @xterm/* to the versions VS Code ships (xterm 6.1.0-beta.197, webgl 0.20.0-beta.196). * docs: tighten Unicode 11 ordering comment
…d paste (superset-sh#3582) * fix(desktop): terminal paste auto-submits first line when bracketed paste is off Terminal's custom paste handler reimplemented xterm's `\r?\n → \r` normalization to enable a chunking path that turned out to be unnecessary. Without bracketed paste, the `\r` sequence makes the shell execute each pasted line as Enter, so a multi-line paste would run only the first line. Mirror VS Code's approach: delegate to `xterm.paste()` (which handles normalization + bracketed-paste wrapping correctly) and add a `shouldPasteTerminalText`-style confirmation dialog for multi-line pastes when the shell doesn't have bracketed paste mode enabled. * drop multi-line paste warning dialog, keep only paste-handler simplification * drop setupPasteHandler entirely, let xterm handle paste natively * Lint * restore minimal setupPasteHandler for Electron clipboard-event propagation * Revert "restore minimal setupPasteHandler for Electron clipboard-event propagation" This reverts commit f3fba94. * remove unused isBracketedPasteRef from useTerminalLifecycle
…ojectPickerPill styling (superset-sh#3593) The footer rendered two DevicePicker instances bound to the same draft state. Remove the right-hand duplicate and restyle the remaining one to use FormPickerTrigger so the Device/Project/Branch pickers read as one segmented control (same text size, color, icon size).
Without it, Chromium rejects https://localhost:* with ERR_CERT_AUTHORITY_INVALID on machines that never had Caddy's local root CA installed with trust flags (e.g. fresh machines, or where the prior `caddy run` sudo prompt was dismissed).
…3591) QStash rejects ":" in deduplicationId ("DeduplicationId cannot contain ':'"). Swap the separator to "_" and drop the ISO8601 string for the epoch ms so the whole key is [a-zA-Z0-9_-], which QStash accepts. Same idempotency semantics — minute-bucket uniqueness per automation.
…uperset-sh#3659) `safe-url.ts` imports `shell` from electron at the module top level, so bun's test runner can't load the file and the whole suite fails with `SyntaxError: Export named 'shell' not found`. Moves the pure URL helpers (`isSafeExternalUrl`, `externalUrlLogLabel`) to `scheme.ts` and has the test import from there. `safeOpenExternal` stays in `safe-url.ts` with the electron import; the barrel keeps the same public surface.
…h#3660) * feat(desktop): redesign v2-workspaces list as sortable Linear-style table Replace stacked card rows with a dense full-width table: Sidebar / Name / Host / Branch / Created columns that align across projects, sortable column headers, proper truncation on long workspace and host names, and hover-revealed row actions. * fix(desktop): address v2-workspaces review feedback - Sidebar column sort direction was inverted (default "desc" put non-sidebar items first); use a normal boolean comparator so descending means "in sidebar first". - Row onKeyDown was firing when Enter/Space bubbled from focused action buttons; ignore keyboard events originating from descendants. - Offline host cell regressed to a native `title` tooltip; wrap it in the shared `<Tooltip>` so the indicator is keyboard-accessible and styled consistently with the action buttons. - Extract SortableHeader into its own folder per repo conventions (one component per file) and move shared sort types into a types module.
…t-sh#3654) * feat(host-service): restore AI session title generation for v2 chat v1 generated AI titles from the first user message and persisted them via chat.updateTitle; v2 routes through host-service's ChatRuntimeManager, which never called the title generator. Wire generateAndSetTitle into host-service sendMessage/restartFromMessage so session titles populate again and sync to the v2 SessionSelector via Electric. * Revert "feat(host-service): restore AI session title generation for v2 chat" This reverts commit 495fc90. * feat(host-service): restore AI workspace naming on v2 create v1 ran the composer prompt through a small model after workspace create to replace the prompt-derived placeholder name with a concise AI title. v2 skipped that step, so workspace names stayed as the raw prompt. Wire a fire-and-forget rename into workspace-creation.create: generate the name from composer.prompt via the host-service model resolver, then call a new jwtProcedure v2Workspace.updateNameFromHost to persist it. Electric syncs the new name to the renderer — the pending/workspace UI updates in place once the model responds. * refactor(desktop): drop prompt-based name derivation from v2 create Names come from user input or a friendly random fallback — the workspace title is then AI-renamed post-create by host-service. Removes the slug-from-prompt branch preview and the prompt-as-workspace-name fallback so there's one naming path per field instead of three. * fix(host-service): address cubic review on AI workspace naming - Drop max-length to 20 to match the prompt's "20 characters or less" instruction (cubic P2). - Guard updateNameFromHost against a workspace deleted between findFirst and update — throw NOT_FOUND like the sibling update procedure (cubic P3). * refactor(desktop): stash friendly branch name on the workspace draft Previously the preview showed "" when unedited and resolveNames generated a fresh random name at submit, so the picker could say one name while a different one got created. Store the friendly name on the draft (rerolled on reset) so preview and submit stay in sync. * fix(host-service): skip AI rename when user has edited the workspace title Host-service now passes `expectedCurrentName` (the name it submitted at create time) to `updateNameFromHost`. The cloud mutation compares it to the row's current name and bails if they differ — so a user edit that lands between create and the AI response wins instead of getting clobbered. * fix: skip AI rename when user typed a custom workspace title Carry an explicit `workspaceNameWasAutoGenerated` signal from the create form through the pending row to host-service. The post-create AI rename only fires when the name came from the friendly-random fallback — a user- typed title wins. Combined with the existing `expectedCurrentName` guard on `updateNameFromHost`, user-typed names are preserved both on create and on post-create edits. Precedence: 1. user-typed title → skip AI 2. friendly fallback + prompt → AI rename 3. friendly fallback, no prompt → keep fallback * fix(trpc): make updateNameFromHost atomic Fold expectedCurrentName into the UPDATE's WHERE clause so the name comparison and the write are a single statement — no TOCTOU window where a concurrent user edit could get clobbered between the pre-check and the update. Error disambiguation (NOT_FOUND / FORBIDDEN / skipped) only runs on the unhappy path.
…t-sh#3661) * fix(desktop): toast and switch workspace when deleting in v2 Destroy can take 10–20s; showing no feedback and leaving the user on the workspace being torn down felt broken. Show a loading toast that resolves to success/error, and navigate to a sibling workspace (or home) the moment destroy kicks off instead of waiting for it to finish. * fix(desktop): simplify to fire-and-forget info toast A loading toast that persists for 10–20s and then resolves to success is noisier than the problem warrants. Just fire an info toast at start; success is already conveyed by the row disappearing. * revert: drop workspace-switch-on-delete, keep only the deleting toast Reverts the handleDeleting split and onDeleting plumbing introduced in e6837fd / 1df7960. Only change now is a single toast fired at the start of the destroy flow. * fix(desktop): navigate off workspace early on delete or hide Delete takes 10–20s; hide is immediate. In both cases we don't want to leave the user staring at a row that's being torn down or hidden. Fires `onDeleting` at the start of destroy (before teardown completes) and wraps `removeWorkspaceFromSidebar` to nav first, then remove. * fix(desktop): switch isActive to useParams so early-nav actually fires matchRoute with params + fuzzy was returning false for this item's hook when the user was viewing it, so navigateAwayIfActive no-op'd. useParams matches v1's pattern (useDeleteWorkspace) which is known to work. * fix(desktop): pick the next visible sidebar workspace directly Previous attempts threaded an onDeleting callback through dialog props and relied on the sidebar item's isActive closure, which wasn't firing. Replace all that with useNavigateAwayFromWorkspace: reads the current URL's workspaceId via useParams, reads the sidebar list from collections, picks the first sibling that isn't the one being removed. Delete and Hide both call it directly — no callback plumbing. * refactor(desktop): use onDeleting callback instead of extracted hook Drops the useNavigateAwayFromWorkspace hook. Nav logic stays inline in the sidebar item hook (where it already lived for the post-destroy path) and is shared between handleDeleting and handleRemoveFromSidebar. The dialog gets the nav via a new onDeleting callback mirroring onDeleted — same pattern, no hook extraction, zero new files. * fix(desktop): use useParams for isActive so nav actually fires matchRoute with params + fuzzy returns false for this hook's isActive check in the early-nav callback path, even when the user is viewing the workspace. useParams is what v1's useDeleteWorkspace uses and is known to work. * fix(desktop): fire onDeleting before dialog close + markDeleting Nav was never landing — dialog close and markDeleting state thrash were swallowing it. Move onDeleting to be the first thing run() does so the navigate call goes out before any other state update can interfere. * fix(desktop): restore useNavigateAwayFromWorkspace hook so nav fires The onDeleting callback route was silently dropping the navigation. The hook-extracted version was the only one that actually worked — call navigateAway(id) directly inside useDestroyDialogState and the sidebar item hook, no prop plumbing. * chore(desktop): clean stale docs + minimize diff before merge - Refresh useDestroyDialogState JSDoc: it now navigates first and fires a one-shot toast (was "run destroy silently, no toast"). - Restore the "deleteBranch preserved on optimistic close" comment that was accidentally dropped. - Revert the isActive matchRoute→useParams swap in the sidebar item hook — isActive is only used for styling now, so the change was noise.
* feat(desktop): port v1 projects + workspaces + sidebar state into v2 First-time v2 launch now migrates the user's v1 local data into v2 cloud + local stores and surfaces a branded summary modal. Covers projects (dedup via GitHub remote, link-or-create), worktrees (adopt into v2_workspaces with legacy-path support), sections (including empty ones), and sidebar ordering with v1→v2 normalization. Idempotent across runs via a new v1_migration_state table. Followups tracked: SUPER-469, SUPER-470, SUPER-471. * fix(desktop): address CodeRabbit feedback on v1→v2 migration - Scope v1_migration_state PK to (organization_id, v1_id, kind) so migrating the same v1 row under different orgs keeps independent state. Regenerated 0041 in place (pre-ship, no chained migration needed). - Reuse v1 section id as v2 section id — deterministic mapping makes the rerun guard in writeV2SidebarState actually dedup; prior crypto.randomUUID would silently duplicate sections on every rerun. - Add sr-only DialogTitle + DialogDescription to V1MigrationSummaryModal for assistive tech; keep the visual welcome header as-is. - Stable React keys in summary entry lists (include array index). - Log warning when v2Project.findByGitHubRemote returns multiple candidates so the constraint-slip case is diagnosable post-hoc.
…set-sh#3672) Forces the desktop coordinator to kill any adopted host-service older than 0.2.0 and respawn from the current app bundle. Prevents the renderer from talking to a stale host-service that's missing newly-added procedures or params — specifically the `workspaceCreation.adopt({ worktreePath })` parameter introduced in superset-sh#3670 for the v1→v2 migration. Observed symptom this prevents: a new Superset.app connects to a zombie host-service from an older Superset Canary install via the shared manifest at ~/.superset/host/<org>/manifest.json. The old service silently strips unknown zod fields, falls through to the pre-worktreePath adopt logic, and returns NOT_FOUND for every legacy-path worktree.
* fix(desktop): v2 file-open honors CMD+O editor choice v2 click-to-open-externally (FilesTab tree, DiffPane entries, terminal links, file pane header) was always hitting Cursor regardless of the editor the user picked in the v2 Open In dropdown. Root cause: the server-side `resolveDefaultEditor` only consults v1 localDb tables, so it never saw the v2 choice (which lives client-side in tanstack-db `v2SidebarProjects.defaultOpenInApp`). The hook now forwards that choice to `openFileInEditor` as an explicit `app` override. Also fixes a silent path-resolution bug that produced doubled paths like `<worktree>/apps/desktop/apps/desktop/...` when relative diff paths were opened without a cwd: `resolvePath` now throws `RelativePathWithoutCwdError` instead of falling back to Electron's `process.cwd()`, and the tRPC input field is renamed `cwd` → `worktreePath` so the intent is explicit. Extracted `useV2ProjectDefaultApp(projectId)` as the single source of truth for reading/writing the v2 preference — used by both `V2OpenInMenuButton` (write on open) and `useOpenInExternalEditor` (read on open). * docs(desktop): mention worktreePath not cwd in withResolveGuard comment * lint
…uperset-sh#3678) The branch still uses deriveBranchName({slug, title}) so the Linear/GitHub identifier stays in the branch for traceability; only the displayed workspace name switches from e.g. "SUPER-172" to the human-readable title.
superset-sh#3667) * fix(desktop): adopt Ghostty keyboard model in v2 terminal v2's terminal runtime only filtered app hotkeys. With kitty keyboard protocol enabled (needed for Shift+Enter disambiguation in claude-code, modifier reporting in neovim/helix), every Mac Cmd chord xterm saw got CSI-u encoded and leaked into TUIs as a literal char — and line-edit niceties like Cmd+Left/Right/Backspace and Option+Left/Right that v1 handles never worked at all. Mirror Ghostty's approach (src/input/key_encode.zig:534-545: "on macOS, command+keys do not encode text"): bubble every Mac Cmd chord out to the host before xterm's kitty encoder runs, then port v1's line-edit chord translators so shell navigation works the same in both renderers. Changes: - Broaden shouldBubbleClipboardShortcut's Mac branch to bubble all Cmd chords (not just Cmd+C/V with selection gating). v1 benefits too. - Port v1's line-edit translators into v2's custom key handler: Cmd+Left/Right/Backspace, Option+Left/Right, Windows Ctrl+Left/Right. Duplicates v1 for now; a follow-up can share the handler properly. - Wire shouldSelectAllShortcut into v2 so Cmd+A selects terminal buffer. - Use xterm.input(data, true) to inject translated sequences into the PTY (fires onData, forwarded by terminal-ws-transport). - Restructure tests around the new Mac rule. * fix(desktop): track kitty flags to gate Shift+Enter CSI-u injection When claude-code or codex pushes kitty progressive-enhancement flags (CSI > N u), the running program expects modified keys as CSI-u. xterm.js v6.1-beta tracks this internally but doesn't expose the active flags, so we mirror them via our own CSI handlers registered alongside xterm's built-ins (registering with return-false passes through to xterm too). With the disambiguate bit active, Shift+Enter now emits the canonical \x1b[13;2u that both claude-code and codex recognise — matching Ghostty / kitty / wezterm, which all gate CSI-u on program-initiated kitty mode (ghostty/src/input/key_encode.zig:88, kitty/key_encoding.c:153). Without kitty flags, Shift+Enter falls through to xterm.js's legacy encoding (plain \r), so bash / zsh still behave normally. Replaces the previous alt-screen heuristic, which incorrectly missed Ink-based TUIs like claude-code that render inline rather than in the alternate buffer. * chore(desktop): instrument v2 terminal keyboard path for diagnosis Add three toggleable diagnostic taps, all gated on localStorage flag `__kbdDebug=1`: - Every keydown that reaches the custom key handler: key / code / mods / current kitty flags. - Every kitty CSI push/set/pop with resulting flag state. - Every onData byte written to the PTY, shown as hex (non-printable escaped, printable verbatim) plus length and kitty flags. Second flag `__kbdDebugSkipOverride=1` temporarily disables our Shift+Enter CSI-u override so we can observe xterm.js's raw encoding. To use: in DevTools console, `localStorage.setItem('__kbdDebug', '1')` (optionally also `__kbdDebugSkipOverride`), reload the terminal pane, reproduce the bug, copy `[kbd:*]` logs. * fix(desktop): match Shift+Enter CSI-u form to active kitty flags Diagnostic trace showed xterm.js emits event-type-suffixed kitty sequences when the running program activates the report-events flag (0x02) — e.g. Escape release came through as \x1b[27;1:3u. Our override was hardcoded to \x1b[13;2u which is the right form only when disambiguate (0x01) is the sole flag; claude-code (which requests flags = 0x07) was rejecting the suffix-less form and submitting instead of newline. Inspect the flags at inject time: emit \x1b[13;2:1u (explicit press event type) when 0x02 is active, \x1b[13;2u otherwise. Also make kbdLog stringify its payload so DevTools shows the bytes inline instead of collapsing to "Object". * chore(desktop): log keyup too, in addition to keydown * chore(desktop): disable Shift+Enter override to capture xterm raw output * fix(host-service): claim TERM_PROGRAM=kitty so TUIs parse our CSI-u Diagnostic trace of v2 terminal Shift+Enter proved xterm.js v6 with kittyKeyboard is emitting the correct kitty-protocol bytes (\x1b[13;2u press + \x1b[13;2:3u release, same as Ghostty). Yet Shift+Enter still submits in claude-code — strings-dumped the claude-code binary and found the actual gate: Ix_ = { ghostty: "Ghostty", kitty: "Kitty", "iTerm.app": "iTerm2", WezTerm: "WezTerm", WarpTerminal: "Warp" } Its CSI-u parser keys off TERM_PROGRAM being in that allowlist. With our previous "Superset" value it ignored the bytes entirely and fell through to Node readline's legacy keypress handling, where Shift+Enter looks like plain Enter and submits. Claim "kitty" — the protocol origin. Fewer downstream version-gated branches than "ghostty" (which claude-code version-checks against 1.2.0) and safer than "iTerm.app" (color-depth gated on version <3.x). TERM_PROGRAM_VERSION stays as our host-service version; programs that care about real kitty version will hit our version string and probably fall back to conservative behaviour. * chore(desktop): remove diagnostic instrumentation and abandoned override code Root cause for claude-code Shift+Enter landed in host-service env.ts (TERM_PROGRAM=kitty). The renderer-side machinery we added while diagnosing is no longer needed: - kbdLog / kbdHex / kbdDebugSkipOverride console tap - createKittyFlagTracker (watched CSI > u pushes to gate the override) - KITTY_FLAG_DISAMBIGUATE / KITTY_FLAG_REPORT_EVENTS constants - shiftEnterCsiU helper - terminal.onData logging tap v2 terminal-runtime.ts is back to a simple createKeyEventHandler calling resolveHotkeyFromEvent → line-edit translators → select-all → clipboard bubble → xterm default. Shift+Enter now goes through xterm's native kitty encoder and claude-code parses it correctly because TERM_PROGRAM is in its allowlist. Net -142 LOC from renderer. * experiment(desktop): narrow Mac Cmd bubble back to VS Code-style Hypothesis: with TERM_PROGRAM=kitty claiming kitty in the claude-code / codex allowlist, kitty-aware TUIs parse CSI-u Cmd+chords correctly and don't show literal char garbage. The broader Ghostty-style "bubble every Mac Cmd chord" may have been overkill — needed only for TUIs that don't speak CSI-u. Revert the Mac branch of shouldBubbleClipboardShortcut to the original narrow rule (Cmd+V + Cmd+C-with-selection) to test. If kitty-aware TUIs stay clean AND non-kitty-aware ones (vim base, less, htop, tmux-without- kitty) are tolerable, we can ship simpler. Line-edit translators and Cmd+A select-all stay — those aren't kitty-specific, they're Mac-convention handling the shell expects regardless of kitty. * experiment(desktop): revert all renderer changes, keep only TERM_PROGRAM=kitty Aggressive minimum — strip terminal-runtime.ts and clipboardShortcuts.ts back to main. The only net change from main becomes: env.TERM_PROGRAM = "kitty" (was "Superset") If claude-code's CSI-u parsing handles Cmd+C/V/Left/Right correctly with TERM_PROGRAM now in the allowlist, and shell fallback is acceptable for the rest, this is the true minimum fix. Things that may regress and be worth checking: - Cmd+Left / Cmd+Right / Cmd+Backspace in shell (no translator → xterm sends CSI-u → bash readline ignores → no cursor navigation) - Option+Left / Option+Right (same, word navigation won't work in shell) - Cmd+A in shell (no select-all handler → CSI-u to PTY) - Cmd+C in vim / less / htop (no broad bubble → CSI-u reaches PTY; if those TUIs don't speak kitty they'll show garbage) If any of those bite, we restore the specific piece(s). * chore(host-service): trim TERM_PROGRAM comment to essentials
Addresses GHSA-w5hq-g745-h8pq (Dependabot #29): uuid < 14.0.0 is missing buffer bounds checks in v3/v5/v6 when a caller-provided buffer is passed, allowing silent partial writes. Our usage is limited to v4()/validate/version with no caller buffers, so there's no direct exposure, but bumping to 14.0.0 clears the alert.
…e dialog (superset-sh#3681) * fix(desktop): persist "also delete local branch" checkbox in v2 delete dialog Wire the v2 delete-workspace dialog into the existing `settings.getDeleteLocalBranch` / `setDeleteLocalBranch` tRPC pair (already used by v1 and the Settings → Git page) so the user's last choice is remembered across deletes instead of always defaulting to unchecked. * fix(desktop): persist v2 delete-branch choice via v2UserPreferences; always force - Move the v2 delete dialog's "Also delete local branch" opt-in off the v1 tRPC settings singleton and onto `v2UserPreferences` — the same collection used for rightSidebar/link-tier prefs. The choice is now persisted the instant the checkbox toggles (optimistic update via @tanstack/db) and shared across every workspace's delete dialog, not per-instance. - Drop the per-hook `deleteBranchOverride` state; read straight from the singleton preferences row. - In host-service workspace-cleanup, always use `git branch -D` when `deleteBranch` is on — the checkbox is the user's consent, so silently refusing unmerged branches (the old `-d`/`-D` gate on `force`) just dropped the opt-in and produced confusing warnings. * chore(desktop): drop redundant setDeleteBranch wrapper in destroy hook Rename destructured `setDeleteLocalBranch` → `setDeleteBranch` at the call site instead of wrapping it in a useCallback that did nothing. Also trim a docstring line that's trivially true now that the opt-in is a global singleton.
…sh#3683) * feat(desktop): v2 Changes file list shift/cmd-click policy Mirror the v2 Files tab click policy on the Changes sidebar: plain click reuses an existing diff pane (or smart-places into the active tab), shift+click opens the diff in a new tab, and cmd/ctrl+click opens the file in the external editor. * refactor(desktop): centralize v2 sidebar modifier-click intent Extract the meta/ctrl -> editor, shift -> new tab, else -> select dispatch into a shared getSidebarClickIntent() helper so the Files tab tree item and the Changes file row resolve modifiers the same way. * refactor(desktop): use path aliases for deep-relative imports * feat(desktop): right-click menu + hover tooltip for v2 sidebar items Surface the click/shift-click/cmd-click actions on v2 file and diff rows so they stay discoverable: add a hover tooltip that lists the modifier shortcuts, and a right-click menu with Open / Open in New Tab / Open in Editor (with shortcut hints) plus the existing path actions. Changes rows get the menu for the first time; Files rows gain explicit Open / Open in New Tab / Open in Editor entries in place of the unwired "Open to the Side" placeholder. * fix(desktop): nest Tooltip inside ContextMenuTrigger so right-click works * fix(desktop): gate cmd-click to files only in v2 Files sidebar
…rset-sh#3691) Remove the feature card from the Product nav dropdown; show $20 struck through next to $15 on the Pro pricing card when Yearly is selected to make the discount explicit. Pin the price row height so toggling between Monthly and Yearly doesn't cause a vertical shift.
…lete prompt (superset-sh#3688) The v2 workspace delete flow showed two warnings: a generic confirm pane, then a "Uncommitted changes in worktree" pane after destroy hit a conflict. Surface the v1 yellow banner inline on the confirm pane via the existing canDelete preflight, force when warnings are shown, and silently retry on the rare race so the user only ever sees one prompt.
…uperset-sh#3679) * fix(desktop): fail closed when adopted host-service has no version The version gate only killed the host when fetchHostVersion returned a string less than MIN_HOST_SERVICE_VERSION. If the host lacked the host.info route entirely (older releases), the helper returned null and we silently adopted it, producing 404s on project.findByPath and other new routes. Flip the guard to fail-closed: any host that can't prove it meets the minimum version is killed and the manifest removed so the next spawn brings up a compatible service. * fix(desktop): compare host-service versions numerically String comparison made "0.10.0" < "0.2.0" evaluate to true, which would have killed any future double-digit minor version. Split the version on dots and compare segments as integers. Also split the adopt-killing log into two branches so the null case reads "version unknown" instead of "version unknown < 0.2.0". * refactor(desktop): use semver library for host-service version check Replace the hand-rolled split-and-compare with `semver.satisfies`, which already handles malformed input by returning false. The `semver` package is already a dependency. * refactor(desktop): inline host-service version check
…switches (superset-sh#3687) * fix(desktop): keep v2 terminals stable across workspace switches Problem: every v2-workspace switch yanked the xterm wrapper out of the DOM and re-opened a WebSocket, producing a visible "switching and reattaching" flash instead of VSCode-style hide/show. The v2-workspace layout's WorkspaceTrpcProvider has a load-bearing key that unmounts the whole subtree on every switch, so the React component for TerminalPane goes away — but the xterm instance, wrapper div, and transport in terminalRuntimeRegistry should survive, and previously the DOM node didn't. Three entangled issues were fixed together: 1. Parking container. On detach, the wrapper used to be wrapper.remove()'d, taking the rendered canvases with it. Now it's appended to a hidden body-level div (#v2-terminal-parking) so xterm stays attached to the document. Re-mount in the new workspace is a DOM move back from parking to the live container — mirrors the existing v1 persistent-webview pattern at usePersistentWebview.ts:14-27 and VSCode's TerminalInstance setVisible model. 2. DOM vs transport split. The previous single registry.attach() both mounted DOM and opened the WebSocket. That forced TerminalPane to gate attach on ensureSession, which made warm returns wait on a tRPC round-trip (visible delay) and made cold mounts race — opening a WS before the server session existed produced "Session not found. Call terminal. ensureSession first.". Split into mount() (synchronous, DOM-only, safe on every mount) and connect() (called only after ensureSession resolves). Matches VSCode's TerminalInstance.attachToElement + _createProcess and Tabby's XTermFrontend.attach + setSession. 3. Effect dep narrowing. TerminalPane's attach effect used to depend on [terminalId, websocketUrl, initialThemeType, workspaceId]; prop churn during the provider key remount (workspaceId flipping while pane data caught up) forced repeated detach/attach cycles. Narrowed to [terminalId] with the others read through refs. websocketUrl changes now go through registry.reconnect(), which is hard-gated on transport already being live so it never opens a WS before ensureSession has resolved. Log walkthrough on a warm workspace switch: pane:effect-cleanup → registry:detach → runtime:detach (wrapper parks) pane:effect-mount → registry:mount → runtime:attach wasParked:true registry:reconnect-skip same-url pane:ensureSession-ok → registry:connect → transport:connect-skip idempotent Instrumentation stays in this commit for the rollout check; a follow-up strips the termLog calls once this is confirmed in production. * chore(desktop): strip v2 terminal lifecycle instrumentation Removes the temporary termLog tracing added to diagnose the workspace-switch reattach bug. The behavior fix (parking + mount/connect split) landed in the previous commit and is confirmed stable in logs; no functional change here. * fix(desktop): keep v2 browser panes stable across workspace switches Browser webviews were being destroyed on workspace switch, discarding guest-page state (URL, scroll, history) even though the registry was designed to persist them. Root cause: usePaneRegistry wired browser destruction through the Panes library's onRemoved hook: onRemoved: (pane) => browserRuntimeRegistry.destroy(pane.id) Under ideal conditions the v2 layout's `key={`${workspace.id}:${hostUrl}`}` remounts the WorkspaceTrpcProvider subtree on every switch, so each workspace gets its own Workspace component whose previous-panes diff never observes a cross-workspace "removal". But the remount isn't always prompt — layout.tsx's useLiveQuery can return stale WS-A data for a tick while page.tsx's already flipped to WS-B. During that tick the existing WorkspaceContent stays mounted, useV2WorkspacePaneLayout calls store.replaceState(WS-B panes) on the same store instance, and the Panes diff correctly sees "WS-A's browser is gone" → fires onRemoved → destroys the webview. By the time the user returns, attach() runs the cold createEntry path and the guest page is lost. Terminals don't hit this because destruction goes through useGlobalTerminalLifecycle, a global sweep against every workspace's persisted paneLayout — cross-workspace "removal" isn't a removal from the sweep's perspective. Fix: mirror the terminal pattern exactly. - Added useGlobalBrowserLifecycle under _authenticated/components/GlobalBrowserLifecycle/, following the same shape as useGlobalTerminalLifecycle (extract pane.ids from all workspace layouts, diff against previous, 500 ms grace delay destroy to tolerate cross-workspace pane moves). - Mounted <GlobalBrowserLifecycle /> alongside <GlobalTerminalLifecycle /> in _authenticated/layout.tsx. - Removed the onRemoved wiring from usePaneRegistry.tsx — the sweep replaces it. Verified with instrumentation that a workspace switch on a live browser pane no longer reaches browserRuntimeRegistry.destroy, and that closing a browser pane still destroys after the 500 ms grace. Followup to PR superset-sh#3687 (terminal-side fix landed earlier on this branch). Plan doc at apps/desktop/plans/20260423-1226-v2-pane-persistence-across-workspace-switch.md captures the full root-cause analysis for both runtimes as a reference for future pane persistence work. Also removes a now-redundant biome-ignore comment in TerminalPane that biome flagged as having no effect after the dep narrowing in the earlier commit. * fix(desktop): mark parked terminal container inert; clarify reconnect doc Two follow-ups from PR superset-sh#3687 review: - `inert` on #v2-terminal-parking. Parked terminals' internal <textarea> still had `tabindex=0`, so a keyboard user tabbing through the app could land in an off-screen terminal and have keystrokes silently go to the wrong pane. `inert` removes the subtree from the tab order and the accessibility tree, and moves focus out automatically, which also handles the "blur before park" concern for free. aria-hidden added for belt-and-suspenders on older engines. - `reconnect` JSDoc. Clarify that the guard only skips `"disconnected"` (never-opened transport, caller should use ensureSession + connect path). `"connecting"`, `"open"`, `"closed"` are all intentionally allowed through — `connect()` aborts any in-flight or stale socket before opening the new one. * chore(desktop): deslop v2 terminal pane effect and detach comment - Collapse TerminalPane's .then()/.catch() into .catch()/.finally(): one connect() call site instead of two identical guarded calls. Same semantics (connect after ensureSession settles, even on rejection), cancellation check consolidated. - Merge the three connect-related comments into one block explaining "connect regardless of outcome" + idempotency. Drop the redundant "// DOM first" inline that the block comment above already covered. - Trim detachFromContainer's 5-line comment down to 2 lines pointing at getParkingContainer — the helper's docstring already explains the parking rationale in full. * perf(desktop): memoize v2 TerminalPane useSyncExternalStore args The module-level `subscribeToState(terminalId)` built a fresh closure on every render, so `useSyncExternalStore` saw a new subscribe function each time and re-subscribed to `terminalRuntimeRegistry.onStateChange` on every TerminalPane render — this is the anti-pattern React's useSyncExternalStore docs explicitly warn about ("If you don't memoize the subscribe function, React will resubscribe to your store every time your component re-renders"). Inline the helpers and wrap both subscribe + getSnapshot in useCallback keyed on [terminalId]. Re-subscribe now only fires when the pane's terminalId actually changes (cold create / destroy), not on every keystroke-triggered re-render.
…ojectId> (superset-sh#3669) * fix(host-service): place worktrees under ~/.superset/worktrees/<projectId> Mirror v1 desktop's convention of keeping worktrees outside the primary checkout tree, and match v2's existing ~/.superset/repos/<projectId> layout for symmetry. Detection switches to the local `workspaces` table (plus the new root for orphan adoption), so legacy worktrees at <repo>/.worktrees/ keep working without a migration. * chore(host-service): trim worktree-path comments * lint
…er (superset-sh#3692) * feat(desktop): v2 AI workspace rename generates title + branch together Post-create rename now makes a single structured-output call returning { title, branchName }, applies the title to v2_workspaces.name, and also renames the git branch (git branch -m in the worktree + updates the host-local workspaces.branch + cloud v2_workspaces.branch). Replaces the naive 20-char slice that was producing mid-word truncations like "New v2 workspaces na". Branch-prefix support for v2 tracked in SUPER-478. * fix(desktop): roll back git rename if cloud write fails; coerce oversized AI names Addresses two review findings on v2 AI rename: - Partial-commit: previously `git branch -m` + host-local `workspaces.branch` were written before the cloud mutate, so a cloud failure left git + local in the new state and the cloud (and the web app) stuck on the old branch. Now we git-rename first, push name+branch to cloud, and only update host-local on cloud success; on cloud throw we git-rename back to the old name. - `.max()` on the zod schema rejected any overlong model output, silently no-op'ing the whole rename. Replaced with `.transform()` pipes that trim the title and sanitize the branch so overshoots are coerced instead of dropped. * fix(desktop): loosen AI workspace name bounds, fall back branch to title slug - Title cap bumped 40 → 150 and drops `.min(1)` — basically trust the small model with `.describe()` guidance. - Branch falls back to a slug of the title when the model's branchName sanitizes to nothing (e.g. all emoji), so branch rename stops being a failure point. * chore(desktop): drop title-slug branch fallback in AI workspace names Per-field gating in the caller already skips the branch rename when branchName is empty; no need to salvage. If the model's output is bad, skip the rename. * refactor(desktop): drop structured-generation wrapper, call Agent.generate directly The wrapper in packages/chat was pure indirection — chat already has @mastra/core as a direct dep. Removed it, added @mastra/core to host-service, and call agent.generate({ structuredOutput }) directly from ai-workspace-names. Also tightened getSmallModel's return type to MastraModelConfig | null so callers don't need casts. * refactor(desktop): hoist AI rename orchestration out of create handler Pulls the ~80-line post-create block out of workspaceCreation.create into applyAiWorkspaceRename in ai-workspace-names.ts — same file as the generator so naming + applying live together. listBranchNames moves to its own util so both callers can share it. Call site in the create handler is now a three-line fire-and-forget.
) * feat(desktop): show PR state as sidebar workspace icon Replace the host-type icon in the v2 dashboard sidebar with a PR state icon (open/merged/closed/draft), colored by state, when a workspace has an associated pull request. Clicking the icon opens the PR on GitHub. Drop the now-redundant bottom-right PR badge, and make the diff stats only colorize when the workspace is active. * fix(desktop): address PR review comments on sidebar PR icon - Stop keydown propagation on the PR icon button so keyboard activation (Enter/Space) does not also trigger the parent row's workspace click. - Use hover:bg-foreground/10 on the PR icon button so hover is visible even when the row is active (which already has bg-muted). - Humanize PR state in the icon tooltip (Open/Merged/Closed/Draft). - Drop the stale rounded class from the diff-stats container now that its background is gone. - Use LuGitPullRequestClosed instead of LuCircleDot for closed PRs. * chore: lint
…set-sh#3695) * fix(desktop): use Alerter for automation detail delete confirm Closes SUPER-436. Replaces native window.confirm() on the automation detail page with the shared alert() helper so the confirm matches the rest of the app's UI. * fix(desktop): surface delete errors via toast.promise Addresses review feedback on superset-sh#3695: the Alerter would silently swallow a failed delete. Wrap mutateAsync in toast.promise so the user sees loading, success, and error states.
…sh#3699) * fix(desktop): honor agent selection in new-workspace modal The modal's agent picker was cosmetic — `selectedAgent` was persisted to localStorage for the UI but never written to the pending row, so the pending page's `buildForkAgentLaunch` always called `getFallbackAgentId` (which hard-prefers claude). Selecting codex, cursor, etc. silently launched claude instead. Thread the selected agent through the pending row into the launch build; "none" now genuinely skips the agent launch (no silent claude substitution). * test(desktop): update buildForkAgentLaunch tests for explicit agent selection Tests were written against the old fallback-to-claude behavior — they passed `agentId: null` and expected Claude to launch. Under the new contract, null is treated as "no selection → no launch". Pass explicit `agentId` in each case. Add coverage for the new null / "none" paths.
…sh#3701) * fix(host-service): count untracked file lines in getStatus (SUPER-472) Untracked files were pushed into `unstaged` with `additions: 0`, so the sidebar LOC delta missed every newly-added file in the working tree (`useDiffStats` sums against-base + staged + unstaged). * feat(host-service): detect renames/copies on untracked files Run git's real rename/copy detection over the working tree by copying .git/index to a temp file, marking untracked files intent-to-add against that copy, and running `git diff -M -C`. Real index is never mutated. Merges matched deleted+untracked pairs into single rename entries with correct additions/deletions, and renders `old → new` in the v2 sidebar file row. Catches: - mv tracked → untracked (rename) - cp tracked → untracked (copy, sourced from the tracked blob) - mv with edits (rename with non-zero stats) Falls back silently to the unmerged deleted+untracked listing on any error (missing index, copy failure, intent-to-add reject). * fix(host-service): address review on getStatus rename/LOC - Cap parallel file I/O at 64 workers in countUntrackedFileLines so workspaces with thousands of untracked files don't EMFILE - Sniff first 8KB for NUL bytes before counting lines so binary files under the 1MB cap (PNGs, lockfiles, compiled artifacts) don't get a meaningless utf-8 line count - Log temp-dir cleanup failures instead of silently swallowing - Add -M -C to staged numstat and propagate file.from as oldPath so staged renames collapse to a single 0/0 entry (matches the working- tree rename detection added in the previous commit)
…uperset-sh#3698) Dev setup writes SUPERSET_HOME_DIR=<worktree>/superset-dev-data (no leading dot), which didn't match the managed-hook regex, so stale notify.sh entries from deleted dev worktrees stuck around in ~/.codex/hooks.json. Widen the pattern to also recognize `superset-dev-data/` and add a regression test.
…h#3700) * fix(desktop): adopt Ghostty keyboard model in v2 terminal v2's terminal runtime only filtered app hotkeys. With kitty keyboard protocol enabled (needed for Shift+Enter disambiguation in claude-code, modifier reporting in neovim/helix), every Mac Cmd chord xterm saw got CSI-u encoded and leaked into TUIs as a literal char — and line-edit niceties like Cmd+Left/Right/Backspace and Option+Left/Right that v1 handles never worked at all. Mirror Ghostty's approach (src/input/key_encode.zig:534-545: "on macOS, command+keys do not encode text"): bubble every Mac Cmd chord out to the host before xterm's kitty encoder runs, then port v1's line-edit chord translators so shell navigation works the same in both renderers. Changes: - Broaden shouldBubbleClipboardShortcut's Mac branch to bubble all Cmd chords (not just Cmd+C/V with selection gating). v1 benefits too. - Port v1's line-edit translators into v2's custom key handler: Cmd+Left/Right/Backspace, Option+Left/Right, Windows Ctrl+Left/Right. Duplicates v1 for now; a follow-up can share the handler properly. - Wire shouldSelectAllShortcut into v2 so Cmd+A selects terminal buffer. - Use xterm.input(data, true) to inject translated sequences into the PTY (fires onData, forwarded by terminal-ws-transport). - Restructure tests around the new Mac rule. * fix(desktop): preventDefault on bubbled clipboard chords Matches VS Code (terminalInstance.ts:1116-1175) and Tabby (xtermFrontend.ts:199-214): call `event.preventDefault()` before returning false from the custom key handler so the browser's default action can't double-fire alongside the Electron `role: 'paste'`/`'copy'` accelerator registered in src/main/lib/menu.ts. Paste still flows — webContents.paste() dispatches the paste event on the focused textarea independently of the DOM keydown default, and xterm's paste listener picks it up. Keydown-only — preventDefault on keyup is a no-op and would suppress kitty protocol release events we still want xterm to handle. * refactor(desktop): collapse translateLineEditChord to a grouped form Seven near-identical 8-line blocks collapsed to a platform+modifier grouped structure with a tiny onlyMod helper. Same behavior, ~55 fewer lines, adding a new chord is one line instead of ten. * revert: don't preventDefault on bubbled clipboard chords Broke Cmd+C/V. On Electron macOS, the browser's keydown → paste-command pipeline is what dispatches the paste event on xterm's textarea, and preventDefault blocks that pipeline. VS Code and Tabby can preventDefault because they implement paste themselves (command system / ClipboardAddon); we rely on xterm's built-in paste listener, so the default must run. Revert of 36cf276.
計画 PR #398〜#414 で 48+ commits を段階的に cherry-pick で取り込み済みだが、 conflict 解消で patch-id が変わるため GitHub UI の "behind" count が 減らない git の制約がある。 本 merge commit は `-s ours` で fork 現状の内容を一切変更せず、upstream/main の 9268654 (superset-sh#3700 Ghostty keyboard model) までを merge-base に入れることで "behind" count を減らす。 fork 側の実内容は全て維持 (PR #398〜#414 の成果物)。upstream 履歴は単に ancestor として記録されるだけ。 唯一の残 upstream commit superset-sh#3697 (split workspace-creation router) は fork の独自拡張 (baseBranchSource / PR checkout / fork note / applyAiWorkspaceRename) と 全面衝突する大規模 refactor のため本 merge に含めず、Issue #415 で追跡。 Merged upstream SHA: 9268654 (superset-sh#3700 adopt Ghostty keyboard model) Tracking issue: #415
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
計画 PR #398〜#414 で upstream 48+ commits を段階的に cherry-pick で取り込み済みですが、conflict 解消で patch-id が変わるため GitHub UI の "behind" カウントが減らない git の制約があります。
本 PR は
git merge -s ours upstream/main~1(upstream9268654e3 #3700まで) で fork 現状の内容を一切変更せず、upstream history を fork main の ancestor に入れて behind カウントを 1 まで減らします。変更内容
9268654e3 fix(desktop): adopt Ghostty keyboard model in v2 terminal (#3700)まで残る 1 commit
167542eb4 [codex] refactor(host-service): split workspace-creation router (#3697)は fork 独自拡張 (baseBranchSource / PR checkout / fork note / applyAiWorkspaceRename) と全面衝突する大規模 refactor のため、本 merge に含めません。詳細は Issue #415 で追跡:
#415
検証済み項目
git diff origin/main HEAD --statがゼロ (fork 実内容完全維持)git cat-file -p HEADで 2 parents (7f535f0 fork main + 9268654 upstream)影響
関連
plans/20260424-upstream-merge-strategy.mdproject_upstream_batch_workflow.md