Repository navigation
Sync upstream c5d40565a (conflicts) - #2
Open
WrittenByAI wants to merge 219 commits into
Open
WrittenByAI wants to merge 219 commits into
WrittenByAI wants to merge 219 commits into
Conversation
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
…tablyai#8772) * Stop usage chips flashing and back off retries for failing providers Two behaviors made the status bar unusable when any provider was persistently failing (bad auth, unsupported plan): 1. Every refetch repainted all providers as 'fetching', so a settled error chip flashed to a loading "…" chip and back on every cycle. withFetchingStatus now keeps settled states (ok/error/unavailable) visible until the new result lands; only providers with no settled state (first load, explicit account-switch clear) show loading. 2. Error providers on the fast activation-retry lane (claude/codex/ grok) were retried every 30s on any focus/show/restore event — forever. Repeated hits drove Claude's tight-budget usage endpoint into 429s, flipping the chip between 'Limited' and its actual error. Retries now back off exponentially per consecutive applied failure (30s, 60s, 120s, … capped at the 15-minute poll cadence) and reset on success or account/target switch. * Count full fetches as failure-lane retries and keep Grok pane refresh feedback - Stamp failing providers' activation-retry clocks when a stale-driven full fetch runs, so the individual failure lane does not fire a redundant retry right after the full fetch already retried them. - GrokUsagePane: manual refresh spinner/disable is now renderer-local, since settled snapshots no longer repaint as 'fetching' during refetches. - Strengthen the backoff-reset test so it distinguishes a reset streak from a stale retry timestamp (CodeRabbit), and add a regression test for the full-fetch retry stamping.
…ning (stablyai#8769) * Surface Grok unified-billing monthly usage instead of a permanent warning Unified-billing Grok accounts have no weekly credits: the /billing?format=credits view returns a config without creditUsagePercent, so the status bar was stuck on 'Grok billing response did not include credit usage' even though the account has a real quota. The default (format-less) /billing view reports it as an included monthly budget (monthlyLimit/used with the billing period). When the credits view has no weekly credit usage, read the default view and surface monthly usage as the provider's 30-day window (already supported by the tooltip and chip visibility for OpenCode Go). If the fallback read fails, the previous 'unavailable' presentation stands rather than escalating to an error chip. * Review fixes: chip renders monthly-only usage; fallback failures keep stale data - StatusBar ProviderSegment: monthly window is chip-visible when it is the sole window (Grok unified billing); fetching/error no-data guards and the icon-only dot now count monthly, matching tooltip.tsx. OpenCode Go chips are unchanged (monthly stays tooltip-only next to session/weekly). - grok-fetcher: monthly-fallback request failures propagate as 'error' so applyStalePolicy keeps the last good monthly snapshot; 'unavailable' is reserved for a successful response without monthly fields. * Settings: show Grok monthly usage row for unified-billing accounts Why: the Grok accounts section only rendered the weekly-credits row, so unified-billing accounts showed a signed-in state with no usage at all. * Use generated localization keys for Grok monthly copy
…6335) * Add in-place screenshot markup for the browser pane Adds a Draw button to the browser toolbar (local and remote/SSH panes) that freezes the visible viewport into a still image and overlays a drawing canvas. Users can mark it up with pen, highlighter, arrow, rectangle, ellipse, and text (color, thickness, undo/redo), then copy the composited PNG to the clipboard to paste into their agent — reusing the existing clipboard screenshot path, so it works for local and remote agents alike. The drawing layer is a renderer-side canvas, so the base image is the only environment-specific piece: local panes use webview.capturePage(), remote panes snapshot the already-displayed screencast frame. Drawing model, compositing, and shape rendering are isolated, unit-tested modules. * Fix browser markup clipboard, transparency, text input, and toggle - Copy now always produces a PNG (the clipboard handler accepts PNG only; a JPEG fallback silently produced an empty clipboard) and targets the clipboard size limit so realistic viewports stay full-resolution. - Flatten the captured base image onto opaque white so transparent page backgrounds no longer ghost the live view through the frozen backdrop. - Keep markup text-input keystrokes local (focus the field explicitly and stop propagation) so the browser pane's global handlers can't swallow them. - Make the Draw toolbar button toggle markup mode off on a second click. * Add markup text sizing, object re-editing, and text-input fixes - Add a font-size control (text tool) to the markup toolbar. - Add a Select tool to re-edit committed shapes: click to select, drag to move, change color/width/font-size of the selection, Delete to remove, and double-click text to re-edit its content. Undo/redo now snapshots the whole shape list so every edit (not just adding) is reversible. - Fix the text input: transparent ink-colored field (no dark theme box over the screenshot) and ignore Enter during IME composition so Japanese conversion no longer commits the annotation early. - Split the grown overlay logic into focused modules (editor hook, pointer + keyboard hooks, canvas renderer, document edits, hit-testing) to stay within the file-size budget. * Improve markup text commit and move toolbar to the bottom - Clicking elsewhere while a text box is open now commits the text and switches to the Select tool, instead of discarding it (the input unmounted before its blur fired) and opening a new box at the click. - Move the drawing toolbar to the bottom (stacked above the actions bar) and open the color/size popover upward. * Fix markup text re-editing alignment, doubling, and selection bounds - Hide the text shape being re-edited from the canvas so the live input isn't doubled by its committed render. - Drop the input's padding/border and use the same font as the canvas so the typing preview sits exactly where the text renders (no shift); field-sizing keeps the box hugging the text. - Estimate text width with full-width (CJK) awareness so the selection box and hit bounds track Japanese text instead of clipping it. - Move the font-size control out of the color popover into its own toolbar button showing the current size. * Unify markup text selection and edit framing Draw the selection box on the canvas even while a text shape is being re-edited (the shape stays hidden so it isn't doubled), and strip the box off the text input so it renders only the editable glyphs. The frame is now identical whether selecting or editing, and the text overlays the same position with the same font, so there's no shift between the two. * Align markup text-edit baseline with the committed render Force the text input's line-height to 1 and zero its height/padding so its top edge matches the canvas textBaseline:'top' render — the editing text no longer sits slightly lower than the committed/selected text. * Address review: markup hit-testing, clear reset, and async guards - Hit-test highlights against their rendered (4x) thickness so the select tool doesn't miss large highlight strokes. - Reset transient editor state (pending text, selection, in-progress drag) on Clear all so a pending input blur can't re-add text and no stale selection lingers over the emptied canvas. - Guard markup completion with the capture token so a stale onDeliver can't reset or error a session the user already cancelled or restarted. - Skip the grab-element keyboard shortcut while the markup overlay is open, matching the already-disabled grab toolbar buttons. - Localize the markup error messages and surface them via a toast (the controller's error was otherwise never shown). * Trim browser markup to a draw-only tool and fix two delivery bugs Reshape the screenshot-markup overlay to match its actual use — a throwaway scribble the user copies once into their agent — and drop the re-editable vector-editor machinery that made it an outlier in scope. - Remove the select/move/restyle/re-edit subsystem: hit-testing, per-shape document edits, drag preview, selection frame, double-click text re-edit, Delete-to-remove, and the CJK text-width heuristic / multi-line text plumbing that only fed selection bounds. Keep pen, highlighter, arrow, rect, ellipse, and text with color/thickness/font-size and undo/redo/clear. - Fix silent data loss on HiDPI: the compositor budgeted only bytes, so a large Retina viewport could produce a PNG under the byte limit but over the clipboard handler's pixel ceiling, which the handler drops silently while the UI still reported success. Composite now scales down to fit maxPixels too. - Fix a stranded UI after a capture failure: the controller stayed "active" with no overlay and an inert Escape. It now returns to idle (capture failure) or back to the drawing surface (compose failure) after the error toast. Net ~660 fewer lines and 3 fewer modules; delivery (freeze → composite PNG → existing clipboard-image paste) is unchanged. Unit tests updated. * Add a one-time discovery hint for the browser Draw button Highlight the new screenshot-markup Draw button the first time it's usable so users — including existing ones — notice the tool. A localStorage-gated, show-once tooltip + highlight ring, independent of the browser contextual tour (which is capped at three steps and only shows to users who haven't seen it). - New use-markup-draw-hint hook: opens once per install when the button is enabled and idle, auto-dismisses, and dismisses on click; skips cleanly when storage is unavailable. - MarkupDrawButton forces its tooltip open with the discovery copy and rings the button while the hint shows. - Localize the hint (drawHint) across en/es/ja/ko/zh; normalize the markup key order alphabetically. * Align the markup copy button label with "Copy Screenshot" The browser pane's two image-copy actions read inconsistently in English — "Copy Screenshot" (the grab flow) vs "Copy markup" (the overlay). Match the new markup button to the existing one: "Copy Markup". English only; other locales already use their own sentence-case convention consistently, so they're unchanged. * Drop the markup Draw hint auto-timeout; dismiss on act only The one-time Draw-button hint no longer auto-dismisses on a timer the user might miss. It now stays open until the user clicks Draw or the button stops being usable (grab started, markup open, or a blank tab), which also fixes a case where losing eligibility mid-hint left the forced-open tooltip stuck over a disabled button. * Optimize markup rendering and harden compose delivery Performance: - Rasterize committed shapes once into an offscreen layer; per-frame paint blits that layer and draws only the in-progress shape, so a fast pointermove stream never re-strokes the whole scene. - Encode the composite PNG off the main thread via canvas.toBlob (a large synchronous toDataURL froze the renderer), with a rAF yield so the "composing" UI paints first. Correctness / review fixes: - Don't overwrite the clipboard after cancel: re-check the capture token between the (now async) compose and onDeliver, closing the window where Escape during composing still delivered the markup. - Keep the offscreen layer and visible canvas in lockstep by threading one measured devicePixelRatio through both paints, and re-measure on window resize so a monitor move (dpr change without a CSS-box change) repaints at the new scale. Blit stretched to the canvas box so a transient mismatch never clips. - Drop the stale "or JPEG fallback" note; compose is PNG-only. * Fix test fixtures for markup pixel-budget clamping to use a truly oversi The old 3840×2160×dpr2 fixture (33.2M px) was already under the 32×1024×1024 (~33.55M) ceiling, so the "never over budget" assertions weren't exercising the clamp path. Switch to 4000×3000×dpr2 (48M px), which genuinely exceeds the ceiling. --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
…l errors (stablyai#8775) * fix(speech): resume interrupted voice model downloads and surface real errors Voice model downloads that died late in the transfer (flaky networks, scanning proxies) failed at the UI's 90% progress cap and restarted from byte zero on every retry, so affected users could never complete any model download (stablyai#8620 / STA-1775). - Retry transient download failures (net::ERR_* transfer cuts, resets, timeouts, HTTP 5xx/429) with an HTTP Range resume from the bytes already on disk, restarting from the canonical URL each attempt since signed CDN redirect URLs expire; bounded backoff, gives up after repeated zero-progress attempts with a diagnosable error. - Correct catalog sizeBytes to exact upstream asset sizes (parakeet v2/v3 are 482/487 MB, not 170/180; paraformer is 1,047 MB, not 115) — also the progress denominator when content-length is missing. - Show the underlying failure cause in the download-error toast and log it in main; it was previously invisible everywhere. All 7 pinned catalog SHA-256 hashes verified against current upstream assets. Live-verified end to end: a transfer killed mid-body resumes with a 206 and completes through checksum, extraction, and validation. Fixes stablyai#8620 * fix(speech): harden resumed model downloads * fix(speech): validate resumed download completion * fix(speech): keep segmented download resumes progressing * fix(speech): don't abandon a download that keeps making progress The retry loop gave up after 8 total failures regardless of progress, so a large model (e.g. the ~1GB Paraformer) over a network that resets every few hundred MB was abandoned mid-download even though every attempt was resuming and advancing — the exact 'stuck then fails' symptom, just later. Replace the all-time failure budget and the separate advancing-segment cap with a single rule: give up only on a genuine stall (repeated attempts with no forward progress) or an absolute request ceiling that bounds a pathological tiny-segment server. Any download that keeps advancing now runs to completion. Verified live under Electron 43: a transfer reset mid-stream on every attempt but advancing each time previously gave up at 67% after 8 attempts; it now completes. All prior resume/cancel/timeout scenarios still pass.
* feat(mobile): add notification opt-in onboarding * fix(mobile): deliver alerts despite desktop focus
…ons (stablyai#8279) Two renderer paths let Promise rejections escape unhandled, where the crash-breadcrumb system retains them and grows the heap until the renderer white-screens (stablyai#8260): - The terminal file-link provider fires a `void Promise.all(...)` whose async mappers probe path existence over SSH. On disconnect these reject with "Remote connection dropped/reconnecting"; add a `.catch` that resolves the callback with no links so the rejection is handled. - "ResizeObserver loop completed" is a benign Chromium quirk; suppress it in crash-diagnostics so it stops filling the breadcrumb buffer and inflating the renderer error count. Scope note: the original fix for stablyai#8260 also proposed Windows pty:kill signal handling (killPtyProcess / isPtyAlreadyGoneError) and a ResizeObserver double-rAF. Those landed on main independently (Windows kill handling by v1.4.135; the pane-title-overlay rect-equality guard from stablyai#2756 already breaks the loop at the source), so this change carries only the two guards still missing upstream. Co-authored-by: Yixian HU <yixian@YixiandeMacBook-Air.local> Co-authored-by: Claude <noreply@anthropic.com>
…ai#8789) * Fix mobile cutover activation and usage refresh loops Co-authored-by: Orca <help@stably.ai> * Fix restored mobile terminal state parity Co-authored-by: Orca <help@stably.ai> * Fix migrated PTY workspace attribution Co-authored-by: Orca <help@stably.ai> * Fix overlapping mobile terminal surface swaps Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
…isi (stablyai#8812) - Replace the force-open Tooltip discovery hint with a Popover (title, body, Try it/Got it actions) that can be dismissed via outside click, Escape, or window blur — Radix's tooltip couldn't handle Electron webview clicks, which never reach the renderer document. - Gate the hint on a new `surfaceActive` prop so it only opens for the visible pane, preventing a portaled layer from anchoring against a hidden/zero-size trigger at the viewport origin. - Add locale strings for the new badge/try/dismiss copy across all supported languages and cover the hook's eligibility/dismiss behavior with unit tests.
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
…#8822) * feat(editor): add Toggle Heading 2, 3, 4 to markdown editor Generalize the heading-1-only collapsible-toggle plumbing to support heading levels 1-4, mirroring the plain heading scales the slash menu and toolbar dropdown already offer. - Introduce ToggleHeadingVariant type + parseToggleHeadingVariant helper - Widen markdown parse/render/sanitize allowlists from heading-1 to 1-4 - Add Toggle Heading 2/3/4 slash commands - Add matching summary sizing in editor + preview CSS - Extend round-trip tests for the new levels Co-authored-by: Orca <help@stably.ai> * fix(editor): preserve toggle heading variants --------- Co-authored-by: Orca <help@stably.ai>
…tablyai#8842) * fix(terminal): reset Windows focus reporting after agent turns * fix(terminal): preserve focus mode across Windows Codex turns
…s for pairing (stablyai#8846) * fix(mobile): detect and repair overriding Windows Firewall Block rules for pairing Firewall inspection now reports an overriding inbound Block rule as blocked instead of false success, the UAC repair removes only conflicting rules for the current Orca executable, TCP pairing port, and Private profile before recreating the scoped allow rule, and the notice re-inspects Windows policy after repair instead of optimistically reporting success. Stale focus-triggered inspections can no longer overwrite a newer result during UAC elevation. Fixes stablyai#8371 * fix(mobile): inspect the ActiveStore so GPO firewall rules are visible Without -PolicyStore ActiveStore the NetSecurity queries read only the local persistent store, so a GPO-applied Block rule was invisible and the post-repair re-inspection could report a false success on managed hosts.
…port (stablyai#7507) * feat(ssh): support Kerberos/GSSAPI hosts via the system OpenSSH transport ssh2 has no gssapi-with-mic support, and adding it would mean forking its protocol layer plus packaging the kerberos native module for three platforms. Instead, route GSSAPI hosts through the existing system-OpenSSH transport, which delegates Kerberos (tickets, SSPI on Windows) to the platform ssh binary. Two tiers, because RHEL-family distros enable GSSAPIAuthentication globally in /etc/ssh/ssh_config and ssh -G therefore reports it for every host: - Targets whose ~/.ssh/config Host block explicitly sets GSSAPIAuthentication yes (imported as target.gssapiAuthentication) try system ssh first, falling through to ssh2 so key auth and credential prompts still work when no ticket is available. - When ssh2 exhausts key/agent auth and the ssh -G-resolved config enables GSSAPI, retry over system ssh before prompting for credentials, so Kerberos-only hosts on distro-default configs connect without a password prompt. Hosts where keys work never leave the ssh2 path. Manual targets flagged for GSSAPI pass -o GSSAPIAuthentication=yes explicitly since they bypass ssh_config. Both tiers work headless (no credential callbacks required). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ssh): harden GSSAPI transport selection (review fixes for PR stablyai#7507) Review fixes on top of the Kerberos/GSSAPI feature branch (s546126/kerberos-ssh): - HIGH: reset useSystemSshTransport on the ssh2 fall-through. doSystemSshProbe sets the flag before spawnSystemSshCommand, which throws synchronously when no system ssh binary is on PATH (outside the probe try/catch). The proactive fall-through previously reset only 2 of 3 transport fields, so exec/sftp kept routing through the failed transport - breaking GSSAPI on Windows-with-Git-ssh and headless Linux. - MEDIUM: throw a cancellation error (not the stale ssh2 authError) when a disconnect supersedes the reactive probe mid-flight, and guard connect()'s catch on disposed, so a deliberate disconnect is not overwritten with auth-failed. - MEDIUM: skip the encrypted-key passphrase prompt when the GSSAPI fallback applies, so a Kerberos ticket is tried before prompting; the general prompt still fires if the probe fails. Adds 3 mutation-verified regression tests and hardens two existing tests to assert the probe actually ran. Not connected to any PR remote. Co-authored-by: Orca <help@stably.ai> * fix(ssh): isolate GSSAPI system transport Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: s546126 <268420947+s546126@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai>
…840) Re-pairing a desktop that was already paired created a duplicate host card (STA-1840). Pairing now resolves the durable host identity by the desktop's pinned publicKeyB64 and reuses the existing id/name, collapses any already-stored duplicates for that key, clears stale relay overlays on a direct-only re-pair, fails closed on unreadable storage, and closes the host's client on pairing success so a reused id reconnects on the newly-paired endpoint. Mobile only. Full mobile suite (1,719 tests) + typecheck/lint/format pass.
…blyai#8709) Reconstruct remote POSIX commands with bounded printf arguments so non-POSIX SSH login shells can forward them without requiring remote base64. Preserve relay and system-SSH stdin, and centralize login-shell flag selection for csh/tcsh compatibility.\n\nValidated against real csh and tcsh OpenSSH targets with built-in and system SSH, including cold relay deployment, stdin upload, PTY I/O, file mutation, and reconnect.
…he sidebar roster (stablyai#8825) * fix(agent-status): reap finished Claude named agents/teammates from the sidebar roster stablyai#8522 stopped one-shot subagents from squatting as idle rows, but named background agents (Workflow/orchestration/ultracode lanes and agent-teams teammates) still piled up permanent "Idle - <type>" child rows for the rest of the session — the reported regression (11 idle rows under an "Orchestration Messages" pane, all idle 3-6h after finishing). Root cause, confirmed against live hook captures (claude 2.1.210): named agents get teammate-shaped ids (a<name>-<hex>) AND now appear in Stop's `background_tasks` as `type: "teammate"` entries whose status stays "running" forever — even after the agent finished. The old code read that shape as a "resumable teammate", so SubagentStop only marked it idle and the fold never reaped it (a present teammate task kept hasTeammateTypedTask true). The rows never left. Fix: the roster now tracks only WORKING children. - SubagentStop removes the child outright (teammate-shaped or not) — it is the reliable finish signal; the teammate task's "running" status is not. - TeammateIdle removes by name as the fallback when a SubagentStop is lost. - A lead Stop's background_tasks still reaps unlisted children: hyphen-free one-shots always, and teammate-shaped rows once a complete inventory shows no teammate-typed task at all. A live named agent whose id never appears is kept only while a teammate-typed task is still present (the done-gate). - Hydration drops persisted idle snapshots so a restart can't re-pile them. Verified live in a dev Electron instance driving a real Claude TUI that spawns four named background agents: pre-fix the pane resolves to done with four persistent "Idle - <probe>" rows; post-fix each row disappears the instant its agent finishes and the roster drains to empty (done, zero child rows). Tests: roster + row-lifecycle + hook-listener suites rewritten to the working-only semantics, grounded in the captured 2.1.210 hook stream (126 passing). Typecheck + oxlint clean. * fix(agent-status): prune persisted idle Claude children * fix(agent-status): persist Claude idle-row hydration cleanup * fix(agent-status): avoid ambiguous teammate idle cleanup * fix(agent-status): reconcile replacement children at roster cap
* Guard add-review-note chord to prevent remount (product B) - Scoped guards consume chord at composer level to prevent remount and leakage - Handle OS key-repeat: ignore when no draft, consume when one is mounted - Clear stale block keys in markdown preview when content renumbers - Flush pending selection before reading targets to fix timing races * fix(editor): repair add-review-note guard tests and close remaining chord gaps - Update the product-B guard tests to the Mod+Shift+A default binding (stablyai#9257 retired Mod+Alt+N as AltGr-unsafe); they asserted the old chord, so seven landed red and the rich-editor repeat test passed vacuously. - Monaco: recompute the annotation target from the live selection at keydown instead of the render-lagged ref, so a chord right after a drag cannot open on the previous selection or miss a fresh one. - Preview: key the stale-block-key cleanup on renderedContent (the DOM the block keys live in), which can lag content during external-edit section preservation. Co-authored-by: Orca <help@stably.ai> * fix(editor): mirror shortcut-guard refs in effects instead of render body CodeRabbit on stablyai#9412: render-body ref writes can leak from a render pass React replays and discards. Move the state->ref mirrors for commentPopoverRef / shouldShowMarkdownAnnotationsRef (MonacoEditor) and activeAnnotationBlockKeyRef (MarkdownPreview) into effects; same-tick keydown paths keep their eager event-handler writes. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
…tablyai#9420) Raise the floating terminal panel from z-30 to z-45 so it sits above notification cards (z-40) but below the modal layer (z-50), preventing notifications from burying the terminal. Raise the toggle button to z-46 to keep it clickable where it overlaps the panel.
* feat(ssh): download folders from remote explorer * fix(ssh): harden remote folder downloads * Add missing getRepo stub to worktree cwd test mock Restoring headless mobile tabs looks up the repo for the active worktree id; the mock lacked getRepo, so the test only passed incidentally. Add it explicitly and return undefined since wt-1 is a worktree id, not a registered repo. * Enable SSH folder downloads, gated for system-SSH connections - Folder downloads require SFTP, unavailable on system SSH (which offers only raw file operations). Add supportsFolderDownload flag to gate the feature in the UI layer. - Reject symlinks at directory-entry level, preventing tree escapes and eliminating unnecessary stat calls. - Check abort signal before opening dialog for better responsiveness when renderer closes. - Log cleanup errors without re-throwing to preserve underlying transfer failures. * Gate SSH folder downloads to SFTP-capable connections Enforce fail-closed gating and add Windows path traversal validation to ensure downloads are only available when explicitly supported and safe. * Gate SSH folder downloads to SFTP-capable connections Enforce fail-closed gating and add Windows path traversal validation to ensure downloads are only available when explicitly supported and safe. * fix(ssh): keep provider types under max-lines after main merge Move FolderDownloadOptions next to the SFTP download implementation and narrow IFilesystemProvider.downloadFolder options to AbortSignal only so types.ts stays within the 300-line oxlint budget when merged with main. --------- Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
…#9070) Only offer 'Proceed Anyway (Unsafe)' if the connected remote runtime advertises browser.certificate-trust.v1 support. Older runtimes cannot honor the request and would fail silently, creating a false affordance. Centralize certificate error normalization to prevent divergence between main and renderer certificate matching, and unify URL redaction for Kagi session token stripping across load-error paths.
…lyai#5785) * Allow hiding Orca Mobile sidebar button once a device is paired Introduce a shared external store (`paired-mobile-devices.ts`) to cache and synchronize the paired mobile device list across the sidebar, Settings pane, and Mobile page. This prevents redundant backend IPC requests when multiple surfaces mount simultaneously. Using this shared pairing status, display an inline "Hide from sidebar" control next to "Orca Mobile" in the navigation sidebar once a device has been paired. This allows users to easily declutter their sidebar after completing setup. * Handle device load errors with recovery on focus/reconnect - Add error flag to distinguish failed device loads from empty device lists - Recover from transient startup IPC failures by retrying on window focus or online - Validate revoke device success before routing or showing success toast - Only show hide control after first device is actually paired - Use snapshot getter to avoid manual device ref tracking in MobilePane * Handle device load errors with recovery on focus/reconnect - Shared module-level listeners for failed load recovery reduce IPC spam compared to per-consumer event listeners on every focus/online event. - Optimistically remove revoked devices if post-revoke reload fails, keeping success toast and intro routing correct. - Prevent repo/branch label overflow with CSS text truncation. - Fix HTML entity rendering: use plain spaces instead of in translations so React text nodes render them correctly. - Add comprehensive tests for the paired devices hook covering load, navigation, revoke, and error recovery paths.
…9437) Resolve Microsoft Store PowerShell App Execution Aliases to their real package executable before ConPTY spawn, while preserving explicit PowerShell choices and the existing fail-safe fallback chain.
…tablyai#9398) * fix(composer): show full worktree path on hover in Run on dropdown Co-authored-by: Orca <help@stably.ai> * refactor(composer): use native title tooltip for Run on paths Co-authored-by: Orca <help@stably.ai> * fix(composer): reliable native-style tooltip for Run on paths The native title attribute won't re-trigger between adjacent rows in Chromium, so the tooltip flickered/dropped as you moved down the list. Use the shared (controlled) Tooltip instead, styled compact + arrowless to read like an OS tooltip; the app-level provider's skip-delay makes row-to-row hovers reliable. Co-authored-by: Orca <help@stably.ai> * fix(composer): stop Run on path tooltip wedging shut Radix's uncontrolled hover state could flash the tooltip open then wedge it closed inside the cmdk list (shared pointer-transit state getting stuck), so re-hovering never reopened it. Drive open state with a controlled hover-intent handler (open after a delay, close after a short grace) so re-entry always reopens and transient enter/leave thrash debounces to a stable state. Co-authored-by: Orca <help@stably.ai> * fix(composer): rebuild Run on path tooltip as a portal (no loop/wedge) Both the native title and Radix Tooltip (controlled or not) misbehaved inside the cmdk list: flashing, wedging shut, or opening/closing in a loop. Replace with a self-contained tooltip that renders a fixed, pointer-events-none portal on document.body and only changes state on pointer enter/leave/down (never pointermove). Opening it cannot reflow the list or become the hover target, so it structurally cannot create the enter/leave feedback loop. Co-authored-by: Orca <help@stably.ai> * fix(composer): trigger Run on path tooltip on the path line, always below - Scope the hover trigger to the truncated path line itself (not the whole row), so only hovering the path reveals it. - Always position the tooltip below the hovered line for predictable placement (was flipping above near the viewport bottom). Co-authored-by: Orca <help@stably.ai> * fix(composer): anchor Run on path tooltip under the path line The old placement anchored to the window's right half when the trigger sat past viewport-center, which shoved the tooltip to the far right edge of the window, disconnected from the dropdown. Always left-align it directly under the hovered path line and cap max-width to the remaining viewport width so long paths wrap instead of overflowing. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
… Windows (stablyai#7742) (stablyai#9311) * test(e2e): prove the terminal daemon survives a main-process crash on Windows (stablyai#7742) Add a win-crash-survival e2e harness (sibling to win-update-e2e) that force-kills ONLY the packaged app's real Electron main (resolved via app.evaluate -> process.pid, /F no /T) and asserts the detached orca-terminal-daemon.exe plus its ConPTY shell survive with no pwsh 0xE9 FailFast, then that a relaunch re-adopts the SAME daemon and the reattached UI binds to the SAME survivor shell (proved via a per-shell env sentinel read back through the restored terminal). This guards the stablyai#7742 fix (standalone relocated daemon that outlives main death) against regression. A directional `--expect orphaned` profile fails on a fixed build, keeping the survival assertions honest. Windows-only; reuses win-update-e2e app-driver/daemon-process modules. * test(e2e): harden Windows crash-survival proof * test(ci): keep crash survival gate durable * test(e2e): tolerate restart hydration navigation * test(e2e): prove exact shell input after crash * perf(ci): avoid crash harness installer rebuilds * test(ci): harden crash survival evidence and cost * test(e2e): fail closed on authoritative crash target * test(e2e): fail closed on crash liveness evidence --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(claude-accounts): block adding a duplicate Claude account (stablyai#6616) Adding the same Claude subscription via Settings -> AI Provider Accounts appended a second managed account with an identical email/organization, producing duplicate rows that confused account selection and rate-limit tracking. doAddAccount now checks the captured identity against existing accounts before appending and rejects the add with a clear message when it matches. Identity is keyed on email + organizationUuid scoped to the runtime (host/WSL distro), so the same email can still be added under a different organization, and both sides are normalized so a legacy account persisted before the runtime fields existed still matches. * fix(claude-accounts): skip rollback I/O for duplicates
…ablyai#8911) The right-sidebar Checks panel merge control merged the PR/MR immediately when a strategy was chosen from its dropdown, with no confirmation — so opening the dropdown to switch strategies (e.g. Squash -> Merge commit) merged on the spot. Every other PR/MR merge surface (PullRequestPage, GitHubItemDialog, TaskPage) already confirms first, and the close/reopen path in this same hook confirms too; only the hosted-review merge path was missing the gate. Add the same confirmation dialog to the hosted-review handleMerge, provider-aware so it reads "Squash and merge PR #N?" / "Squash and merge MR !N?". Selecting a strategy now asks before merging; the merge still runs when explicitly confirmed. Fixes stablyai#7943
…idebar (stablyai#9463) When a runtime host identity (runtime:<envId>) is removed from the saved list, retire the repos, project host setups, and worktree rows it owned so the same physical checkout stops duplicating in the sidebar. setRuntimeEnvironments now diffs the previous in-memory saved list against the new one and purges state owned by any environment that just left, routed through the existing worktree purge cascade (tabs/PTY/browser/editor/agent maps). Scoped to the removal diff, not an absolute keep-set, so a serving instance's locally-persisted runtime-stamped repos (whose env id was never in this instance's saved list) are never torn down. A tombstone set (removedRuntimeEnvironmentIds) guards the three repo-catalog merges so an in-flight fetch for a just-removed env can't re-add purged repos, while a runtime env merely absent from a not-yet-hydrated saved list still merges normally. Refs stablyai#8881
…ng (stablyai#9292) * fix(source-control): bound Create PR eligibility probe so it can't hang The local Electron eligibility path had no renderer-side timeout, so a hung main-process git/gh subprocess (plausible on Windows) left the Create PR header stuck in its "Checking whether this branch can create a pull request..." loading state with the button permanently disabled. The runtime-hosted path was already bounded by callRuntimeRpc's 30s timeout; the local path was not. - Wrap the local window.api.hostedReview.getCreationEligibility call in a 30s timeout so a never-settling probe rejects instead of hanging. - On probe failure/timeout, synthesize a local-status blocker snapshot (dirty -> no_upstream -> needs_sync -> needs_push, mirroring the main-process ordering) so a dirty branch still offers the commit/preparation intent and a clean unpublished branch the publish intent, instead of an inert disabled button. Fall back to the retryable failed state only when local status can't determine a blocker. Extracts the eligibility snapshot builders into their own module to stay under the max-lines cap. Provider-aware copy and host-scoped (local/SSH/runtime) behavior preserved; no git commands changed. * fix(source-control): guard synthesized eligibility fallback (default branch, provider parity) Mirror the main-process canReturnLocalBlocker guard in the renderer's probe-failure fallback so a timed-out/failed probe can't synthesize a 'dirty'/commit blocker on the default branch or a detached HEAD — which would have surfaced an enabled Create PR that commits onto the base branch. The ahead-only needs_push case is likewise dropped to match main (it won't offer a push it can't auth-check first). Infer the provider from the repo remote host so a GitLab (etc.) repo with no linked review shows its own review copy during loading and after a failure instead of the GitHub default. Also: assert the timeout error by type, and rename the snapshot test to match its module. * fix(source-control): keep eligibility timeout stable * fix(source-control): keep eligibility timeout bounded * test(source-control): assert eligibility effect dependencies --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Fix diff editor flashing "Loading…" on every save Saving a file open in a single-file diff tab blanked the editor to a centered "Loading…" for a frame on every content-changing save. The DiffViewer's React key embedded the modified-content signature, so each save changed the key and forced a full unmount/remount of the Monaco diff editor — which shows its built-in loading placeholder while re-initializing, and also discarded scroll position and undo history. The modifiedModelKey → modifiedModelPath rotation already refreshes diff content in place without a remount, so the signature in the outer key was redundant. Drop it; keep the view-state scope and explicit reload nonce for a stable per-tab identity. Adds a regression test asserting a save does not remount the diff editor. * Harden in-place diff model refresh * Preserve diff view state across model swaps * Fix retained diff model disposal race
…abases (stablyai#8924) * fix(agent-history): keep the app responsive while scanning huge OpenCode databases (stablyai#8864) Opening or refreshing Agent Session History froze the entire app when OpenCode's opencode.db had grown to multiple GB (reporter: 29 GB with 20+ live opencode writers). All OpenCode SQLite reads ran synchronously (node:sqlite DatabaseSync) on the Electron main-process event loop, the discovery query evaluated a COUNT+json_extract subquery for every session before LIMIT, and the preview query JSON-parsed every part blob of each session. Live writers bump session.time_updated continuously, so the mtime parse cache missed every 15s/focus/manual refresh and the multi-tens-of-seconds freeze repeated forever. Fix: - Run OpenCode SQLite discovery and per-session parsing on a persistent worker thread (lazy spawn, unref'd, idle teardown, FIFO dispatch, per-call timeouts, crash-loop cap). Faults surface as per-source scan issues instead of stalls; other providers' results always arrive. - Fall back to the in-process reader when no worker bundle exists, surfacing a degraded-mode scan issue so a persistent fallback cannot silently reintroduce the hang. - Bound the queries: sort+LIMIT sessions before computing message counts, and source previews from the newest 100 messages via the (session_id, time_created, id) index instead of scanning every part. Measured on a 22 GB synthetic DB matching the reporter's shape: main- process IPC RTT during a fully cache-invalidated scan went from a 32.8 s continuous block (UI click timeout) to 1 ms max, with the panel populating normally (previews and message counts intact). * fix(agent-history): drop the cached worker handle after a clean exit and document worker-client exports A worker that exits cleanly on its own left this.worker pointing at the dead thread; the next request would post into it and stall to its timeout instead of respawning. Clean idle exits now drop the handle without counting as a death. Also adds JSDoc to the new exported scan-worker surfaces. * fix(agent-history): keep OpenCode scans off the main thread * fix(agent-history): align OpenCode recency ordering
…d false exits (stablyai#9258) * fix(terminal): confirm the agent from ConPTY console presence to avoid false exits On Windows the foreground scan is a whole-process-table PowerShell fork that, under load, exceeds its timeout or returns an incomplete snapshot — the completion coordinator then reads the shell as the foreground and fires a false "agent done" while the agent is still working. While a recognized agent is still active, confirm it with a cheap ConPTY console-membership read instead: a child process still attached to the console means the agent is working, so keep it without the whole-table scan. Fall through to the authoritative scan only when the console is shell-only (the agent likely exited). No-op off Windows, and never worse than the existing degraded-scan fallback. * fix(terminal): apply the ConPTY console-presence check on the daemon foreground path Windows PTYs are hosted by the terminal daemon, whose foreground-identity refresh (not the local provider) is what runs by default — it retired the cached agent on a timed-out or incomplete CIM scan, so foreground reads fell back to the shell and fired a false "agent done" while the agent was working. Mirror the local-provider fix on the daemon path: a degraded scan (available:false) no longer retires the identity; an authoritative scan that resolves no agent is confirmed against a ConPTY console-membership read before retiring (an incomplete snapshot with a child still attached keeps the agent); and the sync foreground read serves the cached agent across a shell fallback on Windows (an unreliable exit signal under load) until the background refresh authoritatively retires it. The membership read stays off the sync path. * fix(terminal): exclude the console-list helper's own process from ConPTY membership The console-membership helper attaches to the console to read it, so GetConsoleProcessList counts the helper's own forked process. A bare shell therefore read as [helper, shell] and looked like it still had a child, so a genuine shell-only console (an exited agent) was never detected — the foreground refresh held the exited agent's identity indefinitely. Drop the helper's own pid before judging membership; a remaining set of only the shell (or the AttachConsole-failure fallback) is not child proof. Fixes real-exit detection on both the daemon and local foreground paths. * fix(terminal): require conclusive ConPTY exit evidence * fix(terminal): absorb delayed ConPTY helper errors
* Make plain-text file:// links clickable in the terminal Printed file:// URIs (e.g. a report path echoed by a tool or agent) were neither http links nor bare filesystem paths, so the terminal's URL and local-path detectors both skipped them and the link was dead. Orca already resolves and opens file:// URIs for OSC 8 hyperlinks. Reuse that exact resolver for plain-text URIs so a printed file:// behaves the same whether or not the emitter wrapped it in an escape sequence: - Promote the (dependency-pure) file-url target resolver into src/shared so the OSC path and the new plain-text path share one implementation. - Add a file:// detector that decodes the URI to a filesystem path and routes it through the existing file-link pipeline (existence probe + openDetectedFilePath), so line/col anchors, %20, Windows drive paths, html-in-browser, editor reveal, and SSH/runtime resolution all just work. Lines without file:// are unchanged: the pass short-circuits to the prior result, so only file://-bearing lines gain a link. - Add unit + integration coverage for detection, decoding, and no-double-link. * Harden plain-text file URI detection * Split terminal file link detection modules
* Standardize per-repo Source Control AI save UX with global recipe patter Extract draft/save logic into `useRepositorySourceControlAiGlobalUx` hook and serial persist queue, matching how global action recipes save: selects persist immediately, CLI args and command template draft until per-action Save. * Support draft/commit for per-repo custom-command input - Drafts custom commands locally on keystroke without backend writes - Commits to persistence only on blur or mode selection change - Adds forceRepoMode local state to fix mode-select snapping with empty fields - Mirrors existing pattern used for action recipe text editing - Improves persist queue with repo-switch safety (returns boolean, pins repoId at schedule time) - Adds comprehensive test coverage for settings hook and persist queue * Fix forceRepoMode logic to preserve REPO intent mid-edit Always set forceRepoMode based on field content instead of only when a repo command is present. This preserves REPO intent when the field is empty during editing, only exiting when a value is explicitly entered. Add test for draft discard and revert behavior.
* Restructure mobile pairing setup into a clear stepped layout * Rework mobile pairing UI: radio-style path selector with sign-in gate fo * fix(mobile): resolve mobile-pairing review findings Applies the code-review findings for the reworked pairing UI: - Clear a displayed Relay QR on sign-out in both MobilePage and MobilePane (the rework dropped the wasSignedInRef watcher, leaving a stale Relay QR next to a "sign in required" prompt). Anywhere stays selected; the QR re-mints as local-only. [#1, #3] - Extract useMobilePairingConnectionMode so both panes resolve the saved preference identically instead of duplicating the state + resync effect. [#6] - Delete ~24 i18n keys the rework left unreferenced; sync locale catalogs to parity. [#4] - Give the connection-path radiogroup roving tabindex + arrow-key nav and document why it diverges from SettingsSegmentedControl. [stablyai#7] - Add MobilePane.test.tsx (previously untested safety logic) and a MobilePage saved-local-only restore test. [#2, #5] Verified: 22 targeted tests pass, typecheck + oxlint clean, localization catalog/coverage and max-lines ratchet green. * fix(mobile): resolve adversarial-review findings for pairing UI Address accepted findings from the mobile pairing UI rework: - MobilePane: add a request-generation epoch so a late getPairingQR response can't paint a stale Relay QR after sign-out, a mode switch, or an address change; arm rotation when discarding a pending mint. - MobilePage: on sign-in, upgrade a signed-out local-only fallback QR to Relay (invalidate + rotate-regenerate); handle null->connected too. - Extract useMobilePairingQrInvalidation so both sign-in/out edges and cross-window preference syncs invalidate/re-mint the QR consistently. - MobilePane: clear + rotate the QR when the selected address changes (manual pick or refresh-driven) so it can't encode the old endpoint. - MobilePairingConnectionOptions: guard the Sign in CTA on configured; show an Unavailable panel on unconfigured builds instead of dead CTA. - Drop the duplicate sign-in helper from MobilePairingSetupSection. - Remove dead i18n keys (title, recommended, signInToGenerate) and add relayUnavailable across all locale catalogs. - Add tests: deferred sign-out/mode-switch races, sign-in upgrade, cross-window sync, unconfigured build, arrow-key radiogroup. TODO left for the relay-label-honesty finding: getPairingQR does not expose the actually-encoded mode when an automatic offer degrades to local-only, so the mismatch can't be surfaced without a new return field. * fix(mobile): resolve relay-pairing deep-review regressions - MobilePane: invalidatePairing now clears loading so a superseded mid-flight generate can't wedge Generate disabled forever - MobilePage: stop auto-minting a local-only QR under the Relay label when signed out with Anywhere; gate Step 2 auto-generate and the Generate button on a shared canMintMobilePairingOffer helper, align with Settings, clear QR + loading on sign-out, mint Relay on sign-in - qr-invalidation: clear pairQrDataUrl (and loading) on every invalidation path so a stale QR can't stay scannable during rotation - Strengthen MobilePane/MobilePage tests for the aligned behavior and stuck-loading coverage - Translate relayUnavailable in es/ja/ko/zh Co-authored-by: Orca <help@stably.ai> * fix(mobile): refuse to mint QR when signed-out with Anywhere selected Replace silent degradation of Anywhere mode to local-only QR with explicit refusal. Add canMintMobilePairingOffer guard across all mint paths (address change, network invalidation, connection mode switch). This ensures the UI honestly encodes the selected pairing path. Improve keyboard focus visibility on the path selector by adding a persistent focus ring. * fix(mobile): detect and flag Relay provisioning degradation When Relay provisioning fails during automatic (Anywhere) pairing offer creation, the offer silently degrades to local-only. This confuses users who selected Anywhere expecting cellular capability. Add connectionMode field to the pairing offer result to expose what the offer actually encodes. The UI now flags degradation when the offered mode mismatches the user's selection. Also move credential rotation logic to the main process: rotate when requested mode differs from the pending token's encoded mode. This ensures QR codes displayed under an old policy can't pair under a new one, and windows reminting after preference sync converge on one token. Rename MobileRelayBetaAvailability → MobileRelayBetaNotice. * test(mobile): verify pairing codes don't flash during Relay mint Assert that the pairing QR and URL remain hidden while the Relay mint is pending, preventing confusing intermediate states when signing in unlocks Relay. --------- Co-authored-by: Orca <help@stably.ai>
Resolved by fork-sync agent; see report.
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.
Upstream
c5d40565amerged into the fork with conflicts.Unresolved markers remain in: (none, but agent was unsure)
Fork files upstream also touched: package.json, pnpm-lock.yaml, src/main/github/client.ts, src/main/ipc/github.test.ts, src/main/ipc/github.ts, src/main/runtime/orca-runtime.ts, src/main/runtime/rpc/methods/github.ts, src/main/runtime/runtime-rpc.ts, src/preload/api-types.ts, src/preload/index.ts, src/renderer/src/assets/rich-markdown-editor.css, src/renderer/src/components/editor/EditorContent.tsx, src/renderer/src/components/right-sidebar/SourceControl.tsx, src/renderer/src/i18n/locales/en.json, src/renderer/src/i18n/locales/es.json, src/renderer/src/i18n/locales/ja.json, src/renderer/src/i18n/locales/ko.json, src/renderer/src/i18n/locales/zh.json, src/renderer/src/lib/language-detect.ts, src/renderer/src/store/slices/diffComments.test.ts, src/renderer/src/web/web-preload-api.test.ts, src/renderer/src/web/web-preload-api.ts, src/shared/types.ts
Fork sync conflict report — upstream
c5d40565a792e11372(v1.4.142-rc.1, the fork base recorded in CLAUDE.md)c5d40565a— "Clarify Orca Mobile pairing connection paths (Clarify Orca Mobile pairing connection paths stablyai/orca#9425)"sync/upstream-c5d40565a(trackingfork/main)MERGE_HEADpresent), all conflicts already resolvedHeadline
There were no conflicts left for me to resolve. By the time I inspected the
worktree, git reported "All conflicts fixed but you are still merging":
git ls-files -u→ empty (no unmerged index entries)<<<<<<</=======/>>>>>>>markers anywhere in the working treeMERGE_RRis empty, so rerere did not silently auto-apply any recordedresolution — nothing was resolved from a cached prior decision
So this merge resolved textually on its own. I made no edits. I did not run
git add,git commit, orgit merge.Because a clean textual merge of this size can still break the fork
semantically (upstream refactors a file, git merges it fine, and the fork's
hook is left orphaned), I spent the effort on verifying the fork's integration
points instead. Results below.
Files changed by both sides (the real auto-merge collision candidates)
Of the 50 files the fork changed since the merge base, 19 were also changed
by upstream. Every one auto-merged:
package.json,pnpm-lock.yamltldrawand@tldraw/assetsstill pinned to5.2.4src/renderer/src/lib/language-detect.tssrc/shared/types.tssrc/main/ipc/github.tsgh:uploadIssueImagehandler intact (line 429)src/main/github/client.tsuploadIssueImage(line 94)src/main/runtime/rpc/methods/github.tsgithub.uploadIssueImageregistered (line 512)src/main/runtime/runtime-rpc.tssrc/main/runtime/orca-runtime.tssrc/preload/index.ts,src/preload/api-types.tssrc/renderer/src/web/web-preload-api.ts(+ test)src/renderer/src/components/editor/EditorContent.tsxTldrawViewerintactsrc/renderer/src/assets/rich-markdown-editor.csssrc/renderer/src/i18n/locales/{en,zh}.jsonsrc/main/ipc/github.test.ts,src/renderer/src/store/slices/diffComments.test.tsNotably, the two files carrying the fork's canvas-notes logic —
src/shared/diff-comments-format.tsandsrc/renderer/src/store/slices/diffComments.ts— were not touched byupstream at all, so the
source: 'canvas'design is uncontested.Per-area detail
1. Canvas viewing (
.tldr→ tldraw)language-detect.ts, only the.protomapping(
'protobuf'→'proto'), at line ~69.'.tldr': 'tldraw'at line 28.entries. Git merged both.
'.tldr': 'tldraw'is still present andthat
isTldrawstill flowseditor-panel-render-model.ts:162→EditorPanelShell.tsx:106,147→EditorContent.tsx:863→ lazyTldrawViewer(EditorContent.tsx:48).2. Canvas notes (
DiffCommentwithsource: 'canvas')source === 'canvas'predicate and theselectedTextshape-summary description.diff-comments-format.tsstill carries the canvasbranch and the "Source: canvas" serialization.
tldraw-canvas-notes.ts:1is stillimport type { Editor, TLShape } from 'tldraw'— a type-only import. Per CLAUDE.md, a runtime binding here would drag in
tldraw's rich-text module and break under Vitest/Node ESM resolution.
3. Canvas → GitHub issue
This is the surface that crosses main/preload/runtime, and upstream touched
every file in the chain. I traced the whole path end to end and it is
complete and consistent:
CANVAS_ASSET_BRANCH = 'orca/canvas-screenshots'still set(
issue-images.ts:22), matching the fork's design and its tests.case to keep working — the web/SSH branch is the one that would have failed
silently if upstream's RPC-registry refactor had dropped the fork's entry.
4. Localization
en.jsonandzh.json.TldrawViewer,TldrawSelectionOverlay,TldrawIssueDialog, plus canvas strings.locales (
en,es,ja,ko,zh) — parity holds, which is whatverify:localization-catalog/verify:localization-coverageenforce inpnpm lint. Also confirmed upstream did not add any new locale file thatwould be missing the fork's keys.
5. max-lines ratchet
AGENTS.md forbids disabling
max-lines, so a merge-induced overflow would needa real refactor. Checked: every file both sides changed that is large
(
EditorContent.tsx,orca-runtime.ts,ipc/github.ts,web-preload-api.ts)is already listed in
config/max-lines-baseline.txtasinline-exempt. Thefork's own files are under budget (largest is
EditorPanelHeader.tsxat 362raw lines against a 400/600 budget that skips blanks and comments), and
upstream did not touch them, so their size is unchanged from the pre-merge
state that was already passing.
What a human must still decide
Nothing about conflict resolution — there is no ambiguous hunk awaiting a call.
The open items are all verification, not decisions:
pnpm typecheck,pnpm lint, andpnpm testall require command approval and this session was non-interactive, so
I did not run them and cannot claim they pass. My verification above is
static (grep/diff-based). Given the size of this jump — 1544 files, a
+136k-line diff, including upstream changes to the runtime RPC layer — the
gates should be run before this merge is committed. Per the fork's own note,
the test gate requires Node 24.
.protolanguage-id rename ('protobuf'→'proto') is abehavior change in a file the fork also edits. It does not affect
.tldr,but if anything in the fork keys off the string
'protobuf', confirm it.MERGE_HEADis still presentand the index is fully staged. Concluding the merge is the human's call.
Verdict
No conflict markers remain, and no fork intent was lost that I could detect by
static inspection. The residual risk is limited to what only a full
typecheck/lint/test run can surface.