Skip to content

iOS: make terminal folder-path taps a setting (Open Folders on Tap) - #8524

Merged
azooz2003-bit merged 31 commits into
mainfrom
feat-ios-folder-tap-flag
Jul 21, 2026
Merged

azooz2003-bit merged 31 commits into
mainfrom
feat-ios-folder-tap-flag

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 20, 2026 •

Copy link
Copy Markdown
Collaborator

On iOS, tapping the terminal hit-tests the tapped cell for a path token and opens the artifact viewer; for a directory that means the folder-browser sheet. Prompt cwd and ls output make directory names easy to hit by accident, so ordinary taps kept opening it. This PR adds a user setting to turn that off, and fixes the Mac-side authorization bugs found while verifying it end to end.

1. Setting: Settings > Terminal > "Open Folders on Tap" (MobileDisplaySettings.terminalFolderTapEnabled, default on, localized en+ja). When on, behavior is unchanged and the tap path gains no stat call. When off, a hit-tested path is classified via a Mac stat bounded by a 2-second injected-clock deadline: a directory falls through as a plain terminal tap; a file opens the viewer; a terminal-scope authorization refusal (forbidden) also opens the viewer so its richer chat-session authorization decides (bare filenames, wrapped paths); any infrastructure failure (deadline, disconnect, transport error) fails closed to a plain tap with no error UI, because the viewer could not have loaded content either and the user asked not to be interrupted. Taps apply newest-first (a per-surface generation supersedes pending classifications), outcomes are revalidated against the tapped cell before acting, and a dismantled surface ignores late results.

2. Fix: terminal-scope stat authorizes directories (was always forbidden, so terminal folder taps opened a dead "Preview unavailable" sheet and could never be classified). authorizedStat falls back to directory-list canonicalization; fetch/thumbnail stay file-authorized; list already exposed strictly more for the same paths.

3. Fix: the path detector strips VT escape sequences. The authorization text comes from a VT export whose OSC prologue glued to the first visible line, so nothing on that line ever authorized. The detector now consumes CSI/OSC/DCS/SOS/PM/APC (7-bit and C1) through their real terminators in one bounded scan — BEL terminates only OSC — so hidden string-control payloads can never become authorized paths.

4. Reader hardening from review: special files (FIFOs etc.) are never opened during classification — nonblocking O_CLOEXEC open with descriptor-level fstat validation (TOCTOU-safe); extension-derived kinds never touch the filesystem, so missing gallery files keep their kinds and folder listings do no redundant metadata calls; denial diagnostics compute off the main actor.

Verified live on an isolated simulator paired to a tagged Mac build (both flag states, file taps, persistence). Tests: CmuxAgentChat 354 (detector/scope/reader incl. FIFO and C1 fixtures), MobileDisplaySettings/TerminalFolderTapPolicy suites (compiled for the simulator; run in the test-ios lane). Structured review ran 6 cycles to a clean final pass; the consciously accepted exceptions (documented in the closeout comment) are the fail-closed-on-infrastructure-failure polarity, the bounded per-tap double stat in disabled mode, relative-token cwd divergence with an attached chat session, and background drain of an abandoned RPC that ignores cancellation.

Known limitation: against an older Mac build (without fixes 2-4), classification errors fail closed, so with the toggle off folder taps behave as plain taps and some file taps may require the Mac to update.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a persisted “Open Folders on Tap” setting, routes it through terminal surfaces, authorizes artifact metadata lookup, uses artifact kind to choose between opening artifacts and focusing the terminal, and strips terminal escape sequences before path extraction.

Changes

Terminal folder tap behavior

Layer / File(s) Summary
Persisted tap preference
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstrings, ios/cmux/Resources/Localizable.xcstrings, Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
Adds the persisted terminalFolderTapEnabled setting, localized toggle, and tests for defaulting, persistence, and reload behavior.
Folder tap decision policy and stat authorization
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalFolderTapPolicy.swift, Sources/TerminalController+MobileTerminalArtifacts.swift, Packages/Shared/CmuxAgentChat/Tests/CmuxAgentChatTests/TerminalArtifactScopeTests.swift, Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalFolderTapPolicyTests.swift
Determines whether taps open artifacts or focus the terminal using artifact kind; stat requests use dedicated authorization across canonical file and directory-list paths.
Terminal surface integration
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalArtifacts.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator+Artifacts.swift
Threads the setting into the terminal surface and applies the policy during artifact-tap handling.
Escape-aware artifact path detection
Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Artifacts/TerminalArtifactPathDetector.swift, Packages/Shared/CmuxAgentChat/Tests/CmuxAgentChatTests/TerminalArtifactPathDetectorTests.swift
Strips recognized VT escape sequences before path tokenization and tests extraction from escaped terminal output.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant GhosttySurfaceCoordinator
  participant TerminalFolderTapPolicy
  participant terminalArtifactStat
  participant TerminalArtifactReadContext
  User->>GhosttySurfaceCoordinator: Tap artifact path
  GhosttySurfaceCoordinator->>TerminalFolderTapPolicy: Evaluate tap policy
  TerminalFolderTapPolicy->>terminalArtifactStat: Request artifact kind when disabled
  terminalArtifactStat->>TerminalArtifactReadContext: Authorize stat path
  TerminalArtifactReadContext-->>terminalArtifactStat: Allow canonical file or directory-list path
  terminalArtifactStat-->>TerminalFolderTapPolicy: Return artifact metadata
  alt Focus terminal
    TerminalFolderTapPolicy-->>GhosttySurfaceCoordinator: focusTerminal
    GhosttySurfaceCoordinator->>GhosttySurfaceCoordinator: clickTerminal
  else Open artifact
    TerminalFolderTapPolicy-->>GhosttySurfaceCoordinator: openArtifact
    GhosttySurfaceCoordinator->>GhosttySurfaceCoordinator: onArtifactPathTapped
  end
Loading

Possibly related PRs

  • manaflow-ai/cmux#7674: Adds the mobile terminal artifact stat infrastructure used by folder-tap decision logic.

Suggested reviewers: lawrencecchen


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (2 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Package Boundaries ❌ Error Sources/TerminalController+MobileTerminalArtifacts.swift adds pure stat/authorization logic in the app target; it should live behind a small SwiftPM package boundary. Extract TerminalArtifactReadContext/authorizedStat into CmuxAgentChat (or a tiny CmuxMobileTerminalArtifacts package) behind a public TerminalArtifactAuthorization API; keep TerminalController as thin RPC glue.
Cmux No Ambient Global State ❌ Error FAIL: TerminalFolderTapPolicy (TerminalFolderTapPolicy.swift:4-24) is a new static-only policy namespace, with decision called as ambient API from the coordinator. Make it an injectable instance type: add an instance decision(...) method, construct it in WorkspaceDetailView/Coordinator, and store it as a property.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (22 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: New UI settings/store stays on @MainActor, the tap delegate is @MainActor, and the new policy uses an explicit @MainActor stat closure; no new unisolated shared Sendable state.
Cmux Swift Blocking Runtime ✅ Passed PASS: Changed production files add only async/await tap policy and stat calls; searches found no new semaphores, locks, sleeps, or sync waits in touched code.
Cmux Browser Automation Off-Main ✅ Passed Diff only touches terminal artifact path detection and mobile artifact stat auth; no browser.* routing or worker-lane WebKit/AppKit changes appear.
Cmux Expensive Synchronous Load ✅ Passed No expensive sync agent-history load was added; tap handling is async, stat RPC is Task.detached(.utility), and settings changes only touch UserDefaults.
Cmux Cache Substitution Correctness ✅ Passed PASS: the PR only threads a UI toggle; the new stat auth uses a TTL-bounded scan store with live-scope fallback, so no unhandled stale/cold cache substitution in a persistence/snapshot path.
Cmux No Hacky Sleeps ✅ Passed PASS: The diff only touches Swift, tests, and xcstrings; the no-hacky-sleeps rule applies to non-Swift runtime scripts, so no in-scope violation exists.
Cmux Algorithmic Complexity ✅ Passed Touched Swift paths use fixed-bounds or linear passes only; no new nested rescans, repeated sorts, or batch rescans were introduced in hot user-data paths.
Cmux Swift Concurrency ✅ Passed The new tap-policy and settings code uses async/await and Observation; no added DispatchQueue/Combine/completion-handler patterns or uncontrolled fire-and-forget Tasks appear in the diff.
Cmux Swift @Concurrent ✅ Passed No added @concurrent misuse: the new tap flow stays @MainActor/UI-bound, and the stat path hops through MobileChatEventSource actor/task.detached.
Cmux Swiftpm Lockfiles ✅ Passed Diff only changes two Swift source files; no Package.swift, Package.resolved, .gitignore, workflow, or xcodeproj/package-reference files were touched.
Cmux Swift Logging ✅ Passed The only added runtime logging is DEBUG-gated cmuxDebugLog output in TerminalController+MobileTerminalArtifacts.swift; no new print/NSLog/file logging or unsafe Logger constants.
Cmux User-Facing Error Privacy ✅ Passed PASS: The new terminal-folder setting and tap policy use generic user text; internal stat/authorization details stay in DEBUG logs and API internals.
Cmux Full Internationalization ✅ Passed The new UI copy uses L10n.string (localized API), and both touched string catalogs add the key with en/ja translations matching their supported locales.
Cmux Swiftui State Layout ✅ Passed PASS: No new ObservableObject/@Published or layout-mutating GeometryReader; the added settings state is @Observable, and the rest is bridge/event-handler code.
Cmux Architecture Rethink ✅ Passed Single settings source flows as a value snapshot into the representable/coordinator; no sleeps, polling, observers, or duplicate action path were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The diff only changes terminal artifact logic; no NSWindow/WindowGroup/identifier code or cmuxAuxiliaryWindowIdentifiers plumbing is present.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/test/localization files; no logs, temp dirs, build output, or other artifact paths were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: the production diff removes the temp dumpAuthorizationText() seam and only adds a DEBUG-gated denial log helper; no new test/debug accessor remains.
Title check ✅ Passed The title clearly summarizes the iOS folder-tap setting change and is concise.
Description check ✅ Passed The description covers the summary and testing well; only non-critical template sections like demo video and checklist are missing.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-folder-tap-flag

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an "Open Folders on Tap" setting for iOS that lets users suppress accidental folder-browser openings when tapping directory names in terminal output. When the setting is off, a bounded (2 s) Mac-side stat classifies the tapped path: directories fall through to a normal terminal tap, files open the viewer, authorization refusals defer to the viewer's richer chat-scope authorization, and infrastructure failures fail closed without error UI. Three related fixes land alongside: terminal-scope authorizedStat now falls back to canonicalDirectoryListPath so directories stop returning a dead "forbidden" result; the path detector strips VT escape sequences before tokenizing so OSC prologues can no longer glue to authorized path tokens; and ArtifactByteReader uses a nonblocking O_CLOEXEC open + descriptor-level fstat to reject FIFOs and special files before any content operation.

  • TerminalFolderTapPolicy is a constructable type with an injectable Clock that races a @MainActor stat task against a cancellable deadline via AsyncStream; generation counters on the coordinator ensure newest-tap-wins and stale results from a dismantled surface are dropped.
  • Mac-side authorizedStat now resolves directories through both canonicalFilePath and canonicalDirectoryListPath fallbacks, fixing the forbidden result that was making folder taps open a dead viewer.
  • VT stripping in TerminalArtifactPathDetector handles the full set of ECMA-48 string-control introducers with correct BEL-only-for-OSC semantics, backed by 12 new parameterized tests covering C1 forms and unterminated sequences.

Confidence Score: 5/5

Safe to merge. The new tap-classification path is well-guarded by generation counters, surfaceView identity checks, and a 2-second deadline that fails closed on infrastructure errors. All three bug fixes are behaviorally correct and test-covered.

The setting plumbing, tap-policy logic, escape-sequence stripping, and Mac-side directory-authorization fix are all narrowly scoped and carry comprehensive new tests. The only open concern — thumbnail re-opening the file by URL after closing the verified fd rather than keeping the fd alive through ImageIO — is a narrow TOCTOU edge that does not regress existing behavior and is noted as a suggestion.

ArtifactByteReader.swift — the thumbnail function open-verify-close-then-CGImageSourceCreateWithURL pattern leaves a narrow window; the suggestion is to keep the fd open and use CGImageSourceCreateWithData instead.

Important Files Changed

Filename Overview
Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Artifacts/ArtifactByteReader.swift Reader hardening: FIFO/special-file protection via openVerifiedRegularFile (O_NONBLOCK + fstat); thumbnail opens-and-closes the verified fd before calling CGImageSourceCreateWithURL, leaving a narrow TOCTOU window
Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Artifacts/TerminalArtifactPathDetector.swift VT escape-sequence stripping added: correct state machine handling 7-bit and C1 CSI, OSC, DCS, SOS, PM, APC sequences with BEL only terminating OSC; well-tested across all newly added cases
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalFolderTapPolicy.swift New constructable type with injectable clock and AsyncStream-based stat/deadline race; forbidden maps to openArtifact, infrastructure failures map to focusTerminal (fail-closed)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator+Artifacts.swift Generation-based tap invalidation prevents stale tap results; correct weak-self capture in async stat closure; nested Task for click-on-focus-path correctly fires-and-forgets with generation guard
Sources/TerminalController+MobileTerminalArtifacts.swift Mac-side fix: authorizedStat now falls back to canonicalDirectoryListPath so directory paths authorize correctly; debug diagnostics run in Task.detached off the main actor
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift New terminalFolderTapEnabled stored property with default-true no-write cold-read pattern, consistent with existing settings
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift Clean plumbing of terminalFolderTapEnabled; surfaceView = nil added to detach() so generation + surfaceView identity checks both invalidate correctly
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift Standard Toggle pattern with L10n-routed localized string and accessibilityIdentifier
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstrings New mobile.settings.terminalFolderTap key with en and ja translations covering all supported locales
ios/cmux/Resources/Localizable.xcstrings New mobile.settings.terminalFolderTap key with en and ja translations matching the package-level catalog entry

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[iOS tap at col/row] --> B{artifactFilesEnabled?}
    B -- No --> Z[clickTerminal + .focusTerminal]
    B -- Yes --> C[visibleTextForArtifactHitTesting]
    C --> D{path token at coordinates?}
    D -- No --> Z
    D -- Yes --> E{folderTapEnabled?}
    E -- Yes --> F[revalidate path in snapshot]
    E -- No --> G[TerminalFolderTapPolicy.decision race: stat vs 2s deadline]
    G --> H{ChatArtifactKind}
    H -- .directory --> I[revalidate path focusTerminal branch]
    H -- .image/.text/.binary --> F
    H -- .forbidden --> F
    H -- infra error / deadline --> I
    I --> J{currentPath == path?}
    J -- No --> K[.ignored]
    J -- Yes --> L[clickTerminal fire-and-forget Task]
    L --> M[.focusTerminal]
    F --> N{currentPath == path?}
    N -- No --> K
    N -- Yes --> O[onArtifactPathTapped .openedArtifact]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[iOS tap at col/row] --> B{artifactFilesEnabled?}
    B -- No --> Z[clickTerminal + .focusTerminal]
    B -- Yes --> C[visibleTextForArtifactHitTesting]
    C --> D{path token at coordinates?}
    D -- No --> Z
    D -- Yes --> E{folderTapEnabled?}
    E -- Yes --> F[revalidate path in snapshot]
    E -- No --> G[TerminalFolderTapPolicy.decision race: stat vs 2s deadline]
    G --> H{ChatArtifactKind}
    H -- .directory --> I[revalidate path focusTerminal branch]
    H -- .image/.text/.binary --> F
    H -- .forbidden --> F
    H -- infra error / deadline --> I
    I --> J{currentPath == path?}
    J -- No --> K[.ignored]
    J -- Yes --> L[clickTerminal fire-and-forget Task]
    L --> M[.focusTerminal]
    F --> N{currentPath == path?}
    N -- No --> K
    N -- Yes --> O[onArtifactPathTapped .openedArtifact]
Loading

Reviews (20): Last reviewed commit: "fix: reject typed special artifacts and ..." | Re-trigger Greptile

Comment on lines +4 to +25
struct TerminalFolderTapPolicy: Sendable {
/// The action the terminal tap handler should take for a detected path.
enum Decision: Sendable, Equatable {
case openArtifact
case focusTerminal
}

/// Applies the folder-tap preference without adding a stat call while enabled.
static func decision(
for path: String,
folderTapEnabled: Bool,
stat: @MainActor @Sendable (String) async throws -> ChatArtifactKind
) async -> Decision {
guard !folderTapEnabled else { return .openArtifact }

do {
return try await stat(path) == .directory ? .focusTerminal : .openArtifact
} catch {
return .openArtifact
}
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Static-only namespace struct — no-ambient-global-state violation

TerminalFolderTapPolicy has no stored properties and exposes exactly one static method; every caller uses it as TerminalFolderTapPolicy.decision(...). This is the empty-struct-as-static-namespace pattern the cmux-no-ambient-global-state rule explicitly flags. The canonical fix is a constructable type — move folderTapEnabled into a stored property and make decision an instance method that accepts only the stat closure, so callers create TerminalFolderTapPolicy(folderTapEnabled: flag).decision(for: path, stat: ...). The tests still access it via @testable import without any seam changes, and the type becomes genuinely injectable.

Rule Used: Flag new ambient global state in production Swift:... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted and fixed in fa266fd: TerminalFolderTapPolicy is now a constructable instance with folderTapEnabled as a stored property and decision(for:stat:) as an instance method.

— Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalFolderTapPolicy.swift`:
- Around line 19-23: Update TerminalFolderTapPolicy to return .focusTerminal in
the stat(path) catch block. In
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalFolderTapPolicyTests.swift
lines 65-74, rename the failure test and expect .focusTerminal. In
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator+Artifacts.swift
lines 272-275, throw an error when the source is unavailable instead of
returning a fake .binary kind.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 02264650-9f46-4abb-b067-bf4f0f3ab9e4

📥 Commits

Reviewing files that changed from the base of the PR and between ce90c0b and 7f07797.

📒 Files selected for processing (10)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator+Artifacts.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceRepresentable.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstrings
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalFolderTapPolicy.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalArtifacts.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalFolderTapPolicyTests.swift
  • ios/cmux/Resources/Localizable.xcstrings

The @Environment displaySettings property is private to WorkspaceDetailView.swift,
so the terminal-artifacts extension file reads the flag through an internal
wrapper, matching terminalFilesChipEnabled.
@cursor

cursor Bot commented Jul 20, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@cursor

cursor Bot commented Jul 20, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@cursor

cursor Bot commented Jul 20, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@cursor

cursor Bot commented Jul 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Comment on lines +41 to +50
let decision: Decision
do {
let kind = try await stat(path)
decision = kind == .directory ? .focusTerminal : .openArtifact
} catch {
decision = .focusTerminal
}
continuation.yield(decision)
continuation.finish()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Stat failure silently drops file taps

All stat errors — including transient network failures, fileNotFound for a file that was visible but just deleted, and the 2-second deadline firing when the Mac is under load — are caught here and return .focusTerminal. The PR description explicitly states "a stat failure still opens the viewer, so file taps keep working in both modes," but the test is named "disabled fails closed when stat throws," and the inline doc says the same — there is a direct contradiction with the stated user-visible contract.

Concretely: a user who disabled folder taps and taps a .swift file reference in the terminal, while the Mac is slow to respond (stat RPC ≥ 2 s), gets .focusTerminal — the viewer never opens — even though folderTapEnabled: false was only meant to suppress directory taps. If the intent is instead to always fail closed (never open the viewer on any stat error), the "file taps keep working in both modes" claim in the PR description and the folderTapEnabled: false user promise should be updated to reflect this restriction.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description was stale — updated. Final behavior is deliberate and documented in the closeout comment: authorization refusal (forbidden) opens the viewer so file taps via chat-scope keep working; infrastructure failures (deadline/disconnect/fileNotFound-after-deletion) fail closed to a plain tap, because the viewer could not load content on a dead link either and this setting exists to remove interruptions. Both polarities were weighed across review cycles; this split is the owner decision.

— Claude Code

@cursor

cursor Bot commented Jul 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Review closeout: 6 structured-review cycles produced 15 accepted-and-fixed findings (fail-closed policy, string-control/BEL escape parsing, C1 forms, bounded classification deadline with injected clock, tap-generation ordering, surface-detach lifecycle, FIFO/O_CLOEXEC descriptor safety, denial diagnostics off-main, extension-kind regression, viewport revalidation). Final structured review: no actionable defect. Consciously accepted exceptions, documented for the record:

  1. Disabled-mode classification fails closed on infrastructure errors (deadline/disconnect) with no error UI; authorization refusal defers to the viewer. Both polarities were flagged as P1 across cycles; this split is the owner decision (silent fallback is the setting's contract).
  2. Disabled-mode file taps pay one classification stat plus the viewer's stat (bounded, per-tap; a visible-only variant was reverted because phone/Mac viewports diverge during scroll).
  3. Relative tokens classify against the terminal cwd while an attached chat session may resolve them against its own cwd in the viewer (last P2). Bounded to opt-out mode + relative token + divergent cwds; worst case is one folder sheet (pre-setting behavior). Proper fix would plumb session scope into tap classification; deferred.
  4. An abandoned classification RPC may drain in the background up to its own deadline when a transport ignores cancellation; user-facing decisions are bounded at 2s.

@cursor

cursor Bot commented Jul 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@cursor

cursor Bot commented Jul 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Closeout addendum: the final two structured-review findings target pre-existing entrypoints outside this PR's changes — the iroh artifact transfer registry (untouched by this diff; blocking FileHandle open predates it) and decode-from-descriptor depth on the thumbnail path (whose pre-PR code had no validation at all; this PR added the descriptor-verified pre-check). Both are tracked in #8581 rather than expanded into this PR. All PR-introduced findings across 7 review cycles (16 accepted) are fixed on this head.

@azooz2003-bit
azooz2003-bit merged commit e3827a4 into main Jul 21, 2026
6 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-ios-folder-tap-flag branch July 21, 2026 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant