* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules
Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day
minimum-release-age supply-chain guard; nothing here needs it). The
bump is a no-op on the existing config.
Enable 3 error rules (backlog autofixed to zero in this commit) and
4 warn rules (surface signal without gating CI):
error (autofixed, behavior-preserving):
- unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:)
- typescript/no-import-type-side-effects (~36: all-inline-type -> import type)
- unicorn/no-array-reverse (19: copy-then-reverse -> toReversed)
warn (real signal, current fires are test-only/correct):
- unicorn/no-array-fill-with-reference-type (aliasing footgun guard)
- typescript/no-unsafe-function-type (bans bare Function type)
- unicorn/prefer-array-flat-map (map().flat() -> flatMap())
- unicorn/prefer-regexp-test (.match() in bool ctx -> .test())
mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix
ran from root and covered mobile/ too.
Verification (all green): oxlint 0 errors (root+mobile+aux configs),
oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed,
builds (electron-vite + web + cli) succeed. node: rewrites confirmed to
skip embedded SSH/CLI string payloads (AST-only); all toReversed sites
verified to operate on fresh copies or write-once locals.
* chore(lint): bump mobile oxlint to 1.71 so inherited rules parse
mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which
lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since
mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile &&
oxlint' failed to parse the new rule. Bump mobile to match root (1.71).
Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit
pass, vitest 978 passed / 0 failed.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(mobile): add commit failure recovery panel with AI fix action
- Surfaces a "Commit failed" panel with a one-tap AI fix button when a
git commit fails in the source control view or PR creation flow
- Detects commit failures specifically during the committing progress
step and captures staged entries and commit message for context
- Extracts commit failure summary and prompt logic into
`src/shared/source-control-commit-failure.ts` and PR checks prompt
into `src/shared/pr-checks-fix-prompt.ts` so both desktop and mobile
share the same implementations
- Adds auto-find of an available Metro port starting from 8081 and
extracts expo CLI bootstrap into `mobile-expo-cli.mjs` shared by
`start-emulator` and a new `start-expo.mjs` wrapper
* Share source-control AI prompts and simplify mobile PR actions
- Extract conflict, check-fixing, and commit-failure prompt builders
to shared modules for reuse by both desktop and mobile.
- Configure Metro in the mobile package to watch and bundle modules
from the repository-root shared directory.
- Remove the desktop-style merge method picker from the mobile PR
actions panel, opting to use repository defaults automatically.
- Refactor mobile hosted review creation and git preparation logic
into dedicated helper files.
* Implement automated git preparation workflow for mobile PR creation
Introduce a structured hosted review intent preparation workflow to handle
staging, AI commit message generation, committing, and pushing changes
automatically before displaying the pull request composer on mobile.
- Map creation block reasons to descriptive user-facing validation errors
(e.g., dirty working tree, default branch, detached head) to match desktop.
- Decouple hosted-review business logic into a dedicated service helper.
- Update source control runner hooks to handle the new preparation flow.
* Refactor mobile PR creation to run intent and open URL directly
Remove MobilePrComposeSheet and the local compose form, moving instead
to a direct PR creation workflow that matches the desktop experience.
- Add runMobileHostedReviewCreateIntent to handle the full prepare,
push, and create sequence.
- Replace useMobileOpenPrSheetRunner with useMobileCreatePrRunner to
trigger the creation workflow and directly open the created PR URL.
- Simplify state management by removing showPrSheet, prPrefill, and
associated local compose sheets.
* Propagate git status and commit state on PR creation failure
Update `MobileHostedReviewCreateIntentOutcome` and the local change
commit helper to include optional `committed` and `status` fields in
their failure results.
This ensures that if PR preparation fails, callers still receive the
current repository status and know if their local changes have already
been committed.
* Add tests for mobile hosted review creation flow
Introduce unit tests for runMobileHostedReviewCreateIntent to verify
different scenarios of creating a hosted review on mobile, including:
- Successful flow including staging, committing, pushing, and creating
- Eligibility block handling (e.g., authentication requirements)
- Error reporting when creation fails after an automatic commit
* Block mobile PR creation on unresolved conflicts and refresh status
Prevent creating a hosted review on mobile when there are unresolved
merge conflicts. Also, return the latest git status on failures and
reload it in the UI to keep the source control screen in sync.
* Prefer fetched PR head SHA over cached status SHA for PR checks
On mobile, a create command can commit before opening the review,
meaning the fetched PR's head SHA is fresher than the route's cached
status SHA. Prioritizing the fetched PR head SHA ensures we fetch checks
for the most up-to-date commit.
* Fix mobile PR creation errors and validate branch presence
- Reject branch matches when the status branch is null or missing to
prevent PR creation when the branch is lost.
- Display actual PR creation errors in the sidebar instead of silently
ignoring them on failure.
- Trim leading and trailing whitespace from the base branch reference
before persisting the worktree link.
* fix: address pr-bug-scan validated finding from #6315
Protect fenced/inline code spans from the HTML stripTags pass in normalizeMobileMarkdownPreviewHtml; blocks stripTags(next) at mobile-markdown-preview-html.ts:95 from deleting <...> inside code.
* Preserve non-tag angle brackets in markdown preview HTML parser
Only strip standard/known HTML tags instead of matching any `<...>`
pattern. This prevents false-positive stripping of generic types
(e.g., `Array<string>`) or math comparisons (e.g., `1 < 2`) in prose.
Additionally, robustify markdown code block preservation by using a
Private Use Area prefix (`\uE000`) and dynamically padding it to avoid
collisions with literal input content.
* Protect code spans when stripping HTML tags in mobile preview
Ensure HTML-like content within code spans is not erroneously removed
by the tag stripping logic. We now protect markdown code blocks and
restore them after stripping tags and decoding HTML entities.
---------
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
- Centralize and align auto-merge eligibility logic across web and
mobile clients.
- Use the `enablePullRequestAutoMerge` GraphQL mutation instead of
`gh pr merge --auto` to prevent immediate merges on clean branches.
- Fall back to `gh pr merge --auto` when a merge queue is required on
the base branch.
- Hide the auto-merge control when only optional checks are pending.
* feat(mobile): surface "Link an existing PR" in PR sidebar empty state
The mobile link-PR building blocks (MobileLinkPrForm, linkMobilePr,
parseGitHubPrReference) existed and were tested, but had no entry point —
only unlink was wired up. Add a "Link an existing PR" action to the no-PR
empty state that opens MobileLinkPrForm and refetches the sidebar on
success, mirroring desktop's link flow (GitHub-scoped via worktree.set).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Polish mobile PR link empty state
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
- Delays input focus by 220ms to ensure animating bottom drawers settle before the soft keyboard is requested, improving focus reliability on mobile.
- Replaces standard TextInputs with this component in tasks workspace creation and the NewWorktreeModal.
* Automatically link newly created pull requests/merge requests to the
worktree using the provider-specific metadata key (e.g., linkedPR,
linkedGitLabMR) and persist the submitted base ref.
* Prefer worktree-specific base ref returned by worktree.show when
resolving the comparison base ref.
* Handle metadata linking failures gracefully as a non-fatal warning
without blocking the pull request creation flow.
When the hosting provider (GitHub) reports conflicts but a local merge
simulation is clean, we now mark the conflict summary as locally clean.
This state is surfaced in both the desktop and mobile sidebars with a
clear explanation and a copyable set of commands to trigger a remote
mergeability recalculation via an empty commit and push.
- Resolve the initial repository in the mobile NewWorktreeModal using
the last-visited worktree's repo ID loaded from AsyncStorage.
- Extract and share new-workspace dialog repository selection and
resolution logic to src/shared, mirroring it on mobile to bypass
Metro bundling limits.
- Add unit tests for last-visited repo reading and selection resolution.
* Allow deleting downloaded speech models on desktop and mobile
Implement safe deletion of local speech models across desktop and
mobile platforms.
- Add a shared helper to coordinate STT service teardown, file deletion,
and voice settings cleanup.
- Prevent deleting speech models that are currently in use or starting.
- Update desktop and mobile settings UIs to expose the delete action.
* Track multiple pending model deletions and update action disabling
- Replace the single pending delete ID string state with a Set of IDs on desktop settings to track multiple concurrently deleting models.
- Switch the desktop delete action to use the standard themed Button component.
- Disable all row interactions on the mobile voice model list when any model action is busy.
- Add unit test coverage verifying that individual row delete button disabled states behave correctly.
* Add worktree-list sidebar for mobile tablet/foldable layouts
On wide canvases (tablet/foldable, >=700pt) the per-host worktree list now
renders as a persistent left sidebar with the routed screens (terminal,
source control, review, accounts, tasks) shown in a detail pane to its
right — mirroring the desktop's sidebar + center layout. Phones keep the
existing single-pane stack navigation unchanged.
- Reuse the existing worktree-list screen as the sidebar via an `embedded`
mode (props for hostId/action, mount-driven fetch since a sidebar is never
the focused route, hide-sidebar control in place of the back button, and
open-into-detail-pane navigation that replaces rather than stacks).
- The default host route renders an empty WorkspaceDetailPlaceholder on wide
layouts (the list lives in the sidebar) and the full screen on phones, so
exactly one instance mounts either way.
- Hide button collapses the sidebar to give the detail pane full width; an
elevated reveal tab brings it back.
- Detail-pane screen transitions use `animation: 'none'` while the split is
active so workspaces swap instantly instead of sliding and flashing the
screen beneath.
- Drag the sidebar's right edge to resize (clamped 280-560pt, detail pane
kept >=320pt, tap-transparent so list rows still work); width persists via
AsyncStorage.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Address PR review feedback (#5505)
- Clamp the restored/persisted sidebar width against the current window and
re-clamp when the window shrinks, so a width saved on a larger device can't
starve the detail pane below MIN_DETAIL_WIDTH. Extract a shared
clampSidebarToWindow helper reused by load, window-resize, and drag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Polish mobile tablet sidebar
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
Show mobile account usage for Claude/Codex system-default logins when rate-limit data exists without Orca-managed accounts.\n\nReview fixes:\n- match inactive account usage to the runtime rateLimits payload shape\n- share usage-bar state so stale window data remains visible during transient fetch errors\n- add regression coverage for both cases
* Add mobile diff review
* Wrap mobile review notes prompt
* Fix mobile review unreviewed navigation
* Clear review completion when a refreshed diff invalidates a reviewed file
mergeMobileDiffReviewState invalidates a file's reviewed flag when its
diff identity changes, but left completedAt set — inconsistent with
markUnreviewed. Drop completedAt on invalidation and add a regression
test.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* Improve mobile session tab scrolling and editor keyboard layout
- Auto-scroll the active session tab into view within the horizontal
tab strip, using a new layout-aware utility with minimal scrolling.
- Use visual viewport measurements in the editor WebView to accurately
report keyboard insets and lift floating controls above the keyboard,
as native events can under-report covered areas.
* Fix active tab scrolling and normalize editor keyboard inset
- Ensure the active session tab is scrolled into view immediately when
its layout finishes to prevent it from remaining hidden off-screen.
- Round, clamp, and validate WebView visualViewport bottom measurements
to avoid rendering inconsistencies from fractional or invalid insets.
- Trigger an immediate keyboard inset report on script initialization.
* Update tabStripOffsetRef when scrolling mobile tab strip
Ensure the cached offset is updated to match the next scroll position. Without this update, subsequent scroll calculations would use stale offset values.
Adds a focused React Doctor oxlint gate for high-signal state/effect rules, disables the noisy js-combine-iterations React Doctor rule, and refactors the mobile new-worktree modal away from prop-change state reset effects.
Auto-generated workspace names walked the marine-creature list in order
and deduped only within the active repo (the default when workspaces are
nested), so the first auto-named worktree in every repo landed on
"Nautilus". That guaranteed identical branch names across repos, which
appear flat and indistinguishable in the sidebar.
The in-order walk also produced "Nautilus-2", "Nautilus-3", ... within a
single repo: the suggester ignores branches left behind by deleted
worktrees, so it kept re-proposing "Nautilus", and the create-time retry
loop suffixed it to dodge the lingering branch. Random selection now
yields a fresh creature each time instead of marching the same name.
- Dedup against worktrees in every repo, not just the active one
- Pick randomly from the unused pool instead of the first list entry
- Lowercase the result to match branch-name convention (fix/seahorse)
- Expand the corpus 260 -> 552 (more marine species plus public-domain
mythological sea creatures) so random picks rarely repeat
getSuggestedCreatureName drops the now-unused repoId/nestWorkspaces
params; the RNG is injectable for deterministic tests.
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* Add agent enablement controls
Implements the Enable/Disable Agents Dashboard behavior described in docs/enable-disable-agents-dashboard.md, including persisted agent enablement state, filtered launch surfaces, and settings UI affordances.
* Clarify agent availability controls
* Respect disabled agents across workspace and AI defaults
- Filter disabled TUI agents from mobile and quick workspace selection
- Avoid implicitly choosing disabled agents for commit/PR AI settings
- Broadcast settings changes to open windows for disabled-agent updates
* rm design doc
* fix: complete agent enablement propagation
* Improve mobile terminal streaming performance
Co-authored-by: Orca <help@stably.ai>
* Add mobile clear terminal action
Co-authored-by: Orca <help@stably.ai>
* Fix terminal connection test mock
Co-authored-by: Orca <help@stably.ai>
* WIP: mobile markdown tabs before rebase
Co-authored-by: Orca <help@stably.ai>
* Add mobile markdown editing
Co-authored-by: Orca <help@stably.ai>
* Harden mobile tab and markdown sync
Co-authored-by: Orca <help@stably.ai>
* Fix mobile terminal reconnect loading race
Co-authored-by: Orca <help@stably.ai>
* Polish mobile terminal keyboard behavior
Co-authored-by: Orca <help@stably.ai>
* Simplify mobile markdown editor chrome
Co-authored-by: Orca <help@stably.ai>
* Move mobile markdown actions to top
Co-authored-by: Orca <help@stably.ai>
* Use app modals for markdown discard
Co-authored-by: Orca <help@stably.ai>
* Dismiss keyboard before markdown confirmations
Co-authored-by: Orca <help@stably.ai>
* Add mobile file explorer
Co-authored-by: Orca <help@stably.ai>
* Fix mobile file explorer type narrowing
Co-authored-by: Orca <help@stably.ai>
* Fix mobile files navigation param
Co-authored-by: Orca <help@stably.ai>
* Show mobile files connection wait state
Co-authored-by: Orca <help@stably.ai>
* Preview text files on mobile
Co-authored-by: Orca <help@stably.ai>
* Simplify mobile file previews
Co-authored-by: Orca <help@stably.ai>
* Clarify unavailable mobile file types
Co-authored-by: Orca <help@stably.ai>
* Fix mobile subscription and preview review issues
Co-authored-by: Orca <help@stably.ai>
* Keep fallback terminals visible on mobile
Co-authored-by: Orca <help@stably.ai>
* Keep mobile terminal tap active
Co-authored-by: Orca <help@stably.ai>
* Preserve mobile terminal fallback order
Co-authored-by: Orca <help@stably.ai>
* Fix mobile session tab authority
Co-authored-by: Orca <help@stably.ai>
* Run mobile tests in mobile CI lane
Co-authored-by: Orca <help@stably.ai>
* Bump mobile app version to 0.0.7
Co-authored-by: Orca <help@stably.ai>
* Allow main window IPC wiring size
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
When pairing fails (e.g. broken Tailscale route, firewall blocking the
WS, expired token, etc.) the mobile app previously got stuck on
'Connecting…' forever with no signal about *where* it stalled.
Adds a structured connection-log stream from the rpc client and renders
it under the 'Connecting…' spinner on both pair-scan and pair-confirm.
Each phase emits a timestamped, color-coded entry: Opening WebSocket,
WebSocket open, Sent e2ee_hello, Received e2ee_ready, Authenticated,
WebSocket closed / Reconnect scheduled / Connect timeout / Handshake
timeout, etc. The log auto-scrolls and stays visible after a failure so
the user can see the last successful step.
Also fixes the silent infinite spinner: pairing now has a 25s overall
timeout — rpc-client retries forever by design (right behaviour for
live sessions), but for the *initial* pair we want a hard ceiling that
surfaces an actionable error with the log visible instead of spinning.
- Adds ConnectionLogEntry / ConnectionLogSink to transport/types.ts
- connect() now accepts { onStateChange, onLog } (legacy fn form kept)
- New ConnectionLog component (mono, level-coloured, +Xs elapsed)
- Pair flows track logs in a ref and render them in connecting/error
- 25s pairing-overall timeout that closes the client and surfaces a
'see log below for where it stalled' error
Co-authored-by: Orca <help@stably.ai>