feat(desktop): YouTubeインポートUXを全面改善#3533
Closed
MocA-Love wants to merge 1022 commits intosuperset-sh:mainfrom
Closed
feat(desktop): YouTubeインポートUXを全面改善#3533MocA-Love wants to merge 1022 commits intosuperset-sh:mainfrom
MocA-Love wants to merge 1022 commits intosuperset-sh:mainfrom
Conversation
…perset-sh#3461) * chore(desktop): auto-restart host-service on bundle change in dev Watches the built host-service.js in NODE_ENV=development and restarts running instances via the coordinator when electron-vite rewrites the bundle. Fast edit→reload loop for packages/host-service and src/main/host-service without restarting Electron. Not true HMR — in-memory host-service state (PTYs, watchers, chat streams) is torn down on each reload. * fix(desktop): wait for host-service bundle to stabilize before reload Rollup rewrites host-service.js via unlink+rewrite, so the fs.watch fires while the file is missing or partially written. The coordinator now polls statSync until size is non-zero and stable for 150ms (5s deadline) before calling restartAll, avoiding MODULE_NOT_FOUND on respawn.
Two adjustments on top of the cherry-picked upstream commits: 1. apps/desktop/src/main/index.ts — upstream superset-sh#3461 uses getHostServiceCoordinator() at the dev-reload call site, but fork imports that symbol as getHostServiceManager to minimize diff with the quit-lifecycle code that still uses the legacy name. Rewrite the call to use the local alias so the desktop main process still builds. 2. apps/desktop/src/renderer/routes/_authenticated/settings/layout.tsx — upstream superset-sh#3466 uses [data-state="open"] to suppress Escape when a Radix overlay is open, but that selector also matches Radix Collapsible (settings/agents AgentCard uses Collapsible with data-state="open"), so expanding any card silently disabled the new Escape-to-close behavior. Narrow the selector to role-based overlay elements only (dialog, alertdialog, menu, listbox).
Address Codex review on #176: the role-based selector missed Radix Popover/HoverCard (e.g. FontFamilyCombobox, ProjectTargetingField), so Escape inside an open popover could navigate away from Settings instead of dismissing the popover. Radix Popper renders popover/hovercard content inside a wrapper with `data-radix-popper-content-wrapper`, which Collapsible (the original regression trigger) never uses. Add a descendant selector for that wrapper to cover popper-based overlays without re-catching Collapsible.
Address CodeRabbit review on #176: closing Settings via Escape with plain navigate() pushes a new history entry so the browser back button re-enters Settings instead of returning to whatever preceded it. Switch to navigate({ replace: true }) — the originRoute is already stored globally, so replacing /settings with it is the behavior users expect.
…ndle chore(upstream): PR1 低リスク修正5件バンドル (superset-sh#3457 superset-sh#3464 superset-sh#3462 superset-sh#3466 superset-sh#3461)
…h#3459) MCP clients on spec 2025-06-18 send resource=<mcp-url> in authorize/token requests. better-auth 1.5.6 validates that against validAudiences and our allowlist only contained the bare API origin, so tokens were rejected with "requested resource invalid". Add the MCP endpoint to validAudiences and to the JWT verifier's audience list.
…et-sh#3458) Replace the 5s tray polling interval with event-driven refresh: `mouse-enter` + host-service status-changed trigger an async rebuild. Drop the hostInfoCache Map — updateTrayMenu is now async and fetches host.info inline with a 2s AbortController timeout, so there is no stale state to manage. Unwrap the superjson envelope (`result.data.json`) that the original polling code missed, which was causing every org to render as a UUID slice (previously) or "Loading…" (after the first pass of this fix). Closes superset-sh#3454
Address Codex review on #177: updateTrayMenu is triggered by both status-changed and mouse-enter and awaits fetchHostInfo in between, so an older invocation can finish after a newer one and overwrite the tray state with a stale orgIds snapshot. In practice that could reintroduce a stopped service's submenu entries and flip the Quit menu between single- and dual-mode variants. Introduce a monotonic trayUpdateToken bumped on entry; drop the result if trayUpdateToken has advanced by the time fetchHostInfo resolves.
chore(upstream): PR2 MCP OAuth audience + tray menu refactor (superset-sh#3459 superset-sh#3458)
* Delete
* plans(workspace-delete): refine design per review
- Drop unsupported claim about v2 branch ephemerality; deleteBranch
defaults off, no persisted preference
- Add Host ownership section: v2Workspaces.hostId is 1:1 so destroy
is host-local; FORBIDDEN from other hosts
- Replace "remote-host cleanup" with "abandoned-host cleanup" as the
real follow-up (cross-device delete isn't a thing under the schema)
* feat(host-service): workspaceCleanup.destroy
Adds a single unified delete path for v2 workspaces. Sequences:
terminals → teardown → worktree → branch → cloud → host sqlite.
- runTeardown silently executes .superset/teardown.sh with a 60s
timeout, SIGKILL on timeout, 4KB output tail capture; returns
status + exitCode + tail.
- disposeSessionsByWorkspaceId kills all active PTYs for a workspace
(releases file locks before worktree remove / teardown).
- Typed errors: CONFLICT for dirty worktrees (prompt force: true),
INTERNAL_SERVER_ERROR with cause.kind=TEARDOWN_FAILED surfaced to
the client via errorFormatter so the renderer can show outputTail.
- deleteBranch opts in (default false); force also upgrades
`git branch -d` to `-D`.
- Cloud failure is swallowed as a warning — disk is already clean,
cloud self-heals on next sync.
* feat(desktop): wire v2 sidebar delete through workspaceCleanup.destroy
- useDestroyWorkspace hook resolves the workspace's host-service URL
and calls workspaceCleanup.destroy, normalizing TRPC errors into a
typed union (conflict / teardown-failed / unknown) for the dialog.
- DashboardSidebarDeleteDialog becomes self-contained: owns the
mutation, the "delete local branch" checkbox (off by default), and
renders the force-retry UI for dirty worktrees and teardown
failures (with outputTail preview).
- useDashboardSidebarWorkspaceItemActions drops the direct
apiTrpcClient.v2Workspace.delete call; post-success cleanup moves
to an onDeleted callback (sidebar removal + focus navigation).
Path B (renderer → cloud v2Workspace.delete) is now unused in desktop.
* fix(workspace-delete): review follow-ups
- Run teardown script via createTerminalSessionInternal (same PTY
primitive v2 uses for interactive terminals) so the script inherits
the user's shell environment — login rcfiles, PATH, nvm/rbenv, etc.
Silent: session is transient and never surfaced as a visible pane.
Output captured via pty.onData; timeout via pty.kill() so behavior
is cross-platform (no process-group SIGKILL on Windows).
- Guard the timeout callback with `if (settled) return;` to prevent
the event-loop race where a successful 60s exit is misreported as
a timeout.
- disposeSession now always marks the DB row disposed even when the
in-memory session is missing, so zombie `active` rows left from
crashes get reconciled during bulk workspace cleanup.
- Export TEARDOWN_TIMEOUT_MS from host-service; renderer uses it in
the dialog copy instead of a literal `60`. Renderer also runs the
output tail through strip-ansi (host-service returns raw PTY bytes;
sanitizing is a presentation concern).
- TeardownFailureCause.signal is now `number | null` (Unix signal
number from node-pty) rather than a bogus NodeJS.Signals cast.
- JSDoc: INTERNAL_ERROR → INTERNAL_SERVER_ERROR on the workspace-
cleanup error contract.
- Tag the audit doc's ASCII-diagram fence as `text` for markdownlint.
- biome lint:fix.
* Refactor
* fix(workspace-delete): optimistic-close UX (no in-dialog wait)
Mirrors v1's pattern: the destroy runs in the background under a
toast.loading → success/error, the dialog closes immediately on
confirm, and only re-opens when the user has a decision to make.
- Confirm/force-retry close the dialog optimistically; mutation
continues with a toast for feedback. No more frozen "Deleting..."
for the up-to-60s teardown duration.
- CONFLICT and TEARDOWN_FAILED reopen the dialog in the matching
error pane so the user can force-retry with full context. The
branch opt-in is preserved across the reopen.
- Unknown errors fall through to toast.error — no reopen.
- isPending state and "Deleting..." button copy dropped from all
panes (never visible since the dialog closes optimistically).
* fix(workspace-delete): move TEARDOWN_TIMEOUT_MS to @superset/shared
The renderer used to value-import TEARDOWN_TIMEOUT_MS from
@superset/host-service, which dragged node-pty (and transitively
@parcel/watcher's native .node binary) into the renderer bundle
— esbuild had no loader for .node and failed the build.
@superset/shared is renderer-safe. Put the constant there so both
host-service and the renderer import the same single source of
truth without crossing the bundler boundary.
* fix(workspace-delete): auth + tolerate missing host-sqlite row
Two bugs unblocking the v2 delete flow:
1. v2Workspace.delete cloud procedure used protectedProcedure
(session), so host-service's JWT auth returned 401. Switch to
jwtProcedure (mirrors v2Workspace.create) with an org-membership
check derived from the workspace's organizationId. Returns
alreadyGone: true idempotently when the row is missing.
2. workspaceCleanup.destroy threw NOT_FOUND when the host-sqlite
row was missing, even though the workspace exists in the cloud
and belongs to this host. That state happens when the workspace
was created via a flow that didn't register it locally or the
host DB was reset — the user has no way to delete without this
fix. Now each disk step is gated on having the local row + a
resolvable project; missing rows surface as warnings and cloud
cleanup still proceeds.
Also makes ctx.api absence a warning instead of an upfront
PRECONDITION_FAILED, so local-only cleanup still completes.
* plans(workspace-delete): cloud-as-commit-point redesign
Reshape destroy as a linear preflight → teardown → cloud → local-
cleanup saga. No tombstones, no reconciler, no persistent state.
- Any failure before the cloud step leaves the workspace untouched.
- Any failure after is a warning (local orphans are cheap).
- Force skips preflight + teardown; cleanup is always --force past
the commit point.
- Three phases cleanly separated so future changes (auto-retry,
cross-device reconcile) land at a single seam.
Also updates the teardown contract to reflect the PTY-via-
createTerminalSessionInternal approach (env parity w/ v2 setup;
cross-platform timeout via pty.kill) and pins TEARDOWN_TIMEOUT_MS
to @superset/shared so the renderer doesn't drag node-pty into
its bundle.
* fix(workspace-delete): cloud is the commit point
Reorders workspaceCleanup.destroy into three phases so the failure
semantics match the plan doc:
0. Preflight (dirty worktree) — throws CONFLICT if !force
1. Teardown script — throws TEARDOWN_FAILED if !force
2. Cloud delete ← commit point; throws passthrough on failure
3. Local cleanup (PTYs, worktree, branch, host sqlite) — warnings only
Any failure in phases 0–2 leaves the workspace fully intact. The
user retries when they've addressed the cause (committed work, fixed
teardown, reconnected cloud, re-authed). Phase 3 is best-effort; local
orphans are cheap and surface as warnings.
No tombstones, no reconciler, no persistent state. Step 3b always
uses --force since we're past the commit point regardless of the
input flag.
* fix(workspace-delete): preserve TeardownFailureCause fields over wire
tRPC's `new TRPCError({ cause })` runs non-Error causes through
getCauseFromUnknown() which wraps them in a synthetic
UnknownCauseError (Error subclass) while copying fields as own
properties. Superjson's transformer recognises it as Error and
serialises only { name, message, stack } — our { kind, exitCode,
signal, timedOut, outputTail } fields were being dropped on the wire,
so the renderer saw an Error with no teardown metadata and
TeardownFailedPane crashed on stripAnsi(undefined).
The errorFormatter now rebuilds a plain object from the cause fields
so superjson serialises it as an object and the renderer gets the
full TeardownFailureCause.
Also defensive outputTail ?? "" in the pane so stripAnsi never
sees undefined.
* feat(ui/alert-dialog): make content text selectable
Desktop globals set user-select: none on html/body for a native feel.
Alert dialogs carry user-facing messages (error details, teardown
output tails, descriptions) that users need to copy — e.g. paste an
error into chat or grep a log snippet.
Applied at the component level so every AlertDialog in the app
benefits, not just the workspace-delete error panes.
* chore(workspace-delete): tighten stale comments + copy
- TeardownFailedPane: drop the defensive-round-trip note now that the
errorFormatter reliably serializes the cause as a plain object;
keep just the `?? ""` coalesce and the WHY of stripAnsi.
- ConflictPane: was phrased as "uncommitted or unlocked work" when
CONFLICT is now only thrown by the preflight dirty check (locked-
worktree cases fall to warnings). Rewrites copy + JSDoc to match.
- teardown.ts: timeout message said SIGKILL, but `pty.kill()` with no
args sends SIGHUP on Unix. Drop the false specificity.
* chore(workspace-delete): dev-only hook to simulate cloud failure
SUPERSET_DEBUG_FAIL_CLOUD_DELETE makes workspaceCleanup.destroy throw
at phase 2 (cloud delete) without needing to stop the cloud API dev
server. Used to verify the destroy saga's passthrough-on-cloud-fail
behavior end-to-end.
No-op when unset; guarded solely by env-var presence (truthy).
* Revert "chore(workspace-delete): dev-only hook to simulate cloud failure"
This reverts commit 5c88d88.
* fix(workspace-delete): review sweep
Addresses open PR comments:
- branchDeleted (P1, cubic): track via local flag inside the try
block instead of computing from preconditions, so the field reports
whether git branch actually succeeded.
- Teardown timeout can hang (coderabbit major): if pty.kill() doesn't
cause onExit to fire (zombie PTY), settle the promise directly after
a 2s grace window so workspaceCleanup.destroy never blocks forever.
- formatTeardownReason signal-terminated (coderabbit minor): handle
`exitCode === null && signal !== null && !timedOut` with a dedicated
"terminated by signal N" branch instead of falling through to
"failed to start".
- Duplicate run calls (coderabbit major): in-flight ref guard in
useDestroyDialogState.run so a rapid second click (same pane or
re-opened error pane) can't fire the mutation twice before the
first resolves.
- Global checkbox id (coderabbit minor): DestroyConfirmPane uses
useId() so the "Also delete local branch" label targeting doesn't
collide across dialog instances.
- Audit doc tense (cubic + coderabbit): v1-v2-delete-patterns-audit.md
now explicitly labeled as a pre-unification snapshot.
- Plan doc delivered-vs-follow-up split (coderabbit): the UI section
and work order clearly mark what landed in this PR vs. what's
follow-up (hotkey, EmptyTabView, V2WorkspaceRow affordance).
…nify chore(upstream): PR3 v2 workspace delete 統合 (superset-sh#3443)
…uperset-sh#3397) * Branch discovery * UI * branch discovery * feat(desktop): paginated branch picker with tabs, checkout, and open actions Rework the v2 new-workspace modal's branch picker to handle thousands of branches and surface existing workspaces directly. - Host-service searchBranches: cursor pagination, refresh flag gated by 30s TTL, server-side filter (branch/worktree), reflog-based recency, git-worktree-derived worktreePath, isCheckedOut flag. - New checkout procedure: git worktree add <path> <existing-branch> (no -b, no dedup). Auto-resolves local vs origin/<branch>. - Renderer: useInfiniteQuery + IntersectionObserver for infinite scroll; tabs for Branch vs Worktree filters; hover-reveal action buttons — Check out on Branch tab (disabled when isCheckedOut), Open on Worktree tab (navigates to existing workspace). Click row body still sets base branch for the prompt-driven fork flow. * Concise doc * fix(desktop): remote-only checkout tracking, host-scoped open, a11y - Remote-only Check out: use `git worktree add --track -b <branch>` so the worktree lands on a proper local tracking branch instead of detached HEAD. Auto-track only fires for the short name; passing `origin/<branch>` is treated as a commit-ish. - Open existing: scope the branch→workspace map by host id so the Worktree tab doesn't navigate to a workspace on the wrong host when the same branch exists on multiple hosts. - Picker a11y: reveal row action buttons on focus-within, not just hover. Swap the disabled span for a focusable button with aria-disabled so keyboard users can reach the "already checked out" tooltip. * fix(desktop): fall back to branch-only workspace lookup when host id unresolved The previous host-scoped filter returned an empty map when targetHostId was null (collections still loading, or no v2_hosts row matched the renderer's machineId). That broke Open on the Worktree tab in the common single-host case. Keep the host filter as a soft constraint — apply it only when a host id resolves; otherwise match by (projectId, branch). * feat(desktop): adopt orphan worktrees on Worktree-tab Create A `.worktrees/<branch>` directory can exist on disk without a matching `workspaces` row (older flows, partial failures, manual creation). The Worktree tab now surfaces these explicitly: rows with a workspace row show "Open" and navigate; rows without one show "Create" and adopt the worktree by registering cloud + local workspace rows, then navigate. - Host-service: `searchBranches` emits `hasWorkspace` per row (joined against workspaces table for this project on this host). - Host-service: new `adopt` procedure — no git ops, just cloud + local DB registration over an existing worktree path. Idempotent. - Renderer: Worktree-tab button label + handler switch on `hasWorkspace`. New `useAdoptWorktree` hook wraps the mutation. - Docs: Worktree-tab row-state table updated, procedure list updated. * Style * Handle git ref * doc change * refactor(host-service): discriminated ResolvedRef for git refs Closes a class of bugs where ref kind was inferred from short-name prefix (`startsWith("origin/")`) — fragile because a local branch can legitimately be named `origin/foo` and would be misclassified as a remote-tracking ref. - New `runtime/git/refs.ts`: `ResolvedRef` discriminated union (local / remote-tracking / tag / head) with template-literal `fullRef` types. `resolveRef` probes against full refnames, classification is unambiguous. Helpers: `asLocalRef`, `asRemoteRef`, `resolveDefaultBranchName`. - `resolveStartPoint` now returns `ResolvedRef`. `create` and `checkout` switch on `kind` instead of inspecting ref strings. Fixes the `origin/foo` local-branch misclassification in `checkout` and the spurious-fetch case in `create`. - `searchBranches` and `listBranchNames` parsing: derive isLocal/isRemote and the user-facing name from the FULL refname's structural prefix (`refs/heads/`, `refs/remotes/origin/`) — not from the short form. - New regression test: local branch named `origin/foo` resolves as `local`, not `remote-tracking`. - New lint script `scripts/check-git-ref-strings.sh` blocks `.startsWith("origin/")` and `.replace("origin/", ...)` outside of `runtime/git/refs.ts`. V1 desktop tRPC routers excluded with a doc'd follow-up — they have pre-existing instances of the bug class. - Pattern documented in `packages/host-service/GIT_REFS.md` along with prior art (GitHub Desktop, VSCode). * fix(host-service, desktop): resolveRef remote-prefix handling + a11y/UX cleanups Addresses review carryovers and a real-bug new finding in the ref helper. - resolveRef: accept `<remote>/<branch>` shortform input. Previously `resolveRef("origin/foo")` probed `refs/remotes/origin/origin/foo` (double-prefix) and returned null. Strip the remote prefix on the remote-probe path only — local probe is unchanged so a local branch literally named `origin/foo` still wins. New refs.test.ts covers all documented input shapes (12 tests). - listWorktreeBranches: drop dead `currentPath === worktreesRoot` branch (a Superset worktree is always a child, never the root). Replace silent catch with console.warn so a failed `git worktree list` is observable. - Picker action buttons: drop native `disabled` attribute on the "Check out" disabled state; keep aria-disabled. Native disabled buttons block pointer events so the Tooltip explaining "already checked out elsewhere" never opens. - IntersectionObserver guard: track an in-flight flag inside the callback so a re-mounted observer can't cascade-load all remaining pages when the sentinel stays in the root margin after a page loads. - BranchRow type: import from useBranchContext barrel instead of re-declaring locally — keeps single source of truth from inferRouterOutputs. - Picker actions (Create, Check out): respect `draft.workspaceName` when set instead of hardcoding `branchName`. Falls back to branchName. * fix(host-service): resolveStartPoint prefers local over remote-tracking A workspace branch like `agreeable-ermine` is local-only. If a stale `refs/remotes/origin/agreeable-ermine` cached ref exists (one-off push, missed prune), the previous remote-first logic silently picked it for the worktree start point — and `git worktree add ... origin/<branch>` failed with "fatal: invalid reference" when the cached ref no longer resolved. Local-first matches user intent: they pick branches from a list of refs they can see locally — they expect to fork from that exact local state. Remote freshness is bounded by the picker's refresh-on-modal-open already triggering `git fetch --prune`. Remote-tracking is still the fallback when local doesn't exist (the remote-only branch case that `Check out` handles correctly). * feat(workspace-creation): plumb baseBranchSource hint from picker to server The picker already knows whether the row the user clicked was local or remote-only (`isLocal` / `isRemote`). Carry that fact through the create flow as `baseBranchSource: "local" | "remote-tracking"` instead of re-resolving server-side. The local-first fix in resolveStartPoint stays as a safer default, but the hint removes the guesswork entirely: - The server can't be misled by a stale cached `refs/remotes/origin/<x>` because it doesn't probe — it uses what the picker said. - The user's intent (the row they actually saw + clicked) is what gets forked from. No silent priority inversion between client and server. Plumbing: - Server: `composer.baseBranchSource?: "local" | "remote-tracking"`. When set, `buildStartPointFromHint` constructs a ResolvedRef directly, skipping `resolveStartPoint`. Without it, falls back to probing (preserves behavior for non-picker callers). - Picker `onSelectCompareBaseBranch` now takes `(name, source)`. Source is `branch.isLocal ? "local" : "remote-tracking"`. - Draft + pending-workspace schema gain `baseBranchSource` (nullable for legacy rows). Pending page retry path passes the field through. * feat(workspace-creation): unify fork/checkout/adopt under pending-page flow Three buttons (Submit / Check out / Create) now share one path: the modal inserts a pendingWorkspaces row tagged with `intent` and navigates to /pending/<id>; the page owns the host-service mutation, the loading/error UX, and retry. Previously only fork went through this path — checkout and adopt were fire-and-forget and silently failed when the slow paths (clone, fetch, setup script) errored. - Schema: pendingWorkspaceSchema gains `intent: "fork" | "checkout" | "adopt"` discriminator and `warnings: string[]`. Fork-only fields (prompt, baseBranch, linkedIssues, linkedPR, attachmentCount) are default-empty for checkout/adopt rows. v2 isn't released — no migration concerns. - Pending page: `useFireIntent` switches on intent and calls the right mutation (createWorkspace / checkoutWorkspace / adoptWorktree). Fires once on first mount via a ref guard; same hook drives Retry. Adopt has no host-side progress steps so the page shows a generic spinner for that intent. Warnings array surfaced on success (especially useful for checkout's "setup terminal failed" case). - PromptGroup: handleCheckout / handleAdoptWorktree now insert a pending row + navigate via shared `insertPendingAndNavigate`. The fire-and-forget mutation calls in this component are gone; so are the local imports of useCheckoutDashboardWorkspace / useAdoptWorktree. - useSubmitWorkspace: drops the post-navigate fire path. It's pure "insert + navigate" now, matching checkout/adopt. mapLinkedContext is inlined into the page where the linkedContext is built per intent. - Doc: PENDING_FLOW.md captures the design — three intents, one path, the schema rationale, retry semantics, and the explicit decisions on adopt UX and warnings display. * fix(desktop): use client collection as source of truth for workspace existence After deleting a workspace, reopening the modal and clicking the branch on the Worktree tab surfaced "Could not find existing workspace for this branch." Root cause: two disagreeing sources: - Server-side `hasWorkspace` (host-service local `workspaces` table): still true, because the cloud delete doesn't cascade to the host DB. - Client `workspaceByBranch` (v2Workspaces collection, cloud-synced): false, because the cloud row was soft-deleted. Picker rendered "Open" from the server snapshot → click hit the client lookup → miss → error toast. Fix: pass `hasWorkspaceForBranch(name)` into the picker from PromptGroup, computed against the cloud-synced collection. The client is authoritative for "does a workspace row currently exist?" — the server field is a host-side cache that can be stale after deletion. Side benefit: an orphan worktree (disk dir without a cloud row) now correctly gets the "Create" button, and clicking it adopts the existing worktree into a fresh workspace. Deleted-but-left-on-disk workspaces become resurrectable with one click. Host-side cleanup (removing the local `workspaces` row + the worktree directory when a workspace is deleted) is a separate concern — flagged for a follow-up. * docs: add workspace delete design (host-service orchestrates cleanup) Captures the design for full-resource workspace deletion — worktree on disk, optional branch, cloud row, local host-DB row. Host-service orchestrates in that order (hard-to-reverse side first). Covers failure modes, UX decisions (delete-branch opt-in, confirm only when dirty), and what replaces the current cloud-only v2Workspace.delete. Not implemented here — separate PR picks this up. * fix(host-service): adopt always creates a fresh cloud row The old short-circuit returned `existingLocal.id` whenever a local `workspaces` row matched the (project, branch) pair, without calling cloud. After a prior workspace was hard-deleted in the cloud, the host sqlite row leaked — so re-adopting the worktree echoed a phantom id whose cloud row no longer existed. Renderer navigated to it, Electric never synced anything back, sidebar/target page showed "not found" indefinitely. Fix: drop the local-idempotency shortcut. Always call `v2Workspace.create`; the new cloudRow.id is authoritative. If a stale local row exists for this (project, branch), replace it with the fresh mapping. Also: pending page now waits for the newly-created cloud row to sync into the Electric-backed `v2Workspaces` collection before navigating, with a 3s fallback. Fast intents (adopt) were beating the sync to the punch and landing on /v2-workspace/<id> before the row was visible. Docs: replaced the draft delete design with the canonical version — v2 delete unifies through `workspaceCleanup.destroy`; implementation owned by a follow-up PR. * fix: reset pending-page refs on navigation, clear progress on early throws, harden lint script Three PR-review follow-ups: - Pending page: reset `firedRef` / `navigatedRef` when `pendingId` changes under a mounted component. Previously, navigating from one pending page to another kept the flags stuck at true → the second page never fired its mutation and sat in "creating" forever. - Checkout procedure: `clearProgress(pendingId)` on the empty-branch and `safeResolveWorktreePath` error paths. Without this, a stale pending row lingered until the 5-minute sweeper removed it. - check-git-ref-strings.sh: distinguish ripgrep exit codes. Exit 1 (no matches) still passes; exit 2+ (unreadable file, bad regex) now fails loudly. `2>/dev/null` previously masked real scan failures as clean passes. * refactor(desktop): tighten pending-row schema, extract + test intent payload builders Addresses two code-smell findings from the PR review: - pendingWorkspaceSchema had `hostTarget`, `linkedIssues`, `linkedPR` as `z.unknown()`, forcing `as`-casts at every read site and hiding malformed rows until a consumer crashed. Replaced with structured zod shapes (discriminatedUnion for hostTarget, typed object schemas for linkedIssues/linkedPR). Drops the casts in the pending page and the submit hook, and any malformed row now fails zod parsing instead of reaching the dispatch. - Intent dispatch logic in useFireIntent (the switch that translates pending-row state into mutation inputs per intent) was untested and tangled up with IO. Extracted the payload-building into pure helpers in buildIntentPayload.ts and added a contract suite (11 tests, all passing) covering: - mapLinkedContextFromPending: internal filtering, github filtering, skip-missing-fields, linkedPR passthrough, empty-input - buildForkPayload: full fork shape, empty-prompt → undefined, attachments plumbing, host-tracking hostTarget survives - buildCheckoutPayload: branch + runSetupScript only - buildAdoptPayload: minimal shape Pending page's useFireIntent becomes a thin wrapper: build payload, call mutation, update collection. Bonus cleanup: the `hostUrl` derivation block collapses from a nested ternary with 3 string casts to a plain discriminant switch now that the type is known. * docs: consolidate branch discovery, pending flow, and delete designs Merges three separate design docs in the modal directory into one `DESIGN.md` covering the full v2 workspace-creation architecture: - Branch discovery (data shape, server flow, client flow) — was BRANCH_DISCOVERY_DESIGN.md - Actions per row (Fork / Check out / Open / Create, authority for hasWorkspace) - Workspace creation flow (three intents, pending-row schema, pending-page dispatch, baseBranchSource plumbing, per-mutation details) — was PENDING_FLOW.md - Workspace delete (follow-up PR spec with host-service ownership principle) — was WORKSPACE_DELETE_DESIGN.md - Invariants + enforcement (authority decisions, tests, lint, type guarantees) Cross-links to `packages/host-service/GIT_REFS.md` which stays separate — it's a cross-cutting pattern doc, not modal-specific. Code-comment pointers updated from the old filenames to the new consolidated DESIGN.md with section references. * docs: rename DESIGN.md → V2_WORKSPACE_CREATION.md, move to apps/desktop root The doc covers a multi-module subsystem (modal + pending page + host-service procedures) — it doesn't belong nested deep inside the modal directory where 'DESIGN.md' is also too generic to be findable. Moved to apps/desktop/V2_WORKSPACE_CREATION.md, parallel to AGENTS.md and CLAUDE.md, where system-level architecture docs live in this repo. Name says what it covers. Code-comment pointers updated to use the new path. Internal cross-link to GIT_REFS.md fixed for the new depth. * docs: fold WORKSPACE_CREATION_FALLBACK research into V2_WORKSPACE_CREATION The fallback doc was the original design proposal for resolveStartPoint + --no-track + targeted single-ref fetch. All shipped in this PR. The spec/diff sections are now stale — implementation lives in code. Kept the prior-art comparison (VS Code, T3Code, GitHub Desktop, v1) as an appendix in V2_WORKSPACE_CREATION.md — that's the part future contributors might re-derive without reference. Updated the comparison table to reflect what we actually shipped (local-first instead of remote-first; targeted single-ref fetch). Added the rationale for each rejection (why not VS Code's upstream lookup, why not T3Code's gh-merge-base, etc.). Removed packages/host-service/WORKSPACE_CREATION_FALLBACK.md. * refactor(desktop): extract picker controller + linked-context hooks from PromptGroup PromptGroup was 592 lines mixing form state, agent prefs, branch picker state + handlers, link handlers, hotkey wiring, and JSX. Split the picker controller and link handlers into co-located hooks; PromptGroup is now 360 lines focused on form composition. - useBranchPickerController: owns picker state (search, filter), the branch-context query, host-id resolution + workspace lookup, and the three per-row action handlers (Open / Check out / Adopt). Returns a `pickerProps` bag the caller spreads into <CompareBaseBranchPicker>. Hides ~70 lines of plumbing behind one hook call. - useLinkedContext: bundles addLinkedIssue / addLinkedGitHubIssue / removeLinkedIssue / setLinkedPR / removeLinkedPR. Pure delegation over updateDraft. - Collapse the PromptGroup / PromptGroupInner indirection. There was no error boundary, provider, or conditional in the wrapper — just a pass-through. Removed. Behavior unchanged. Typecheck + lint clean; 11 pending-page tests pass.
…f lint Upstream superset-sh#3397 adds scripts/check-git-ref-strings.sh which bans string-literal prefix checks against the remote shortname to catch misclassification of branches literally named like the remote. The fork has a display-only prefix stripper in ChangesHeader that uses four startsWith calls for different ref formats; the origin/-prefix branch trips the new rule even though it is not doing ref kind classification. Replace the four branches with a single regex literal that strips the same set of prefixes. Semantics are unchanged.
Three upstream bugs reported by Codex / CodeRabbit on #179: 1. workspace-creation.ts:655 (Codex P1) — the remote-tracking branch path passed startPoint.remoteShortName (`origin/foo`) to `git worktree add`, leaving the exact `origin/foo` ambiguity this refactor was meant to fix. Pass startPoint.fullRef (`refs/remotes/origin/foo`) instead so git cannot confuse it with a local branch literally named `origin/foo`. 2. pending/$pendingId/page.tsx:221 (CodeRabbit Major) — syncTimedOut stayed true across pending row switches, so the next successful pending skipped its local-sync wait and reintroduced the `workspace not found` race. Reset on pendingId change. 3. GIT_REFS.md:151 (CodeRabbit Minor) — the type-only re-export example showed `runtime/git/refs.ts` re-exporting from `./refs`, which reads as self-referential. Rename the example barrel file to `runtime/git/index.ts`. Deferred (upstream bugs, out of scope for this fork pickup): - useBranchPickerController.ts v2Hosts-loading guard (Codex P2) - resolve-start-point.ts refExists error discrimination (CodeRabbit Major) - scripts/lint.sh check-script exit-code propagation (CodeRabbit Major)
…icker chore(upstream): PR4 v2 paginated branch picker with checkout/open actions (superset-sh#3397)
…rride migrator (superset-sh#3460) * feat(desktop/hotkeys): directional pane focus for v1 + Mac alt modifier Restores keyboard pane navigation to v1 workspaces with the same Cmd+Alt+Arrow chords v2 uses, via a standalone spatial neighbor walker over v1's MosaicNode<string> tree (no cross-package coupling to v2). Also lets the recorder accept alt-only chords on Mac and warns when bound alt-only chords could mask typing special characters. * feat(desktop/hotkeys): best-effort migrator for v1 event.key overrides The v1 recorder stored chords from event.key (layout-dependent raw glyphs like "meta+,", "ctrl+shift+@", "meta+alt+ª"), while the new store matches on event.code names. Extends sanitizeOverride with a token rewrite pre-pass covering US-ANSI punctuation, shifted glyphs, and macOS Option dead-keys; gates the Option dead-key table behind a navigator.keyboard-based US-layout probe so non-US Mac users aren't silently rebound to the wrong physical key.
…uperset-sh#3472) Flip Cmd+Alt+Arrow (mac) / Ctrl+Shift+Alt+Arrow (win/linux) back to the pre-superset-sh#3403 behavior: - Cmd+Alt+Left / Right → Previous / Next Tab - Cmd+Alt+Up / Down → Previous / Next Workspace FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} remain registered but are now unbound by default — users who want directional pane focus can rebind them in Settings → Keyboard. PREV_TAB_ALT / NEXT_TAB_ALT (Ctrl+Tab / Ctrl+Shift+Tab) are unchanged. Also move SCROLL_TO_BOTTOM on Windows/Linux from ctrl+shift+alt+down to ctrl+end to avoid the silent collision with NEXT_WORKSPACE that this restoration would otherwise introduce. buildRegisteredAppChords uses a Map keyed by canonical chord, so duplicate chords silently clobber each other in the reverse lookup. mac SCROLL_TO_BOTTOM (meta+shift+down) is unchanged. Registered users who previously overrode any of these IDs keep their overrides — defaults are just fallbacks.
Two blocking issues Codex found on the conflict resolution: 1. apps/desktop/src/renderer/hotkeys/migrate.ts — the fork guard that short-circuited when localStorage already had a hotkey-overrides entry bypassed upstream superset-sh#3460's new sanitizeOverride / detectUSLayout logic. The whole point of the `-v2` marker bump is to re-sanitize existing localStorage overrides that a pre-sanitizer build wrote with corrupt values, so skipping them defeats the migration. Restructure: instead of an early return, parse the existing localStorage overrides, re-run them through sanitizeOverridesMap with the US-layout probe, and write the cleaned map back. Still avoids overwriting with stale tRPC data (the fork's original intent). 2. apps/desktop/src/renderer/routes/_authenticated/_dashboard/workspace/ $workspaceId/page.tsx — the v1 FOCUS_PANE_{LEFT,RIGHT,UP,DOWN} hotkeys were missing `enabled: isActive`. Every other useHotkey in this file already has that guard because KeepAliveWorkspaces keeps inactive WorkspacePage instances mounted; without the guard, a user who rebinds the FOCUS_PANE_* ids would see the hotkey fire in hidden background workspaces as well.
Introduces the main-process scaffolding for a new fork-local "TODO" feature
that drives Claude Code autonomously toward a user-defined goal until a
decisive verify command passes. This commit establishes the backend
surface — schema, supervisor, and tRPC router — without any renderer work
or existing-UI integration, so it can be iterated on and reviewed in
isolation.
Why this shape
--------------
- The supervisor is pure TypeScript in the main process, not a second
Claude Code. All creativity stays in one worker; "management" is
deterministic code. This avoids LLM-to-LLM communication, which the
research survey flagged as the biggest reliability sink for long-horizon
autonomous loops.
- The worker runs as interactive Claude Code inside a real PTY pane (same
infra the existing Run button uses), so users can watch it live and type
into it to intervene. Completion per turn is detected by idle timing on
the PTY data stream; decisive success is the exit code of the user's
verify command (e.g. `bun test`). LLM self-report is never trusted.
- Fork-conflict surface is kept to three 1-line edits in existing files
(trpc routers index, local-db schema.ts re-export, local-db schema
barrel). Everything else lives in new files under new directories.
What lands here
---------------
- apps/desktop/plans/todo-agent-plan.md — full design doc covering goals,
non-goals, architecture, execution loop, intervention UX, UI surface,
fork-conflict strategy, data model, tRPC surface, phased delivery, and
unresolved questions.
- packages/local-db/src/schema/todo-sessions.ts — new `todo_sessions`
SQLite table (workspace-scoped, status machine, budget, verdict fields,
artifact path). Re-exported from schema.ts so drizzle-kit picks it up
without changing the drizzle.config.ts entry.
- apps/desktop/src/main/todo-agent/
- types.ts zod input schemas + shared constants
- session-store.ts localDb-backed CRUD + EventEmitter fan-out, plus a
worktree-path resolver for main-process callers.
- supervisor.ts Singleton loop driver: prepares artifacts
(`.superset/todo/<id>/goal.md`), writes the iteration
prompt into the worker PTY via the workspace
terminal runtime, waits for idle, runs the verify
command as a detached child process, applies
futility (3x same failing test) and budget
(iteration count, wall-clock) guards, and settles
the session to done/failed/escalated/aborted.
Also exposes abort() (sends double Ctrl-C to the
pane) and sendInput() passthroughs.
- trpc-router.ts `todoAgent.*` router: create / list / get /
attachPane / abort / sendInput + an observable-based
subscribeState subscription (per trpc-electron
constraint documented in apps/desktop/AGENTS.md).
- index.ts Barrel.
- apps/desktop/src/lib/trpc/routers/index.ts — register the new router
as `todoAgent` on the app router (import + one field, clearly fork-
marked).
Not yet in this commit
----------------------
- Renderer UI (TodoButton, TodoModal, TodoPanel) and the PresetsBar
integration point next to WorkspaceRunButton.
- Drizzle migration file. Per repo policy, migrations are generated by
running `bunx drizzle-kit generate` locally and never hand-written;
this will be generated when the feature is wired end-to-end.
- Stop-hook integration via `--settings`. v1 uses idle-detection to
stay decoupled from Claude Code CLI internals. Tracked as an
Unresolved item in the plan doc for v2.
Verified
--------
- `bun run typecheck` in apps/desktop — clean.
- `tsc --noEmit` in packages/local-db — clean.
Refs: apps/desktop/plans/todo-agent-plan.md
chore(upstream): PR5 hotkeys — v1 directional pane focus + Cmd+Alt+Arrow restoration (superset-sh#3460 superset-sh#3472)
Adds the first user-facing surface of the autonomous TODO agent: a compact TODO button placed immediately left of WorkspaceRunButton in PresetsBar, plus the creation modal that collects the task details the supervisor needs to start a run. Scope of this commit -------------------- Deliberately limited to session *creation*. Clicking the button opens a modal, the user fills in the form, and submit creates a `todo_sessions` row via `todoAgent.create`. The supervisor does not start executing yet — pane attach + execution handoff lands in a follow-up commit along with TodoPanel. This keeps each commit independently reviewable and rollback-safe. TodoButton (TodoButton/TodoButton.tsx) -------------------------------------- - Small ghost-variant button with a list icon and "TODO" label, styled to sit naturally next to WorkspaceRunButton without visually competing with it. - Polls `todoAgent.list` every 3s for the current workspace and shows a badge with the count of queued/preparing/running/verifying sessions so users can see at a glance that work is in flight. - Opens the modal as local state; no global store needed. TodoModal (TodoModal/TodoModal.tsx) ----------------------------------- Form fields, each mapped 1:1 to the zod schema in `main/todo-agent/types.ts`: - Title (max 200) - What should be done? (multiline, max 10k) - Clear goal / acceptance criteria (multiline, required — this is the single most important input for making the loop terminate) - Verify command (default `bun test`, exit code is the ground truth) - Max iterations (default 10, capped at 100) - Wall-clock minutes (default 30, capped at 240) Submit calls `electronTrpc.todoAgent.create.useMutation` and invalidates `todoAgent.list` so the button badge updates immediately. Success and failure are surfaced via the existing sonner toast. Cancel and close both reset the form. Rendering changes ----------------- - `PresetsBar.tsx` now imports TodoButton and renders it inside the existing `ml-auto flex items-center gap-1 shrink-0` wrapper, immediately before WorkspaceRunButton. The wrapper already handles spacing so no layout tweaks are needed. - Both the TodoButton import line and the render line are isolated additions to keep upstream merge conflicts cheap. Co-location ----------- Component code follows the repo's folder-per-component convention under `src/renderer/features/todo-agent/` so all fork-local feature code stays in one directory and is easy to delete or rebase. Verified -------- - `bun run typecheck` in apps/desktop — clean.
Closes the v1 control loop for the autonomous TODO agent: users can now
start a queued session, watch it run in a normal workspace terminal
tab, abort it, and type interventions directly into the running worker.
TodoButton dropdown
-------------------
The primary click still opens the creation modal (fast path for the
common action), but a chevron next to the button now opens a
DropdownMenu with "New TODO…" and "Open panel" so users can reach the
sessions drawer without having to create a new task first. The button
group is rendered as a single fused control (rounded-r-none +
rounded-l-none) so it reads as one widget next to WorkspaceRunButton.
TodoPanel (TodoPanel/TodoPanel.tsx)
-----------------------------------
Right-side Sheet, 540px wide, 2-column layout:
- Left: scrollable list of sessions for the current workspace, polled
every 2s while the panel is open. Selection is local state.
- Right: detail view for the selected session — status, title,
description, goal, verify command, iteration/budget snapshot, last
verdict reason (as a max-h-40 scrollable pre block so long failure
logs don't blow up the layout).
Controls in the detail view:
- **Start** (visible only when status === "queued")
The handoff to the supervisor is done client-side in four steps so
it composes cleanly with existing workspace terminal infra instead
of adding new tab-creation primitives in the main process:
1. `useTabsStore.getState().addTab(workspaceId)` creates a new
terminal tab + pane in the Zustand store. The tab shows up in
the workspace tab bar like any other terminal, so anyone can
click over to watch the worker live.
2. `setTabAutoTitle(tabId, "TODO: …")` labels the tab so it is
easy to spot.
3. `launchCommandInPane` (same helper the existing agent launcher
uses) runs interactive `claude <prompt>` in the new pane,
passing the session-specific initial prompt that points at
`.superset/todo/<id>/goal.md` (written by the supervisor at
creation time).
4. `todoAgent.attachPane({ sessionId, tabId, paneId })` hands the
session over to the supervisor, which takes it from `queued`
to `running` and begins the idle-detect/verify loop.
- **Abort** (visible when active and already attached): calls
`todoAgent.abort` which double-Ctrl-C's the pane and marks the
session aborted.
- **Intervene input**: a small Input + Send button that writes text
directly into the worker PTY via `todoAgent.sendInput`. Enter
submits, shift+Enter does nothing (no multi-line for v1). This is
the explicit "you can intervene while it runs" surface the plan
doc promised; users can also just click over to the terminal tab
and type there, since it is a real PTY.
A small footer reminds users that the worker runs in a normal
workspace terminal tab and can be opened from the tab bar directly —
no special terminal embed is needed inside the panel itself for v1,
which avoids bringing in the heavy TerminalPane + registry UI.
Scope deliberately out of this commit
-------------------------------------
- No auto-start on create. The user must explicitly click Start from
the panel. This makes the handoff observable and keeps the modal
commit rollback-safe.
- No live PTY embed inside the panel. v1 relies on the workspace's
own tab bar for that. Can be added later if users want an
in-panel viewer.
- No queue UI. The supervisor already queues internally if a second
Start is pressed while another session runs, but there is no
renderer affordance to reorder yet.
Integration note
----------------
`addTab` is the underlying Zustand method; `addTerminalTab` only
exists on the agent-session-orchestrator adapter layer as a thin
wrapper. Calling `addTab` directly keeps this feature from depending
on the full AgentLaunchTabsAdapter plumbing.
Verified
--------
- `bun run typecheck` in apps/desktop — clean.
Adds migration 0049, auto-generated by `bunx drizzle-kit generate` in packages/local-db against the new `todo_sessions` table that landed in the backend-scaffold commit. Creates the table with all 22 columns, the three indexes defined in the schema file (workspace / status / created_at), and the two foreign keys (workspace_id → workspaces.id ON DELETE CASCADE, project_id → projects.id ON DELETE SET NULL). Per repo policy, migration SQL and snapshot files are never edited by hand; they are regenerated from the schema source. The journal update is part of the same generate run. Required follow-up: the migration runs automatically on the next desktop app start (local-db migrations apply on boot). No manual action needed beyond relaunching the app.
guessFailingTest (apps/desktop/src/main/todo-agent/supervisor.ts)
-----------------------------------------------------------------
The previous heuristic was a single regex matching `FAIL|✗|×` and
would both miss common runners and return run-specific strings that
broke the "same failure 3 times in a row → escalate" check — a timing
suffix like "(12 ms)" changing between runs was enough to reset the
consecutive-failure counter and make the futility guard toothless.
The replacement:
- Strips ANSI escapes before matching, so colored runner output is
handled.
- Tries a prioritized list of line patterns covering bun test, vitest
(tree view + summary + inline), jest (FAIL + ✕), generic ✗, TAP /
node:test ("not ok 1 - …"), and playwright. Priority order matters
because some runners emit several matches per failure and we want
the most specific one first.
- Falls back to the first line containing "Error:" or "Assertion:" so
shell verify commands that are not test runners (build scripts,
type-checkers) still produce a stable identifier.
- Normalizes the returned id through `normalizeTestId`, which:
* drops "(NNN ms)" and "[NNN ms]" timing suffixes,
* collapses object hex addresses ("Foo@0x7f8b…") to "Foo@0x?",
* truncates wording-variant ": expected X to be Y" tails,
* caps length at 240 chars.
This is the part that actually makes the futility guard work: the
same logical failure now produces the same id across reruns even
if the runner prints slightly different noise.
Plan doc paths (apps/desktop/plans/todo-agent-plan.md)
------------------------------------------------------
Three references still pointed at the Postgres `packages/db` schema
from the original design sketch. The feature actually lives in
`packages/local-db` (SQLite, the desktop app's local store). Updated
both the "files touched" checklist and the inline data-model code
block so future readers of the plan don't hunt in the wrong package.
Verified
--------
- `bun run typecheck` in apps/desktop — clean.
…uperset-sh#3463) * feat(desktop): add Review tab to v2 workspace sidebar Port the v1 Review tab functionality into the v2 workspace sidebar, replacing the "Checks — Coming soon" stub. Uses v2-native host-service endpoints (git.getPullRequest, git.getPullRequestThreads) instead of v1 electron endpoints. Features: - PR header with title, state icon, review decision badge - Collapsible CI checks section with status icons and links - PR comments with avatars, timestamps, and preview text - Copy individual comments or copy all to clipboard - Comment pane: click a comment to view rendered markdown in a tab - GitHub link in pane header to open comment on GitHub - Copyable tables in rendered markdown * fix(desktop): address review tab PR feedback - Fix memory leaks: add timeout cleanup refs in CommentPane and CopyableTable, clear previous timeout on rapid clicks - Add error state: show "Unable to load review status" when tRPC query fails instead of infinite loading spinner - Fix getIcon fallback: show MessageSquare icon when avatarUrl is missing instead of rendering <img src={undefined}> - Add aria-label to comment open button for screen readers - Add aria-hidden to decorative arrow icon in PRHeader - Add group-focus-visible:opacity-100 to PRHeader arrow for keyboard nav * fix(desktop): add .catch() to all clipboard promise chains Prevents unhandled promise rejections in the renderer if electronTrpcClient.external.copyText.mutate() fails. * fix(desktop): guard state updates after unmount in CommentPane Add isMountedRef to both CommentPane and CopyableTable to prevent setCopied calls after unmount when the async clipboard IPC resolves after the component is gone. * fix(desktop): fix comments not loading until refresh The threads query had staleTime: 30_000 which cached empty results when the query fired before the host-service had PR data synced. Remove staleTime so every mount/refocus fetches fresh data (background polling at 30s still runs via refetchInterval). Also tighten the enabled condition to use prQuery.isSuccess instead of !!prQuery.data for clearer intent, and use composite key for CheckRow to handle duplicate check names. * fix(desktop): unmount guard in CommentsSection + single-pass check status - Add isMountedRef to CommentsSection so markCopied doesn't set state after unmount (same pattern as CommentPane/CopyableTable) - Rewrite computeChecksStatus as single loop instead of 3 passes (filter + some(failure) + some(pending) each called coerceCheckStatus) * feat(desktop): render mermaid diagrams + syntax highlighting in comment pane Use the existing CodeBlock component which handles mermaid via @streamdown/mermaid and syntax highlighting via react-syntax-highlighter. * fix(desktop): lighten mermaid diagram background in comment pane * fix(desktop): hide mermaid label + lighten actual diagram background - Hide the "mermaid" label and action buttons on the block wrapper - Strip the outer border/padding so it blends with the page - Target the SVG element itself to lighten its background color * fix(desktop): revert hacky mermaid overrides, use CSS text color instead Revert the custom Streamdown/SyntaxHighlighter approach. Go back to using the shared CodeBlock component. Instead of changing the mermaid canvas background, override the SVG text fill and node/edge label colors via CSS to use --foreground, making them readable on both light and dark themes. * fix(desktop): use mermaid themeVariables for light text in dark mode Use Streamdown directly with custom themeVariables to set light text colors (primaryTextColor, nodeTextColor, labelTextColor, etc.) on the dark mermaid theme. Removes the CSS hacks that couldn't override mermaid's inline SVG styles. * fix(desktop): mermaid dark theme contrast + hide label/wrapper chrome - Use theme: "base" with full dark color palette so themeVariables actually control node fills (dark gray nodes, light text) - Hide "mermaid" label and action buttons via CSS - Strip background/border from outer block and inner wrapper so diagram sits directly on the page background * fix(desktop): extract CommentCodeBlock as standalone component Move code block rendering out of useMemo into a proper React component so the component identity is stable across renders. Prevents unmount/ remount of Streamdown/SyntaxHighlighter when the theme changes. Mermaid theme variable objects extracted as module-level constants.
When a user created a TODO session against a workspace with `type="branch"` (no worktree row), the supervisor threw `todo-agent: workspace <id> has no worktree` at creation time and refused to run. The bug was that `resolveWorktreePath` only looked at the `worktrees` table. Workspaces in this app come in two flavors: - `type="worktree"` — backed by a real `worktrees.path` - `type="branch"` — runs directly in the project's `mainRepoPath`, with no worktrees row at all The existing terminal runtime already handles both via `workspace-terminal-context.ts`, which falls back to `projects.mainRepoPath` when no worktree row exists. The TODO agent now follows the same resolution strategy: LEFT JOIN both `projects` and `worktrees`, return `worktreePath ?? mainRepoPath`. The only undefined-returning case is now "workspace row itself does not exist". This unblocks session creation for any branch-type workspace. No schema or API surface changes.
Three functional issues Codex found on upstream superset-sh#3463 cherry-pick: 1. useReviewTab.tsx normalizeThreadsToComments only picked thread.comments[0], silently dropping every reply in a review thread. v1 flattened all comments per thread. Loop over thread.comments instead so the listing and badge match the real PR state. 2. CommentPane.tsx fed ReactMarkdown the raw comment body without overriding <a> or <img>. GitHub-supplied links would navigate the main window away, and arbitrary remote images would load. Add CommentLink (opens via electronTrpcClient.external.openUrl) and CommentImage (uses SafeImage, which only renders data: URLs). 3. useReviewTab.tsx returned badge: openCommentCount unconditionally, so the tab showed a "0" badge when there were no open comments. Changes tab already suppresses zero badges; match that. Deferred (upstream bug, host-service schema change required): - Review comments never get a GitHub URL because the GraphQL query and host-service types in packages/host-service don't fetch/expose reviewComment.url. Fixing this needs host-service router changes and is out of scope for this cherry-pick.
Two changes that turn the TODO agent from a "code task + test gate"
feature into a general autonomous task runner that covers research and
investigation use cases, and aligns the UI language with the rest of
this fork-local feature.
1. Verify command is now optional
---------------------------------
Motivation: not every TODO has a sensible acceptance command. Research
tasks ("このファイル群を調査して設計案をまとめて"), code-reading
tasks, and one-shot refactors do not have a `bun test` that can decide
"done" — forcing users to invent one made the feature feel
code-centric when it is really about autonomous execution in general.
Behavior:
- `packages/local-db/src/schema/todo-sessions.ts`: `verify_command` is
now nullable. New migration `0050_todo_verify_optional.sql`
(drizzle-kit generated) applies the NOT NULL drop.
- `apps/desktop/src/main/todo-agent/types.ts`: `verifyCommand` is now
an optional zod string that transforms empty to undefined, so
trimming an empty input reliably reaches the supervisor as
"unset" rather than "empty string".
- `apps/desktop/src/main/todo-agent/supervisor.ts`: new branch at the
top of `runSession` for the "no verify" path — single-turn mode.
It writes the initial prompt once, waits for the worker PTY to go
idle, and marks the session `done` with a verdict message asking
the user to review the output in the worker terminal. No iteration
loop, no futility detection, no budget polling beyond the shared
wall-clock cap on the idle-wait. The user drives any follow-up
turns manually by typing into the same terminal tab.
- The existing iteration loop is preserved verbatim for sessions
that do have a verify command; only the branch above it is new.
- Goal doc and per-iteration prompts composed by the supervisor now
switch wording based on whether a verify command is set
(`renderGoalDoc` and `buildIterationPrompt`), and the panel's
Start handler does the same for the initial claude invocation.
Rationale for keeping single-turn as one iteration rather than
capping the existing loop at 1: the loop's structure assumes a
verify-then-maybe-continue flow. Short-circuiting it keeps the
branching explicit and makes the "単発モード" state machine
readable at a glance in the supervisor.
2. UI localized to Japanese
---------------------------
This is a fork-local feature and the rest of the user's workflow is
in Japanese, so there is no reason for the TODO surface to be
English. Translated strings:
- `TodoButton/TodoButton.tsx`: tooltips, dropdown items
- `TodoModal/TodoModal.tsx`: dialog title/description, all form
labels, placeholders, helper text, buttons, toasts, and error
messages. The verify field is explicitly marked "(任意)" and its
helper text explains the empty-equals-single-turn behavior. The
budget fields (max iterations, wall-clock minutes) are now
conditionally rendered only when the verify field has a value,
since they have no meaning in single-turn mode.
- `TodoPanel/TodoPanel.tsx`: sheet header, session list empty
state, detail labels (ステータス/タイトル/やってほしいこと/
ゴール/Verify/予算/直近の結果), button labels (Start remains
in-English as a recognizable verb, 中断/送信 are translated),
intervene input placeholder, footer hint. The Verify field in the
detail view now shows "単発モード(verify なし)" when the session
was created without one, and the budget display adapts too.
- Toast messages (作成しました / 開始しました / 中断しました /
送信に失敗しました, etc.) and the error thrown by trpc `create`
when workspace path resolution fails.
- Supervisor-authored `goal.md` content and in-prompt wording are
also Japanese so the worker Claude speaks the same language as
the user.
Verified
--------
- `bun run typecheck` in apps/desktop — clean.
- Migration generated via `bunx drizzle-kit generate` in
packages/local-db (not hand-edited).
Two related fixes for a failure mode reported after the v1 rollout.
Symptom: on Start, the worker terminal tab showed
claude ".superset/todo/<id>/goal.md …"
⎿ Please run /login · API Error: 401 {authentication_error …}
and yet the TodoPanel flipped the session to `done · iter 1`. Two
distinct bugs were stacked here: (1) the command shape we sent to the
PTY was not the one the rest of the app uses, and (2) the supervisor
treated "the worker went idle" as "the worker finished successfully"
even when the idle was caused by an immediate authentication crash.
1. Use the canonical claude prompt command builder
---------------------------------------------------
`TodoPanel/TodoPanel.tsx`'s Start handler was building the launch
command by hand:
const command = `claude ${JSON.stringify(initialPrompt)}`;
That invocation was subtly wrong in two ways:
- It skipped `--dangerously-skip-permissions`, which is part of the
canonical claude command defined in
`packages/shared/src/builtin-terminal-agents.ts` and is included by
every other agent launch path in the app (Run button, tasks view,
agent preset menu). Bypassing it changes how claude-code boots and
how its interactive auth / tool-use prompts are handled.
- It passed the prompt as a single JSON-quoted positional arg instead
of using the heredoc-cat form produced by `buildPromptCommandString`
for the `argv` transport. The heredoc form is what the terminal
runtime's `~/.superset/bin` shim is designed to see, and it survives
multi-line prompts, quoting, and the wrapper's argument parsing.
Both problems go away by routing through `buildAgentPromptCommand`
from `@superset/shared/agent-command` with `agent: "claude"`, which
is the exact same code path the existing Run / task launches use.
The panel now calls:
buildAgentPromptCommand({
prompt: initialPrompt,
randomId: session.id,
agent: "claude",
})
and writes the resulting string through `launchCommandInPane`.
`session.id` is already a UUID so it is a fine `randomId` for the
delimiter.
2. Detect worker startup errors instead of marking them `done`
---------------------------------------------------------------
`supervisor.ts`'s `waitForIdle` used to return a plain `boolean` for
"did we reach idle?" and the single-turn path settled the session
with `status: "done"` as long as idle was reached. That is the wrong
contract: if the claude process prints an auth error and exits
(or sits at a login prompt), the PTY goes idle too, and the session
was being reported as successfully complete.
Changes:
- `waitForIdle` now accumulates PTY output into a ~16 KB ring buffer
during the wait and returns `{ idled, buffer }` instead of just
`idled`. The buffer is used only for post-hoc scanning; it is not
emitted anywhere.
- New `detectStartupError(buffer)` helper scans the captured text
(with ANSI stripped) for a small, deliberately conservative set of
fatal markers:
* `Please run /login`
* `authentication_error` / `Invalid authentication credentials`
* `claude: command not found` / `command not found: claude`
* `API Error: 5xx`
* `fatal:`
Each pattern maps to a Japanese, actionable `verdictReason`
explaining what went wrong. The set is intentionally narrow so we
do not confuse a normal test failure inside the worker's TUI with a
startup crash — those patterns never appear in healthy runs.
- Single-turn path now runs the detector immediately after idle. On a
hit, the session is moved to `failed` with the matched reason. On a
miss, the existing "done with review-the-terminal" verdict stands.
- Iteration-mode path runs the detector once, after the first
iteration's idle, before executing the verify command. This is the
only moment the detector adds value: running verify against a
worker that never actually booted would produce a misleading
"verify failed" verdict instead of the real reason. Subsequent
iterations are assumed to be live because the supervisor is still
feeding them follow-up prompts and the worker is clearly
processing.
Behavior after this fix on the reported failure
------------------------------------------------
Starting a session against a workspace whose `claude` binary is not
authenticated will now:
1. Launch claude via the canonical preset command (same as Run
button). If the auth problem was an artifact of the hand-built
command shape, it may resolve on its own.
2. If claude still fails authentication, the session will show
`status: failed` with verdictReason
"Claude Code の認証に失敗しました(API Error 401)。ワーカーの
ターミナルで `/login` を実行してください。" instead of the
misleading `done · iter 1`.
Verified
--------
- `bun run typecheck` in apps/desktop — clean.
…rkspace list for TODO agent
This commit is the backend half of a broader redesign of the TODO
agent surface. Three discrete changes land here:
1. `goal` is now optional on todo_sessions
-------------------------------------------
Motivation: not every TODO has a crisp acceptance sentence. Research
and investigation tasks naturally use "やって欲しいこと
(description) が終わったら完了" as the implicit goal, and making users
invent a separate goal string was pure friction.
Changes:
- `packages/local-db/src/schema/todo-sessions.ts`: `goal` column drops
`.notNull()`. Migration `0051_todo_goal_optional.sql` is the drizzle-
kit generated table-recreate migration that removes the NOT NULL
constraint.
- `apps/desktop/src/main/todo-agent/types.ts`: `todoCreateInputSchema`'s
`goal` is now an optional trimmed string that transforms empty to
undefined — same pattern as `verifyCommand`.
- `apps/desktop/src/main/todo-agent/trpc-router.ts`: the `create`
mutation now inserts `goal ?? null` so an omitted goal becomes a DB
null, not an empty string.
- `apps/desktop/src/main/todo-agent/supervisor.ts`:
- `renderGoalDoc` now emits "(未指定。上記『やって欲しいこと』が
完了した時点で完了とみなす)" as the goal body when the session
has no explicit goal, so the file the worker reads still has a
coherent acceptance section.
- `buildIterationPrompt` composes a `goalClause` that says either
"ゴール(受け入れ条件)を達成することを目指してください" or
"『やって欲しいこと』が完了した時点で完了とみなしてください"
depending on whether `session.goal` is set, and threads that
clause through all three prompt shapes (single-turn, first
iteration with verify, retry iteration).
2. AI rewrite helper for the TODO creation form
------------------------------------------------
New backend for the sparkle/✨ button that the creation modal will get
in the follow-up commit. Click → send the field's current text to a
small model with a tight rewrite prompt → receive a cleaner, more
LLM-friendly version back.
Implementation notes:
- Reuses the existing `callSmallModel` plumbing from
`apps/desktop/src/lib/ai/call-small-model.ts` — the same path the
workspace auto-namer uses. Zero new credential handling, zero new
provider fallback logic, diagnostics integration for free.
- `apps/desktop/src/main/todo-agent/enhance-text.ts` exposes
`enhanceTodoText(rawText, kind)` where `kind` is `"description" |
"goal"`. Each kind has a dedicated Japanese system prompt baked in:
* description: "ユーザーが書いた雑な TODO の記述を、自律
コーディングエージェントが理解しやすい明確な指示に書き換える"
* goal: "雑なゴールを、検証可能な受け入れ条件に書き換える"
Both prompts explicitly say "元の意図を保つ" and "新しい要件を
追加しない" to prevent the model from hallucinating scope creep,
cap the output at ~1-6 lines, and return only the rewritten text
without any "Sure, here's the rewrite:" preambles.
- Invokes via `callSmallModel` → `generateText` from the Vercel AI SDK
directly, since the `model` passed to the invoke callback is a
`LanguageModel` from `@ai-sdk/anthropic` (for the Anthropic path,
`claude-haiku-4-5-20251001`) or `@ai-sdk/openai` (OpenAI path).
Both accept `generateText({ model, system, prompt })` uniformly,
so the branching in `ai-name.ts` isn't needed here.
- `describeEnhanceFailure(attempts)` turns the SmallModelAttempt[] into
a user-facing Japanese error string, honoring the same hierarchy
the workspace namer uses (expired > failed > unsupported >
missing-credentials).
New tRPC surface:
- `todoAgent.enhanceText` — `{ text, kind }` in, `{ text }` out.
Throws TRPCError(INTERNAL_SERVER_ERROR, <japanese message>) on any
failure so the renderer can surface it in a toast.
3. Cross-workspace session list for the Agent Manager view
----------------------------------------------------------
The existing `todoAgent.list` query is workspace-scoped. The
follow-up Agent-Manager-style view needs a single flat feed of all
TODO sessions grouped by workspace, so we can present something
closer to Antigravity's "all agents in one place" UX.
- `apps/desktop/src/main/todo-agent/session-store.ts` adds a new
`TodoSessionListEntry` type (session fields + workspaceName,
workspaceBranch, projectName) and a new `listAll()` method that
LEFT JOINs `workspaces` and `projects` for those labels, filters
out workspaces being deleted (`isNull(workspaces.deletingAt)`),
and orders by `createdAt DESC`.
- `apps/desktop/src/main/todo-agent/trpc-router.ts` exposes
`todoAgent.listAll` as a no-arg query returning that entry list.
- The existing `list` is kept for any callers that only need a single
workspace and will continue to back the per-workspace badge count
on the TODO button.
Housekeeping
------------
`.gitignore` now includes `.superset/todo/` so TODO agent runtime
artifacts (goal.md + any per-session state files) stay out of git.
Verified
--------
- `bun run typecheck` in apps/desktop — clean.
- Migration generated via `bunx drizzle-kit generate` in
packages/local-db (not hand-edited).
fix(desktop): TODO/Schedule ダイアログ横幅を !important で確実に広げる (再対応 #238)
Claude Code は MCP ツールを `mcp__<server>__<tool>` という生の名前で 流してくるが、ライブストリームの UI で `mcp__` プレフィックスは読む側に 取って意味のないノイズ。`mcp__aivis__talk` を `aivis talk` に整形して 表示するようにした。 - `formatToolDisplayName` を新設し ToolCallCard のラベル描画で使用 - 全 MCP ツールは専用 palette (Plug アイコン + indigo) に集約。サーバー別の 色分けはしない(フォールバックの Wrench グレーより識別しやすくする 意図で、色数を増やしすぎてレインボーにしない) - 生イベントには触らないので履歴ログの形式は不変
feat(desktop): ライブストリームのMCPツール名を "server tool" 形式で表示
- TodoManager の「新規」で開く Composer Dialog の幅が `sm:max-w-lg` (512px) に 制限されていた問題を修正。`!w-[min(1080px,calc(100vw-3rem))]` で !important 指定して base 側を確実に上書き (Issue #238 の TodoModal とは別経路だった) - 左サイドバーのグループ見出し / ChangesSidebar の truncate が効かない問題を修正。 Radix ScrollArea が内部で `display: table` のラッパを挿入するため子要素の 幅が確定せず ellipsis が出なかった。共通 ScrollArea で `[&>div]:!block` を 適用して block layout に強制 - 新規 TODO ダイアログにも Claude model / effort ピッカーを追加。既定値は ユーザ設定の `defaultClaudeModel` / `defaultClaudeEffort` から seed し、 未タッチなら --model / --effort フラグは CLI に渡さない - ClaudeRuntimePicker の Select Item を単純化 (説明文・ヒント段落を削除)。 使うユーザは model / effort 名称だけで十分なため
…e-picker fix(desktop): TODO 新規作成ダイアログの幅 / truncate / model&effort ピッカー
Settings > Notifications に "From YouTube" ボタンを追加し、 URL と開始時刻 / 長さ (最大30秒) を指定して カスタム通知音をクリップ生成できるようにする。 ローカルにインストール済みの yt-dlp と ffmpeg を使用する。 未インストール時は分かりやすいエラーで案内する。 Closes #258
- yt-dlp/ffmpeg をログインシェルの PATH 経由で解決し、 Finder/Dock 起動時の Homebrew パス欠落を回避 - ffprobe も事前チェックして欠落時に明示エラー - yt-dlp の出力をテンプレート (.%(ext)s) で受け取り、 実際に生成されたファイルをディレクトリスキャンで取得 - setCustomRingtoneDisplayName 呼び出しで importedAt を 上書きしないように既存値を保持 - Start time の秒入力を 0-59 にクランプ
feat(desktop): YouTube URL から通知音を作成 (#258)
GUI 起動の Electron プロセスでは login shell PATH の取り込みが 不安定で、ffmpeg/ffprobe がインストール済みでも見つからないこ とがあった。 - 必要なバイナリを env.PATH と /opt/homebrew/bin 等のフォール バックディレクトリから絶対パスで解決 - spawn には絶対パスを渡し、yt-dlp には --ffmpeg-location を 明示 - 解決失敗時のエラーメッセージに「ログインシェル PATH を確認」 と追記
fix(desktop): YouTube ringtone の yt-dlp/ffmpeg を絶対パスで解決
カスタム通知音カードに3点メニューを追加し、Rename と Delete を選べるようにする。 - main: deleteCustomRingtone を新設、ファイル + メタデータを除去 - tRPC: ringtone.deleteCustom / ringtone.renameCustom を追加 - UI: RenameRingtoneDialog 新規追加、RingtoneCard に ドロップダウンメニュー(カスタムのみ)を追加 - 削除時に CUSTOM_RINGTONE_ID が選択中ならビルトインに自動切替 Refs #258
feat(desktop): カスタム通知音の削除 / リネーム機能
OS デフォルトの window.confirm ではなく、リネームと同じ Dialog コンポーネントで 削除確認 UI を提供する。 Closes #262
vibrancy ON 時に `--background` が rgba (半透明) になる仕様のまま、 6066d72 で FileViewer 系のサーフェスを `bg-white` 等から `bg-background` に移行したため、Excel ビューア/Excel diff/画像プレビュー/ HTML プレビューの背景まで透過し下のウィンドウが透けて見えていた。 これらのサーフェスは読み取り専用のコンテンツ表示なので、 vibrancy の影響を受けないよう `bg-background-solid` に差し替えて 不透明な背景へ戻す。`--background-solid` はテーマ切替には追従しつつ vibrancy alpha は適用されないため、ダイアログ等と同じ扱い。 対象: - FileViewerContent: HtmlPreviewWebview / 画像プレビュー - SpreadsheetViewer: シート本体コンテナ - SpreadsheetDiffViewer: 左右 DiffTable
Issue #264 対応。AgentManager の TODO セッション詳細と スケジュール一覧で、使用中の Claude モデルと思考 effort を 確認できるようにする。DB にはもともと保存されていたが、 作成/編集時の ClaudeRuntimePicker 以外では UI に出ていなかった。 - ClaudeRuntimePicker に getClaudeModelLabel / getClaudeEffortLabel を追加 (DB-persisted 値 → ピッカー上のラベルに解決) - SessionDetail の Verify/予算 行の下に「Claude モデル / 思考 effort」を追加 - ScheduleListRow の頻度行の下にモデル/effort のミニ表示を追加
CodeRabbit レビュー対応: - 送信中の Esc / オーバーレイクリックでダイアログが閉じないようガード - 不要な handleConfirm ラッパーを除去し onConfirm を直接呼び出す
Codex のレビュー(PR #267)対応。getClaudeModelLabel / getClaudeEffortLabel は fromPersisted* を通していたため、既知の選択肢に含まれない永続値(旧ビルド由来 の廃止モデルなど)が「デフォルト」に丸められて表示され、実際の設定と食い違う 恐れがあった。null/undefined のときだけ「デフォルト」と表示し、それ以外で 選択肢にヒットしない値は生文字列のまま返すようにする。
fix(desktop): カスタム通知音の削除確認をカスタムダイアログ化
…play feat(desktop): TODO詳細とスケジュール一覧にモデル/Effortを表示
fix(desktop): Excel diff/raw viewer を vibrancy でも不透明に戻す (#263)
- ScheduleListRow の編集/削除メニューが Dialog と干渉して一瞬で閉じる問題を修正 (setTimeout で open を遅延、modal=false) - TodoManager の Enter 送信で IME 変換確定時の誤送信を防止 (isComposing チェック) - ヘッダーの Claude/Codex ステータスをホバー起動 Tooltip からクリック起動 Popover に変更し、中のリンクからサイトを開く形に
Codex レビュー指摘対応。onSelect 内の e.preventDefault() を外すことで Radix の既定クローズ挙動を残しつつ、setTimeout で Dialog を次 tick に 遅延して Outside-click 競合も回避。
fix(desktop): ラベル統一 + スケジュール編集干渉 + IME Enter + ステータスPopover
- フル音声を先行ダウンロードし、波形ビジュアライザで範囲トリムできるようにする - フェードイン/アウト、再生速度(atempo)設定を追加(通知音に反映) - yt-dlp/ffmpeg未インストール時にHomebrewインストールボタンを表示 - YouTubeのタイトルとサムネイルを着信音のカードに表示 - superset-temp-audio:// カスタムプロトコルで一時音声ファイルをレンダラーに提供
Contributor
|
Important Review skippedToo many files! This PR contains 300 files, which is 150 over the limit of 150. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (300)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
概要 (Issue #276)
変更内容
新規ファイル
src/main/lib/temp-audio-protocol.ts-superset-temp-audio://カスタムプロトコル(一時音声ファイルをレンダラーに安全に提供)src/renderer/.../AudioEditor/AudioEditor.tsx- 波形ビジュアライザ、トリムハンドル、フェード/スピードコントロール主な変更
youtube-ringtone.ts:downloadFullYouTubeAudio()を追加、ffmpegフィルタチェーン(atempo/afade)対応、workDirクリーンアップをtry/finallyで確実に実行ringtone/index.ts:checkBinaries,installBinaries,downloadYouTubeAudio,cleanupTempAudio手続き追加YouTubeImportDialog.tsx: マルチステップUI(url → downloading → editor)に刷新RingtonesSettings.tsx: サムネイル表示対応custom-ringtones.ts/shared/ringtones.ts:thumbnailUrlフィールド追加テスト計画
Summary by cubic
Rebuilt the YouTube ringtone import to be fast and visual: we download full audio up front, then let you trim on a waveform, add fades, and change speed before importing. Also shows the video’s title and thumbnail on the ringtone card, and adds smart binary checks with one‑click install on macOS (addresses Issue #276).
New Features
yt-dlp, then visual trim with a waveform editor (drag handles).ffmpegfilters (afade,atempo).superset-temp-audio://.Dependencies
yt-dlpandffmpeg. On macOS, the dialog shows “Install with Homebrew” buttons if missing.Written for commit f679998. Summary will update on new commits.