Skip to content

Add model selection lab to the New Task composer - #8800

Merged
azooz2003-bit merged 34 commits into
mainfrom
feat-newtask-model-picker
Aug 4, 2026
Merged

azooz2003-bit merged 34 commits into
mainfrom
feat-newtask-model-picker

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Adds model selection to the iOS New Task sheet with five UX variants, switchable at runtime from Settings → CMUX Labs → New Task Model Lab (DEBUG builds only; release builds keep the feature off).

Mechanism. A curated per-provider model catalog (MobileTaskAgentModelCatalog) detects the provider from the template command's first token (claude, codex, opencode) and, only when the user explicitly picks a model, inserts that provider's flag right after the executable token (claude --model 'claude-opus-4-8' …, codex -m 'gpt-5.5' …, opencode --model '…' …). With no model selected the template command stays byte-for-byte verbatim, preserving the composer's no-shell-parsing invariant and all existing verbatim tests. The selection rides the existing draft/snapshot pipeline (MobileTaskComposerDraft.modelID, MobileTaskSubmissionSnapshot.modelID), so it persists across relaunches and stays idempotency-safe; legacy drafts without the field decode fine.

Variants (lab enum TaskComposerModelPickerVariant, default in DEBUG = A):

  • A combined — the agent menu becomes agent+model: agents with models get a submenu (Default + models); label reads "Ask Claude · Opus 4.8".
  • B separateRow — dedicated Model menu row under the agent selector.
  • C trailingChip — compact capsule chip on the agent row opening a model menu.
  • D pillStrip — one-tap model pills under the agent row.
  • E contextRow — Model row in the context card beside Mac/Directory.
  • Off — current behavior.

Model UI only appears for templates whose command starts with a known provider binary (never for Shell or unrecognized custom commands). All new strings localized (en + ja). Tests: catalog detection/injection/quoting, composer opt-in invariant, draft/snapshot round-trips (187 pass in CmuxMobileShellModel).

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Adds model selection to the iOS New Task composer with a lab-switchable layout, discovers models from connected Macs, and adds chunked task attachments. Commands are only rewritten when a model is explicitly selected; otherwise the template stays byte-for-byte unchanged.

  • New Features

    • Model discovery from the Mac when it advertises task.models.v1: iOS calls mobile.task.models.list per provider, caches per Mac/provider, and falls back to curated models when discovery isn’t available; UI only for known providers.
    • Curated models for Claude, Codex (adds “GPT-5.6 Luna”), and OpenCode with first-token provider detection.
    • Minimal composer (DEBUG default) with a full-bleed prompt, options sheet, agent pill, dedicated model pill, and submit; switchable in Settings → CMUX Labs → New Task Model Lab; release builds keep Classic.
    • Five picker variants with one placement each; in the minimal composer the model pill is the single entry point for all non-Off variants; classic combined agent submenu shows “· ” with checkmarks; selection persists in drafts and submission snapshots; localized (en/ja).
    • Task attachments for agent templates: pick Photos or Files, downsample images, upload in chunks via mobile.task.attachment.upload; caps—10 items, 8 MiB/image, 32 MiB/file, 64 MiB total; shown only when the selected Mac advertises task.attachments.v1; attachment paths flow via CMUX_TASK_ATTACHMENTS and are appended to the prompt.
    • UI polish: the composer pill scroller uses hard edges between the options and submit buttons (no underlap or blur/fade); pills are clipped cleanly; the XCUITest now also exercises overflowing pills and locks this layout.
  • Bug Fixes

    • Quote-aware, bounded flag rewrite: replace --model/-m only before --, never in quotes; stop at the first simple-command boundary (incl. control operators, relevant redirections, and word-initial #); handle attached separators and update all hits; if commented, insert after the first token instead.
    • When the picker is Off, hidden models are stripped at both request-resolution points via a shared resolver; if bytes change, mint a fresh operation ID to keep retries idempotency-safe.
    • If a draft’s saved model is no longer in the curated/discovered list, drop it and mint a fresh operation ID instead of reusing recovery state.
    • Prevent agent/model pill label clipping on longer titles by adopting the new width immediately; on iOS 26 the entire Menu is keyed so bounds don’t animate through stale sizes.
    • Capability checks adopt MacPairingKey lookups to resolve the exact tagged instance (or any same-device pairing), fixing detection for task.attachments.v1 and task.models.v1.
    • Stop interactive keyboard dismissal from re-scrolling the prompt editor; dismissal is now immediate. Keep the prompt at a user-chosen scroll position and prioritize prompt scrolling over sheet drag; added UI tests for gesture ownership and scroll-position stability.
    • Fix the composer hard-edge UI test element lookup so it reliably targets the pill scroller and neighboring controls.

Written for commit e08cb75. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added mobile task agent model selection to the iOS task composer (default + provider-curated choices) and applied the selection to the generated command when applicable.
    • Model selection is now included in draft creation, restoration, and submission flow.
    • Added multiple model-picker UI variants and a DEBUG-only “New Task Model Lab” settings area to preview layout and picker options.
  • Bug Fixes
    • Improved failure recovery UI by consolidating retry and “start again” controls into a single consistent flow.
  • Tests
    • Added coverage for model selection persistence and command-rewriting behavior, including provider detection and round-trip decoding.

Adds a curated per-provider model catalog (Claude, Codex, OpenCode) with
opt-in model-flag injection into template commands, and five UX variants
for picking the model in the New Task sheet (combined agent menu, model
row, trailing chip, pill strip, context row), switchable at runtime from
the DEBUG-only CMUX Labs 'New Task Model Lab'. No model selected keeps
template commands byte-for-byte verbatim; release builds stay off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 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 curated agent-model support to task composition, persists selected model IDs through drafts and submissions, and introduces debug-configurable iOS picker layouts with reusable model-selection controls and updated task composer wiring.

Changes

Task composer model selection

Layer / File(s) Summary
Provider catalog and command composition
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift, .../MobileTaskCommandComposer.swift, Packages/iOS/CmuxMobileShellModel/Tests/*ProviderTests.swift
Detects supported providers, exposes curated models, validates IDs, and inserts or replaces provider-specific model flags.
Submission and draft persistence
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskComposerDraft.swift, .../MobileTaskSubmissionSnapshot.swift, Packages/iOS/CmuxMobileShellModel/Tests/*
Carries optional model IDs through draft encoding, snapshot rebinding, command composition, and request equivalence checks.
Picker configuration and controls
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift, .../TaskComposer/*Model*, ios/cmux/Resources/Localizable.xcstrings
Adds debug-only layout and picker variants, persistence and lab settings, localized labels, and reusable menu, row, chip, and pill controls.
Sheet selection and restoration
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet*
Validates model selection for the active template, restores and snapshots model IDs, and passes model state through classic and minimal composer layouts.
Composer UI wiring
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/*
Connects model state and callbacks across agent menus, prompt cards, minimal layouts, context sections, options sheets, and recovery content.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TaskComposerPromptCard
  participant TaskComposerSheet
  participant MobileTaskAgentProvider
  participant MobileTaskSubmissionSnapshot
  User->>TaskComposerPromptCard: Select agent model
  TaskComposerPromptCard->>TaskComposerSheet: Pass model ID
  TaskComposerSheet->>MobileTaskAgentProvider: Validate model for template
  TaskComposerSheet->>MobileTaskSubmissionSnapshot: Save model ID
  MobileTaskSubmissionSnapshot->>MobileTaskAgentProvider: Compose command with model flag
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description clearly explains the changes and testing, but it omits the required Demo Video, Review Trigger, and Checklist sections. Add the missing template sections, include a demo video for the UI changes, and complete the testing and checklist items.
✅ Passed checks (23 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 Changed UI stores are explicitly @MainActor; new model types are plain Sendable values, with no new isolation regressions or unsafe Sendable refs.
Cmux Swift Blocking Runtime ✅ Passed No new semaphores, sleeps, sync waits, or locks in the touched production Swift files; only a short SwiftUI animation changed.
Cmux Browser Automation Off-Main ✅ Passed Diff only changes iOS task-composer model/UI files; no browser socket commands, policy routing, or WebKit/AppKit worker changes are touched.
Cmux Expensive Synchronous Load ✅ Passed No new sync agent-history/file-parsing load was added; the diff only adds in-memory model selection and command rewriting on existing UI paths.
Cmux Cache Substitution Correctness ✅ Passed Selected model state is event-driven and revalidated against the current provider; hidden Off-variant models are stripped before snapshot submission.
Cmux No Hacky Sleeps ✅ Passed Diff touches only Swift/tests/xcstrings; no TS/JS/shell/build-runtime files or hacky waits were introduced.
Cmux Algorithmic Complexity ✅ Passed No new nested rescans on scalable collections; added lookups are over fixed small model catalogs, and template scans happen only on explicit actions.
Cmux Swift Concurrency ✅ Passed No new DispatchQueue/Combine/completion-handler patterns were introduced; the only Task usage is preexisting and lifecycle-managed.
Cmux Swift @Concurrent ✅ Passed No changed Swift file adds nonisolated async work or invalid @concurrent; the new async directory-picker/submission helpers are explicitly @MainActor UI-bound.
Cmux Swift Package Boundaries ✅ Passed Diff stays in CmuxMobileShellModel/UI SwiftPM targets; reusable provider/command logic is behind package boundaries, not app-root Sources.
Cmux Swiftpm Lockfiles ✅ Passed PR only changes Swift source/tests; no .gitignore, Package.swift, Package.resolved, or Xcode package-reference files were touched, so the lockfile rule isn’t violated.
Cmux Swift Logging ✅ Passed PASS: The full PR diff adds no print/debugPrint/dump/NSLog/Logger/os_log in runtime Swift; only behavior changes and tests were touched.
Cmux User-Facing Error Privacy ✅ Passed Task-composer failure/recovery text stays generic; no provider names, flags, snapshots, env vars, or raw upstream details appear in user-facing copy.
Cmux Full Internationalization ✅ Passed All new user-facing Swift copy uses localized APIs, and the new xcstrings entries are complete for en/ja with no raw UI literals found.
Cmux Swiftui State Layout ✅ Passed No banned SwiftUI patterns were introduced: state uses @Observable/@State, no GeometryReader, and no render-time writes or lazy-row store refs in the new task composer views.
Cmux Architecture Rethink ✅ Passed No banned timing, observer, lock, or duplicate-owner patterns; model selection stays owned by TaskComposerSheet and flows via snapshots/closures.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: the diff only adds settings/navigation SwiftUI views and model-selection state; no new NSWindow/WindowGroup/WindowController or cmux.* auxiliary window identifiers.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/test files under Sources/Tests; none are logs, caches, build output, or scratch artifacts.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The PR adds genuine DEBUG-only lab UI/defaults, not a test-observability accessor or wrapper in production Sources.
Cmux No Ambient Global State ✅ Passed PASS: the new model-selection logic is scoped to types/extensions; I found no top-level funcs, mutable globals, or new singleton-style state in the touched production files.
Title check ✅ Passed The title clearly identifies the main change: adding model selection to the iOS New Task composer.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat-newtask-model-picker
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-newtask-model-picker

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 24, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a model-selection feature to the iOS New Task composer, with five UX variants (combined menu, model row, trailing chip, pill strip, context row) switchable at runtime from a DEBUG-only lab in Settings. In production builds, the feature is fully off via renderedVariant/.off and renderedStyle/.classic guards; in DEBUG it defaults to the combined menu (variant A).

  • Catalog & injection: MobileTaskAgentModelCatalog detects the provider from the first token of the template command and inserts the provider-specific model flag (--model/-m) right after the binary only when a model is explicitly selected; a nil model or unrecognized command leaves the command byte-for-byte unchanged.
  • Draft/snapshot persistence: selectedModelID is added to MobileTaskComposerDraft and MobileTaskSubmissionSnapshot, with re-validation against the current template's catalog on every restore; legacy drafts decode cleanly because modelID is String? with a default-nil initializer.
  • State management: Template switch always resets selectedModelID = nil; updateTemplatesFromStore re-validates the model against the edited catalog; the combined-menu variant fires updateSubmissionRequest twice per agent+model tap which is synchronous and correct, though the double reconcile is worth keeping an eye on if the recovery path gains side effects.

Confidence Score: 5/5

Safe to merge. The model-selection feature is fully off in release builds, all command mutation is guarded by catalog validation, and draft/snapshot round-trips are backward compatible.

The core injection path is well-tested and byte-for-byte preserves commands without an explicit model selection. All new strings are localized for both supported locales. The production data model changes decode cleanly from legacy drafts. The only new observations are debug-only concerns that do not affect shipping behavior.

TaskComposerAgentMenuContent.swift and TaskComposerSheet.swift — specifically how the combined-menu variant fires two sequential updateSubmissionRequest calls per agent+model tap, and whether that stays safe if reconcileCurrentRequest gains non-idempotent side effects in the future.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentModelCatalog.swift New static catalog for provider detection and flag injection. Provider detection is reliable (binary basename), shell-escape is correct, and nil-model passthrough preserves the verbatim invariant. Double-flag injection on pre-flagged templates is a pre-existing concern already captured in thread.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift Central coordinator: adds @State var selectedModelID, validates the draft model on init, dispatches to classic/minimal layouts. The combined-menu variant double-fires updateSubmissionRequest per tap which is synchronous and functionally correct but worth monitoring if recovery-reconcile gains side effects.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift Persists selectedModel?.id into drafts and snapshots; re-validates model ID from restored snapshots against the live catalog before accepting it; selectTemplate correctly clears the model on agent switch.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swift Combined-variant buttons call selectTemplate then selectModel as two sequential actions, both triggering updateSubmissionRequest. Functionally correct; see sheet-level note on the double reconcile.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift Adds taskComposerLayoutStyle and taskComposerModelPickerVariant behind #if DEBUG/#else with constant production values; the #else branch returns .classic/.off so no debug state leaks into release.
ios/cmux/Resources/Localizable.xcstrings Adds 325 lines of string entries; all new keys have both en and ja translations with state: translated.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User opens New Task sheet] --> B{Draft has modelID?}
    B -- Yes --> C[Validate model against current template command]
    B -- No --> D[selectedModelID = nil]
    C -- Valid --> E[selectedModelID = draft.modelID]
    C -- Invalid / wrong provider --> D
    E --> F[User picks agent or model]
    D --> F
    F --> G{Action type}
    G -- selectTemplate --> H[selectedModelID = nil, updateSubmissionRequest]
    G -- selectModel --> I{Model valid for current template?}
    I -- Yes --> J[selectedModelID = model.id, updateSubmissionRequest]
    I -- No --> K[no-op]
    H --> L[buildSubmissionSnapshot]
    J --> L
    D --> L
    L --> M{selectedModelID != nil?}
    M -- Yes --> N[commandApplying inserts flag after binary token]
    M -- No --> O[command unchanged byte-for-byte]
    N --> P[MobileTaskSubmissionSnapshot.composition]
    O --> P
    P --> Q[Submit task to Mac]
Loading

Reviews (2): Last reviewed commit: "Add Codex-style composer layout to the N..." | Re-trigger Greptile

Comment on lines +32 to +35
public enum MobileTaskAgentModelCatalog {
/// Lexically detects a provider from the command's first
/// whitespace-delimited token.
///

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 Caseless enum used as static namespace

MobileTaskAgentModelCatalog is a caseless enum whose entire API consists of public static functions — exactly the static-namespace shape the cmux-no-ambient-global-state rule flags. The inline /// lint:allow namespace-type comment is a doc comment, not a recognized suppression directive. The preferred shape is a struct that can be constructed and injected (e.g. public struct MobileTaskAgentModelCatalog { public init() {} … } with instance methods), which also opens a test-seam for custom catalogs without exposing implementation details to production Sources/.

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.

Fixed in 656cf07: dissolved the caseless enum entirely. Detection is now MobileTaskAgentProvider.init?(command:), the curated lists are provider.models, and flag application is an instance method, so there is no namespace type left.

— Claude Code

Comment on lines +103 to +115
}

/// Inserts a provider-specific model flag immediately after the command's
/// first token.
///
/// A `nil` model identifier or unsupported command is returned unchanged.
/// Everything after the first token remains byte-for-byte identical.
/// - Parameters:
/// - modelID: CLI model identifier to single-quote for the flag value.
/// - command: User-authored task-template command.
/// - Returns: The flagged command, or the original command when not applicable.
public static func commandApplying(modelID: String?, to command: String) -> String {
guard let modelID,

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 Double-flag injection when template already specifies a model

commandApplying always inserts the flag immediately after the binary token with no guard against an existing --model / -m flag in the template command. A user whose template is claude --model claude-sonnet-4-5 --verbose and who then picks claude-opus-4-8 in the lab will produce claude --model 'claude-opus-4-8' --model claude-sonnet-4-5 --verbose. Claude CLI's flag-precedence behaviour for duplicate --model is not guaranteed, so the selected model may be silently ignored. Consider scanning command[tokenRange.upperBound...] for an existing flag matching the provider's flag spelling before injecting, and either replacing it in place or returning the command unchanged with the new model applied to the flag that is already there.

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.

Fixed in 656cf07: command(applying:to:) now replaces the value of an existing --model / -m / --model= token in place (scanning stops at a standalone --), so the explicit selection always wins and no duplicate flag is emitted. Covered by new unit tests.

— 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: 2

🤖 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/TaskComposer/TaskComposerAgentMenu.swift`:
- Around line 138-170: Replace the two-step selection calls in the
template/model menu with one shared combined action, such as
selectTemplateAndModel(templateID:modelID:), covering both the default nil-model
case and specific models. Implement the combined action alongside the existing
selectTemplate and selectModel logic so both mutations occur within a single
updateSubmissionRequest reconciliation/persistence block, then update each menu
button to invoke only that action while preserving selection state and
accessibility behavior.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift`:
- Around line 44-47: The model-name lookup and accessibility configuration are
duplicated across the model-picker variants. In
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift#L44-L47
and `#L35-L40`,
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelContextRow.swift#L41-L53,
and
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelRow.swift#L49-L61,
add shared helpers for resolving the selected MobileTaskAgentModel display name
and applying the accessibility label/value/hint, then replace each local
implementation with those helpers.
🪄 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 Plus

Run ID: 417109d5-34bf-490b-a0b9-58b77ab9ce2c

📥 Commits

Reviewing files that changed from the base of the PR and between ea51d55 and b93b394.

📒 Files selected for processing (27)
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentModelCatalog.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskComposerDraft.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskSubmissionSnapshot.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentModelCatalogTests.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskCommandComposerTests.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskComposerDraftTests.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskSubmissionSnapshotTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenu.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuActions.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuValue.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerContextSection.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelContextRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelMenuContent.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPickerLabView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPickerVariant.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPillStrip.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
  • ios/cmux/Resources/Localizable.xcstrings

New minimal layout (lab-switchable, DEBUG default): full-bleed prompt
canvas titled by the working directory, back chevron, and a bottom
control bar with a + options sheet (name, Mac, directory), agent pill,
model pill, and a circular submit button. Classic card layout stays
available via CMUX Labs and renders unchanged; release builds keep
classic. Adds GPT-5.6 Luna to the Codex model catalog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Round 2 (52a4d22): added a Codex-style composer layout per owner request — full-bleed prompt canvas titled by the working directory, back chevron, bottom control bar with + options sheet (workspace name / Mac / directory), agent pill, model pill, and circular submit. Lab-switchable in Settings → CMUX Labs → New Task Model Lab (Layout: Classic / Composer); Composer is the DEBUG default, release stays Classic. Classic renders unchanged. Also added GPT-5.6 Luna to the Codex model catalog. 188 model tests pass; both layouts sim-verified.

@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/TaskComposer/TaskComposerSheet.swift`:
- Around line 371-406: Extract the duplicated injected-override/store-fallback
logic from the top-level directory-picker sheet and minimalOptionsSheet into
shared private helpers such as resolvedSearchTaskDirectories(query:) and
resolvedListTaskDirectories(path:offset:). Replace both existing searchMac and
listMac closure implementations with calls to those helpers, preserving the
current arguments and fallback behavior.
🪄 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 Plus

Run ID: e2d2df0e-1d19-4f33-af03-08ed6d91e905

📥 Commits

Reviewing files that changed from the base of the PR and between b93b394 and 52a4d22.

📒 Files selected for processing (14)
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentModelCatalog.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentModelCatalogTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenu.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerFailureRecoveryContent.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerLayoutStyle.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPickerLabView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerOptionsSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPrimaryAction.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
  • ios/cmux/Resources/Localizable.xcstrings

- Dissolve the MobileTaskAgentModelCatalog static namespace into
  MobileTaskAgentProvider (detection init, models, model(id:),
  command(applying:to:)) per the no-static-namespace policy.
- Replace an existing --model/-m/--model= value in place instead of
  injecting a duplicate flag that the template's own value would
  override; stop scanning at the -- end-of-options token.
- Pin the task-composer accessibility preview to the classic layout and
  Off variant on fresh installs so the XCUITest suite keeps a stable
  element tree; CMUX_UITEST_TASK_COMPOSER_LAYOUT/_MODEL_VARIANT opt in.
- Give each lab variant exactly one placement in the composer layout:
  combined stays in the agent submenu, contextRow stays in Task Options,
  the rest collapse to the standalone bottom-bar pill.
- Gate the composer submit button on blocking completed-operation
  recovery, matching the classic layout.
- Make combined menu taps a single atomic template+model mutation.
- Share the model display-name fallback and accessibility triple; share
  the directory search/list fallback closures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift`:
- Around line 95-103: Update the model-flag handling in the token loop of
MobileTaskAgentProvider so when the token after a recognized model flag is `--`,
insert the selected quotedID before the boundary instead of replacing or
removing `--`; preserve existing behavior for normal values and missing values,
and add a regression test covering `claude --model -- "$CMUX_TASK_PROMPT"`.
🪄 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 Plus

Run ID: 8226f0f2-a84f-4f6b-98ec-1714db293d53

📥 Commits

Reviewing files that changed from the base of the PR and between 52a4d22 and 656cf07.

📒 Files selected for processing (17)
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentProviderTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenu.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuActions.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelContextRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelDisplay.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift

- Quote-aware token scanning in command(applying:to:): flag text inside
  single/double-quoted arguments is one opaque token, so quoted mentions
  of --model are never rewritten; every real model flag before -- is
  replaced (not just the first); a flag directly before -- gets its
  value supplied in place.
- Gate selectedModel on the rendered picker variant so a draft-restored
  model cannot ride into snapshots or submissions while the picker is
  Off; the stored selection survives for when a variant is re-enabled.
- Show the selected model in the composer layout's combined variant
  (agent pill title gains ' · <model>') and add visible checkmarks to
  the combined submenu rows.
- Extend compact composer controls (+, submit, pills, chip, row, pill
  strip) to 44pt activation targets without changing their visuals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 29, 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.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift (1)

677-677: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not clear the retained model while the picker is off.

Line 677 assigns from selectedModel, which is deliberately nil whenever the picker variant is .off. Any template refresh therefore erases selectedModelID, so re-enabling the picker cannot restore the prior valid choice. Validate the ID directly against the selected template’s provider instead, and add an off → refresh → on regression test.

Based on PR objectives, the hidden selection must remain available when the picker is re-enabled.

🤖 Prompt for 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.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`
at line 677, Update the template refresh logic around selectedModelID so it does
not overwrite the retained ID when the model picker variant is .off. Validate
the existing ID directly against the selected template’s provider rather than
deriving it from selectedModel, while preserving valid selections and clearing
only invalid IDs. Add a regression test covering off → refresh → on and
confirming the prior model selection is restored.
🤖 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/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift`:
- Around line 104-120: Update the model-flag detection in the command-rewrite
method containing modelFlagSpellings and tokenRange to decode shell-quoted token
values before comparing them with flag spellings, so tokens such as "--model"
are recognized. Ensure replacement logic removes or updates the existing
provider-specific model option rather than prepending a duplicate, and add a
regression case for `claude "--model" old -- "$CMUX_TASK_PROMPT"`.

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`:
- Line 677: Update the template refresh logic around selectedModelID so it does
not overwrite the retained ID when the model picker variant is .off. Validate
the existing ID directly against the selected template’s provider rather than
deriving it from selectedModel, while preserving valid selections and clearing
only invalid IDs. Add a regression test covering off → refresh → on and
confirming the prior model selection is restored.
🪄 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 Plus

Run ID: 58ee3f46-2f33-4bda-9d19-486172510010

📥 Commits

Reviewing files that changed from the base of the PR and between 656cf07 and feff01d.

📒 Files selected for processing (9)
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentProviderTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelPillStrip.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift

- Stop the model-flag scan at the first simple command's end: a newline
  separator or a token carrying an unquoted ;, |, or &. A compound
  template like 'claude "$CMUX_TASK_PROMPT"; formatter --model compact'
  now inserts Claude's flag after the first token and leaves the later
  command untouched.
- Route every submission through effectiveSubmissionSnapshot: while the
  picker variant is Off, a hidden model captured by the cached restored
  request (or an adopted recovery request) is stripped and the command
  recomposed with the same operation identifier, closing the untouched-
  draft bypass of the selectedModel gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift (1)

674-678: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve the hidden model selection during template refresh.

At Line 678, .off makes selectedModel nil by design, so every refresh clears selectedModelID. Validate the stored ID directly against the refreshed template instead.

Proposed fix
-            selectedModelID = selectedModel?.id
+            selectedModelID = selectedModelID.flatMap { modelID in
+                selectedTemplate.flatMap { template in
+                    MobileTaskAgentProvider(command: template.command)?
+                        .model(id: modelID)?
+                        .id
+                }
+            }
🤖 Prompt for 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.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`
around lines 674 - 678, Update the template refresh logic in TaskComposerSheet
so selectedModelID is not cleared when selectedModel is nil, including the
intentional `.off` state. Validate the existing stored model ID directly against
the refreshed selected template, and only update it when that template no longer
contains the ID.
🤖 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/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift`:
- Around line 104-120: Update the model-flag scanning logic around tokenRange
and containsUnquotedCommandBoundary to split an unquoted shell operator suffix
from an attached value, rather than treating the whole token as only a boundary.
Replace the existing model value for both separated and equals forms while
preserving the operator and stopping at the first simple-command boundary. Add
regressions for legacy; and legacy&& cases, including --model=legacy;.

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`:
- Around line 674-678: Update the template refresh logic in TaskComposerSheet so
selectedModelID is not cleared when selectedModel is nil, including the
intentional `.off` state. Validate the existing stored model ID directly against
the refreshed selected template, and only update it when that template no longer
contains the ID.
🪄 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 Plus

Run ID: 2954d896-0503-4d42-86f4-a05fb5d28dd3

📥 Commits

Reviewing files that changed from the base of the PR and between feff01d and 4d862a1.

📒 Files selected for processing (4)
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskAgentProviderTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift

azooz2003-bit and others added 11 commits July 28, 2026 23:05
- Build the composer options sheet lazily: MinimalLayout now takes a
  deferred builder, so directory-candidate construction (a workspace
  walk) runs only when Task Options is presented, not on every prompt
  keystroke's body rebuild.
- Reconcile a hidden model at the submission boundary by marking the
  request dirty instead of post-hoc snapshot surgery: resolution runs
  through makeSubmissionSnapshot (whose selectedModel gate strips the
  model) and MobileTaskSubmissionIdentity mints a fresh operation ID for
  the changed bytes, keeping retries idempotent.
- Process a model flag attached to a command separator: --model=old;,
  --model old;, and --model; are rewritten before scanning stops, so a
  stale value can no longer override the selection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Reconcile a hidden model at BOTH request-resolution boundaries via one
  shared resolver: when the Off picker hides a model a clean cached
  request still carries, resolution is forced through the selectedModel
  gate and the identity mints a fresh operation ID, so a persisted draft
  can never pair model-less bytes with an ID previously bound to
  model-bearing bytes. Replaces the submit-only proxy check.
- Treat redirection operators as part of the simple command: & adjacent
  to > (2>&1, >&2, &>file) and | preceded by > (>|file) no longer end
  the flag scan, so a stale --model after a redirection is still
  replaced. Control operators (;, |, &, &&, ||) still end it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Stop the model-flag scan at an unquoted word-initial #: a commented
  flag is never rewritten, and the selection is inserted after the first
  token instead of being silently swallowed by a comment edit.
- Do not reuse a draft's operation ID (or restore its completed-
  operation recovery) when the draft's model no longer survives
  curated-list validation; the resulting default-model command gets a
  fresh idempotency key.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cker

# Conflicts:
#	Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift
#	Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
#	Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
Dogfood feedback: + implied adding something; the button configures the
task (name, Mac, directory), so it now shows slider.horizontal.3.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dogfood feedback: switching the agent or model to a longer title left
the pill label clipped for the length of the resize animation. The pill
content now uses fixedSize so the capsule adopts the new intrinsic
width immediately, and the label subtree is identity-keyed on the title
so it swaps instead of animating through stale-width frames.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ipping

Dogfood feedback: the combined lab variant hid the standalone model
pill (models lived only in the agent submenu), which read as the model
picker disappearing. The composer layout now has one canonical model
treatment: a dedicated pill beside the agent pill for every non-Off
variant; the agent menu is forced plain and the options sheet never
repeats the contextRow, so the pill stays the single entry point.

The label clipping on longer titles survived the fixedSize fix because
the identity key sat on the label content while the UIKit menu button
still animated its frame. The .id now keys the whole Menu, so a title
change swaps the button instead of animating through stale bounds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dogfood request: pills should dissolve toward the neighboring options
and submit buttons instead of clipping at the scroller bounds. iOS 26
uses the native soft scroll edge effect (progressive blur + fade);
earlier systems approximate it with a 14pt alpha-mask fade per edge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cker

# Conflicts:
#	ios/cmux/Resources/Localizable.xcstrings
@cursor

cursor Bot commented Jul 31, 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 and others added 2 commits July 30, 2026 20:34
Main's typed MacPairingKey re-key changed the secondary-subscription
registry key from a device-id string to the full pairing key. The
attachment and model-discovery capability checks now resolve through a
shared controlSubscriptionMatching helper that keeps the old semantics:
exact pairing when a tag is given, any same-device pairing otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dogfood feedback: the scroll edge effect never rendered because the
buttons sat NEXT TO the scroller, so no content ever passed beneath an
edge. The pill scroller now spans the bar with the attachment/options
buttons and the submit button living in its leading/trailing safe-area
insets: pills genuinely scroll under them, which is what activates the
native iOS 26 soft scroll edge effect (progressive blur + fade). Pre-26
keeps an opaque button background as the fallback occlusion.

Also stop the prompt editor from yanking long text back down while
scrolling up: interactive keyboard dismissal resized the editor every
drag frame and UITextView re-scrolled to the caret each time; dismissal
is now immediate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit and others added 5 commits July 30, 2026 20:57
…ction

SwiftUI's scrollEdgeEffectStyle only styles effects the system already
owns (bars/glass), so pills merely underlapped the buttons. The bar row
is now a thin UIKit host: a horizontal UIScrollView spans the bar, the
button clusters float above it, and on iOS 26 each cluster carries a
UIScrollEdgeElementContainerInteraction bound to the scroll view's
edge, which renders the real progressive blur+fade beneath the buttons
as pills pass under. Pre-26 clusters keep an opaque background.
Content insets track the cluster widths (the attachment button is
capability-gated), resting the pills between the clusters.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The UIKit-hosted bar livelocked SwiftUI (hosting-controller sizing
feedback re-rendered every frame) and blanked the composer, which also
made the pills unscrollable. The pills return to the proven SwiftUI
ScrollView under safe-area-inset button clusters; a zero-size probe in
the scroll content walks to the backing UIScrollView and a coordinator
binds UIScrollEdgeElementContainerInteraction to transparent container
views behind each cluster. Fail-soft: if the probe finds no scroll view
or the OS predates iOS 26, nothing attaches and the bar just underlaps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two native attempts failed structurally: SwiftUI's scrollEdgeEffectStyle
never renders for floating siblings, and hosting the bar (or just the
clusters) in UIKit for UIScrollEdgeElementContainerInteraction either
livelocked the view graph or dropped cluster content, because the
effect's shape must come from the container's descendants. The bar now
stays pure SwiftUI: clusters carry an ultraThinMaterial background
(full blur under the buttons) and a 24pt gradient-masked material band
beside each cluster fades passing pills into the bar background --
the same progressive blur+fade the system effect draws, with no
UIKit bridging left to break scrolling or layout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 31, 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.

@cubic-dev-ai cubic-dev-ai 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.

34 issues found across 88 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerMinimalLayout.swift:178">
P2: Shell or unrecognized templates can still show a model pill after the template list is refreshed, exposing a model menu whose selections are rejected by `selectModel`. Gate this condition on the selected template having a `MobileTaskAgentProvider` (or clear the stale selection before passing `models`) so the UI preserves the known-provider-only invariant.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAgentMenuContent.swift:54">
P2: Some model choices shown for a non-selected agent cannot actually be selected after that provider has a cached discovery result, because this submenu uses the curated catalog while `selectTemplate` validates against the provider's discovered models. Build each provider submenu from the same per-template availability data used for validation, or make validation accept exactly the catalog displayed here.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift:169">
P1: Relaunching after an attachment-bearing attempt can reuse an idempotency key for attachment-free request bytes; drafts need to invalidate the operation/recovery IDs when staged attachments cannot be restored, or persist enough attachment state to reconstruct the request.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift:44">
P1: Editing the selected template can leave a model selected for a command/provider that does not support it, causing the picker to remain visible for shell/unrecognized commands and potentially injecting the old provider’s model ID into the new provider’s command. Revalidate or clear the selection whenever the refreshed template’s detected provider changes, while preserving raw-name selections only for the same provider’s delisted models.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerFailureRecoveryContent.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerFailureRecoveryContent.swift:25">
P2: During recovery refresh, VoiceOver loses the refresh action's localized name and exposes only a generic progress/disabled control; retaining an accessibility label for the current refresh action while showing the spinner would keep the operation understandable.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift:18">
P3: Images over the 60 MB raw-input ceiling are shown as if compression failed below 8 MB, even when compression was never attempted; use a distinct raw-size error and localized message.</violation>

<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift:21">
P2: Cancelling an in-flight photo batch can show a false “That image couldn’t be compressed below 8 MB.” alert because `prepare(url:)` returns `nil` on cancellation and this guard converts it to `.imageRejected`; preserve cancellation before mapping `nil` to an image rejection.</violation>

<violation number="3" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift:61">
P2: A file that changes or materializes with a different size between the metadata read and `copyItem` can appear staged but fail upload because its recorded `byteCount` differs from the copied bytes; validate the copied destination size and use that value before creating the attachment.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskAttachments.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskAttachments.swift:117">
P1: A timeout or connection loss after the Mac accepts any non-final chunk makes the attachment impossible to retry: the next attempt resends offset 0, but the host requires the current staged byte count as the contiguous offset. Making upload retries resume from the host’s acknowledged byte count (or adding a resumable upload/progress query) would preserve the advertised idempotent retry behavior.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskModelAvailability.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskModelAvailability.swift:54">
P1: A template/provider change can submit a model ID belonging to the old provider because this fallback preserves any missing ID without checking provider identity. Scope stale-ID preservation to the same provider/template or clear the selection when the provider changes, while retaining the intended fallback only for a refresh that delists a model from the same provider.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileImageAttachmentPreparer.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileImageAttachmentPreparer.swift:33">
P2: Cancelling an in-flight prepare does not stop stale image-encoding work, so quickly replacing a large image can leave multiple expensive ImageIO jobs running in the background. Make the encode loop cancellation-aware by checking cancellation between compression attempts (or otherwise move the cancellable boundary inside the work).</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPromptCard.swift:91">
P2: Long discovered model names can make the trailing-chip layout push the paperclip attachment control off the prompt card because the chip refuses horizontal compression. Constraining the chip width and truncating its label, or making this row horizontally scrollable, keeps both controls reachable.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStrip.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStrip.swift:52">
P2: The attachment remove control has only a 22×22 hit region, making the close affordance difficult to activate and inconsistent with the composer’s 44pt activation-target contract. Keep the 18pt icon but give the button a 44×44 frame.</violation>

<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStrip.swift:63">
P2: When request editing is disabled, VoiceOver still exposes the chip’s Remove Attachment action even though the visible close button is disabled and `removeAttachment` rejects the same phase, leaving an accessible action that does nothing. Propagating the disabled state to the combined chip (or omitting the action while disabled) keeps the accessibility behavior consistent.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift:154">
P2: A previously selected model is dropped on relaunch when a cached refresh delists it. The draft restore path validates against the new list but does not pass the draft ID as `previouslyValidModelID`, even though that API is specifically designed to preserve such selections; passing the draft ID here would keep the choice visible and idempotent.</violation>

<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift:860">
P1: Refreshing templates can submit the previous template's model to a different provider because the raw-ID fallback preserves it across template changes; preserve the ID only when the selected template/provider is unchanged, otherwise clear or revalidate it.</violation>
</file>

<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelDiscovery.swift">

<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelDiscovery.swift:74">
P2: A wall-clock rollback can make an expired model list pass this cache check and be served stale beyond the ten-minute TTL; reject negative ages or use a monotonic clock for expiry.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift:118">
P2: Quoted or escaped option words are not recognized as model flags or end-of-options markers, so an explicit selection can leave the template's original model setting in place or rewrite positional arguments; normalizing shell quoting/escaping for comparisons while retaining raw ranges for edits would make quoted and unquoted forms consistent.</violation>

<violation number="2" location="Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskAgentProvider.swift:126">
P1: A dangling model flag consumes a subsequent option as its value, so model selection can silently remove options from user-authored templates; treating option-like values as missing and inserting the selected value before them would preserve the option.</violation>
</file>

<file name="Sources/Mobile/MobileHostService+TicketAuthorization.swift">

<violation number="1" location="Sources/Mobile/MobileHostService+TicketAuthorization.swift:69">
P2: A workspace- or terminal-scoped attach ticket can now perform the Mac-wide attachment upload because this branch skips the existing Mac-scoped authorization helper. Applying `ticketMacScopedWorkspaceMutationAuthorizationError(authorization: authorization)` here would keep uploads consistent with the `workspace.create` operation they precede.</violation>
</file>

<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift">

<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift:172">
P1: Concurrent retries can corrupt an attachment: both requests may pass the offset check against the same size and append, while each reports a smaller `receivedBytes` than the file actually contains. Serializing by operation/upload ID (or using an equivalent filesystem lock and atomic state transition) would preserve the contiguous-offset and idempotency contracts.</violation>

<violation number="2" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift:173">
P2: Retrying a successfully staged non-final chunk after a lost RPC response is rejected as out of order. Because progress is appended before the response and no chunk receipt is recorded, the same `uploadID`/offset cannot be retried; recording and validating chunk progress (or otherwise making duplicate chunks return the existing result) would preserve the store's stated retry/idempotency behavior.</violation>

<violation number="3" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift:201">
P2: Uploaded attachments are not guaranteed to be owner-only: `createFile`/`moveItem` never apply private permissions, and a moved file retains its staging mode. Applying 0700 to the cache/operation directories and 0600 to staged, final, and metadata files—including after atomic writes and moves—would avoid local data exposure.</violation>

<violation number="4" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStore.swift:201">
P1: A filesystem error between moving the final file and writing its completion record makes an attachment unrecoverable or creates a duplicate on retry. Finalization would be safer if the completion state and file move were made recoverable/transactional, or if retry reconciliation detected the already-moved file before starting a new staging upload.</violation>
</file>

<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStoreError.swift">

<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStoreError.swift:2">
P3: This public error carries a human-readable `message` but doesn't conform to `LocalizedError`, so callers using `error.localizedDescription` (e.g. a generic UI alert) get the generic "The operation couldn't be completed" text instead of the message the struct was built to convey. Consider conforming to `LocalizedError` and returning `message` from `errorDescription`, matching the pattern used by other error types in this codebase.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift:42">
P3: This chip's Menu label omits the .id(selectedModelName) identity-swap that the sibling pill treatments add for the same reason. The neighboring modelPill/agentPill in TaskComposerMinimalLayout.swift use both .fixedSize and .id(...), noting that fixedSize alone doesn't stop the UIKit Menu button from animating its frame and clipping the label on iOS 26 — consider matching that pattern here so the trailingChip label doesn't clip when the selected model name changes length.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift:212">
P2: These two "rejects unknown values" tests rely on the host process environment not setting CMUX_UITEST_TASK_COMPOSER_PREVIEW (or the layout/model overrides). If that env var is set on a dev or CI machine running XCUITests/screenshot recipes, the stored-unknown fallback resolves to .classic/.off instead of the asserted .composer/.combined and the test flakes. Pass an explicit environment (e.g. [String:String]()) like the sibling preview tests do to keep these hermetic.</violation>
</file>

<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelProvider.swift">

<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelProvider.swift:11">
P2: The curated model catalog now exists in two independently maintained lists, so Mac fallback discovery and the iOS fallback can drift and present different choices. A shared catalog or an explicit cross-platform consistency test would keep the provider IDs and display names synchronized.</violation>
</file>

<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelParser.swift">

<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelParser.swift:42">
P2: Codex model discovery silently ignores a valid TOML configuration such as `model = 'gpt-5'` because the parser accepts only double-quoted values. Supporting both TOML string forms (or using a TOML parser) would ensure manually edited configs are reflected in the model picker.</violation>
</file>

<file name="Sources/TerminalController+MobileTaskAttachments.swift">

<violation number="1" location="Sources/TerminalController+MobileTaskAttachments.swift:22">
P1: A valid mobile client can consume unbounded disk space by starting uploads with fresh `operation_id` UUIDs. The per-operation 64 MiB cap does not provide a host-wide limit, and this handler creates a new store for every request; abandoned operation directories are only pruned opportunistically after they are already retained for seven days. Adding a process-wide/account-scoped staging quota and rejecting new operations once it is exceeded would prevent the attachment endpoint from becoming a disk-exhaustion vector.</violation>

<violation number="2" location="Sources/TerminalController+MobileTaskAttachments.swift:30">
P2: Attachment uploads synchronously perform directory creation, pruning, metadata reads/writes, and up to a 3 MiB `FileHandle` write in `store.upload`. This RPC is not classified in `ControlCommandExecutionPolicy.socketWorkerMethods`, so `mobile.task.attachment.upload` reaches `v2MobileTaskAttachmentUpload` through the main-actor dispatch and can freeze the app while each chunk is staged. Routing the filesystem operation through the worker lane (with an explicit concurrency/admission limit) would keep large uploads off the UI actor.</violation>
</file>

<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift:46">
P2: A second picker batch can be submitted while the first batch is still finishing. Both `stageSelectedPhotos` and `stageSelectedFiles` cancel the previous task, but each task unconditionally clears the shared `attachmentStagingTask` in its `defer`; the cancelled task can therefore clear the reference to the newer task after the newer task has started. Since the submit controls use `attachmentStagingTask == nil`, this can enable submission before the newest files have been staged, causing those selected files to be absent from the snapshot/upload. Clearing the reference only when it still belongs to the completing batch (for example with a per-batch token or task identity) would preserve the staging gate.</violation>

<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift:137">
P2: A file or image that finishes staging after the user switches the template to a plain shell is still appended here. `selectTemplate` clears the existing attachments when selecting a shell, but the in-flight staging task is not cancelled; the late attachment can therefore reappear in the shell composer. `uploadAttachments` then returns success with no paths when `snapshot.composition.initialCommand == nil`, so the attachment is silently ignored on submit. Rejecting and deleting late results for plain-shell templates (and/or cancelling the staging task during the template change) keeps the UI and submission consistent.</violation>
</file>

<file name="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelShellRunner.swift">

<violation number="1" location="Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskModels/MobileTaskModelShellRunner.swift:48">
P1: Model discovery can outlive its advertised 5-second deadline and leave the worker request hung. `readDataToEndOfFile()` blocks before `waitUntilExit()`, while the timeout only kills the shell PID; a descendant that inherited stdout can keep the pipe open indefinitely. A nonblocking reader/cancellation pipe and bounded process-group termination would make the deadline effective.

(Based on your team's feedback about nonblocking process pipe draining.) [FEEDBACK_USED].</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

directory: directory,
workspaceName: workspaceName,
didEditDirectory: didEditDirectory,
attachments: attachments.map(\.submissionAttachment),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Relaunching after an attachment-bearing attempt can reuse an idempotency key for attachment-free request bytes; drafts need to invalidate the operation/recovery IDs when staged attachments cannot be restored, or persist enough attachment state to reconstruct the request.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift, line 169:

<comment>Relaunching after an attachment-bearing attempt can reuse an idempotency key for attachment-free request bytes; drafts need to invalidate the operation/recovery IDs when staged attachments cannot be restored, or persist enough attachment state to reconstruct the request.</comment>

<file context>
@@ -128,11 +160,13 @@ extension TaskComposerSheet {
             directory: directory,
             workspaceName: workspaceName,
             didEditDirectory: didEditDirectory,
+            attachments: attachments.map(\.submissionAttachment),
             operationID: operationID
         )
</file context>

// re-enabling a variant restores the visible choice.
guard displaySettings.taskComposerModelPickerVariant.renderedVariant != .off,
let selectedModelID else { return nil }
return modelAvailability.selectedModel(id: selectedModelID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Editing the selected template can leave a model selected for a command/provider that does not support it, causing the picker to remain visible for shell/unrecognized commands and potentially injecting the old provider’s model ID into the new provider’s command. Revalidate or clear the selection whenever the refreshed template’s detected provider changes, while preserving raw-name selections only for the same provider’s delisted models.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift, line 44:

<comment>Editing the selected template can leave a model selected for a command/provider that does not support it, causing the picker to remain visible for shell/unrecognized commands and potentially injecting the old provider’s model ID into the new provider’s command. Revalidate or clear the selection whenever the refreshed template’s detected provider changes, while preserving raw-name selections only for the same provider’s delisted models.</comment>

<file context>
@@ -0,0 +1,80 @@
+        // re-enabling a variant restores the visible choice.
+        guard displaySettings.taskComposerModelPickerVariant.renderedVariant != .off,
+              let selectedModelID else { return nil }
+        return modelAvailability.selectedModel(id: selectedModelID)
+    }
+
</file context>

"upload_id": attachment.id.uuidString,
"file_name": attachment.displayName,
"total_bytes": data.count,
"offset": range.lowerBound,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: A timeout or connection loss after the Mac accepts any non-final chunk makes the attachment impossible to retry: the next attempt resends offset 0, but the host requires the current staged byte count as the contiguous offset. Making upload retries resume from the host’s acknowledged byte count (or adding a resumable upload/progress query) would preserve the advertised idempotent retry behavior.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskAttachments.swift, line 117:

<comment>A timeout or connection loss after the Mac accepts any non-final chunk makes the attachment impossible to retry: the next attempt resends offset 0, but the host requires the current staged byte count as the contiguous offset. Making upload retries resume from the host’s acknowledged byte count (or adding a resumable upload/progress query) would preserve the advertised idempotent retry behavior.</comment>

<file context>
@@ -0,0 +1,187 @@
+                    "upload_id": attachment.id.uuidString,
+                    "file_name": attachment.displayName,
+                    "total_bytes": data.count,
+                    "offset": range.lowerBound,
+                    "data_b64": data.subdata(in: range).base64EncodedString(),
+                    "last": isLast,
</file context>

public func selectedModel(id: String?) -> MobileTaskAgentModel? {
guard let id else { return nil }
return models.first { $0.id == id }
?? MobileTaskAgentModel(id: id, displayName: id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: A template/provider change can submit a model ID belonging to the old provider because this fallback preserves any missing ID without checking provider identity. Scope stale-ID preservation to the same provider/template or clear the selection when the provider changes, while retaining the intended fallback only for a refresh that delists a model from the same provider.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskModelAvailability.swift, line 54:

<comment>A template/provider change can submit a model ID belonging to the old provider because this fallback preserves any missing ID without checking provider identity. Scope stale-ID preservation to the same provider/template or clear the selection when the provider changes, while retaining the intended fallback only for a refresh that delists a model from the same provider.</comment>

<file context>
@@ -0,0 +1,56 @@
+    public func selectedModel(id: String?) -> MobileTaskAgentModel? {
+        guard let id else { return nil }
+        return models.first { $0.id == id }
+            ?? MobileTaskAgentModel(id: id, displayName: id)
+    }
+}
</file context>

if let selectedTemplateID, !templates.contains(where: { $0.id == selectedTemplateID }) {
self.selectedTemplateID = templates.first?.id
}
selectedModelID = selectedModel?.id

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: Refreshing templates can submit the previous template's model to a different provider because the raw-ID fallback preserves it across template changes; preserve the ID only when the selected template/provider is unchanged, otherwise clear or revalidate it.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift, line 860:

<comment>Refreshing templates can submit the previous template's model to a different provider because the raw-ID fallback preserves it across template changes; preserve the ID only when the selected template/provider is unchanged, otherwise clear or revalidate it.</comment>

<file context>
@@ -641,6 +857,7 @@ struct TaskComposerSheet: View {
             if let selectedTemplateID, !templates.contains(where: { $0.id == selectedTemplateID }) {
                 self.selectedTemplateID = templates.first?.id
             }
+            selectedModelID = selectedModel?.id
             // Sync template edits unless the user typed the directory.
             syncSuggestedDirectory()
</file context>

return
}
updateSubmissionRequest(reconcileRecovery: true) {
attachments.append(attachment)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A file or image that finishes staging after the user switches the template to a plain shell is still appended here. selectTemplate clears the existing attachments when selecting a shell, but the in-flight staging task is not cancelled; the late attachment can therefore reappear in the shell composer. uploadAttachments then returns success with no paths when snapshot.composition.initialCommand == nil, so the attachment is silently ignored on submit. Rejecting and deleting late results for plain-shell templates (and/or cancelling the staging task during the template change) keeps the UI and submission consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift, line 137:

<comment>A file or image that finishes staging after the user switches the template to a plain shell is still appended here. `selectTemplate` clears the existing attachments when selecting a shell, but the in-flight staging task is not cancelled; the late attachment can therefore reappear in the shell composer. `uploadAttachments` then returns success with no paths when `snapshot.composition.initialCommand == nil`, so the attachment is silently ignored on submit. Rejecting and deleting late results for plain-shell templates (and/or cancelling the staging task during the template change) keeps the UI and submission consistent.</comment>

<file context>
@@ -0,0 +1,238 @@
+            return
+        }
+        updateSubmissionRequest(reconcileRecovery: true) {
+            attachments.append(attachment)
+        }
+    }
</file context>

attachmentStagingTask = Task { @MainActor in
defer {
attachmentPhotoSelection = []
attachmentStagingTask = nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: A second picker batch can be submitted while the first batch is still finishing. Both stageSelectedPhotos and stageSelectedFiles cancel the previous task, but each task unconditionally clears the shared attachmentStagingTask in its defer; the cancelled task can therefore clear the reference to the newer task after the newer task has started. Since the submit controls use attachmentStagingTask == nil, this can enable submission before the newest files have been staged, causing those selected files to be absent from the snapshot/upload. Clearing the reference only when it still belongs to the completing batch (for example with a per-batch token or task identity) would preserve the staging gate.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift, line 46:

<comment>A second picker batch can be submitted while the first batch is still finishing. Both `stageSelectedPhotos` and `stageSelectedFiles` cancel the previous task, but each task unconditionally clears the shared `attachmentStagingTask` in its `defer`; the cancelled task can therefore clear the reference to the newer task after the newer task has started. Since the submit controls use `attachmentStagingTask == nil`, this can enable submission before the newest files have been staged, causing those selected files to be absent from the snapshot/upload. Clearing the reference only when it still belongs to the completing batch (for example with a per-batch token or task identity) would preserve the staging gate.</comment>

<file context>
@@ -0,0 +1,238 @@
+        attachmentStagingTask = Task { @MainActor in
+            defer {
+                attachmentPhotoSelection = []
+                attachmentStagingTask = nil
+            }
+            for item in items.prefix(remainingAttachmentCount) {
</file context>

originalFileName: String
) async throws -> TaskComposerAttachment {
let sourceByteCount = try sourceFileByteCount(at: sourceURL)
guard sourceByteCount <= MobileImageAttachmentPreparer.maximumRawInputBytes else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: Images over the 60 MB raw-input ceiling are shown as if compression failed below 8 MB, even when compression was never attempted; use a distinct raw-size error and localized message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentStager.swift, line 18:

<comment>Images over the 60 MB raw-input ceiling are shown as if compression failed below 8 MB, even when compression was never attempted; use a distinct raw-size error and localized message.</comment>

<file context>
@@ -0,0 +1,124 @@
+        originalFileName: String
+    ) async throws -> TaskComposerAttachment {
+        let sourceByteCount = try sourceFileByteCount(at: sourceURL)
+        guard sourceByteCount <= MobileImageAttachmentPreparer.maximumRawInputBytes else {
+            throw StagingError.imageRejected
+        }
</file context>

@@ -0,0 +1,17 @@
/// A wire-ready mobile task attachment storage failure.
public struct MobileTaskAttachmentStoreError: Error, Equatable, Sendable {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This public error carries a human-readable message but doesn't conform to LocalizedError, so callers using error.localizedDescription (e.g. a generic UI alert) get the generic "The operation couldn't be completed" text instead of the message the struct was built to convey. Consider conforming to LocalizedError and returning message from errorDescription, matching the pattern used by other error types in this codebase.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/MobileTaskAttachments/MobileTaskAttachmentStoreError.swift, line 2:

<comment>This public error carries a human-readable `message` but doesn't conform to `LocalizedError`, so callers using `error.localizedDescription` (e.g. a generic UI alert) get the generic "The operation couldn't be completed" text instead of the message the struct was built to convey. Consider conforming to `LocalizedError` and returning `message` from `errorDescription`, matching the pattern used by other error types in this codebase.</comment>

<file context>
@@ -0,0 +1,17 @@
+/// A wire-ready mobile task attachment storage failure.
+public struct MobileTaskAttachmentStoreError: Error, Equatable, Sendable {
+    /// RPC error code.
+    public let code: String
</file context>

}
.disabled(isDisabled)
.taskComposerModelAccessibility(valueName: selectedModelName)
.accessibilityIdentifier("MobileTaskComposerModelChip")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: This chip's Menu label omits the .id(selectedModelName) identity-swap that the sibling pill treatments add for the same reason. The neighboring modelPill/agentPill in TaskComposerMinimalLayout.swift use both .fixedSize and .id(...), noting that fixedSize alone doesn't stop the UIKit Menu button from animating its frame and clipping the label on iOS 26 — consider matching that pattern here so the trailingChip label doesn't clip when the selected model name changes length.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerModelChip.swift, line 42:

<comment>This chip's Menu label omits the .id(selectedModelName) identity-swap that the sibling pill treatments add for the same reason. The neighboring modelPill/agentPill in TaskComposerMinimalLayout.swift use both .fixedSize and .id(...), noting that fixedSize alone doesn't stop the UIKit Menu button from animating its frame and clipping the label on iOS 26 — consider matching that pattern here so the trailingChip label doesn't clip when the selected model name changes length.</comment>

<file context>
@@ -0,0 +1,49 @@
+        }
+        .disabled(isDisabled)
+        .taskComposerModelAccessibility(valueName: selectedModelName)
+        .accessibilityIdentifier("MobileTaskComposerModelChip")
+    }
+
</file context>

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift">

<violation number="1" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift:21">
P3: The native UIKit pillar bar duplicates layout constants (`fixedClusterHorizontalPadding = 16`, `fixedControlVisualInset = 3`, `clusterGap = 10`) that must stay in lockstep with paddings defined in the SwiftUI layer (`TaskComposerMinimalLayout`'s `leadingCluster`/`trailingCluster` `.padding(.leading/.trailing, 16)`). Because the two values live in different files with no shared constant, a future change to the SwiftUI cluster padding will silently misalign the native capsule glass geometry and the pills' scroll-to-clear spacing without any compile-time signal. Consider deriving these values from a single shared metric (e.g., passing the paddings in from the SwiftUI side through `TaskComposerPillBar`, or exposing a shared constant) so the native overlay cannot drift.</violation>

<violation number="2" location="Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift:56">
P2: VoiceOver can announce the model/agent pills before the leading attachment/options controls because the UIKit sibling order is opposite the composer's visual order; an explicit accessibility element order (leading host, pills host, trailing host) would keep keyboard and assistive-technology navigation consistent with the UI.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@cursor

cursor Bot commented Aug 3, 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 Aug 3, 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 Aug 4, 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.

…cker

# Conflicts:
#	Packages/macOS/CmuxControlSocket/Tests/CmuxControlSocketTests/ControlCommandExecutionPolicyTests.swift
@cursor

cursor Bot commented Aug 4, 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
azooz2003-bit merged commit ed44c7d into main Aug 4, 2026
6 checks passed
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