Repository navigation
Add Xcode-style project visualizer pane - #4996
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR introduces a complete Project Panel feature for cmux, enabling users to browse, inspect, and interact with Xcode projects. The implementation spans a new local Swift package (CMUXProjectModel) with data models and adapters, multiple SwiftUI view layers for project navigation and settings editing, RPC command infrastructure, workspace integration, and session persistence. The feature is wired into the existing panel system and CLI. ChangesProject Panel Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
|
New `project` pane type that opens a `.xcodeproj` or `.xcworkspace` and renders it the way Xcode does: workspace + project navigator, targets table, Build Settings Levels view, and schemes. Hides low-value pbxproj plumbing (raw UUIDs, xcuserdata, container blueprint strings) while keeping the model honest about resolved vs derived values. New `CMUXProjectModel` leaf SPM package with: - `XcodeProjectAdapter` (tuist/XcodeProj-backed) that walks groups, targets, schemes, build configurations, and target memberships. - `XcconfigParser` (handles `#include` / `#include?`, cycle detection, trailing line comments) merged into target metadata so PRODUCT_BUNDLE_IDENTIFIER, *_DEPLOYMENT_TARGET, SUPPORTED_PLATFORMS populate even when the values live in `.xcconfig`. - 15 Swift Testing tests, green in ~60 ms total. Surfaces through the existing Panel plumbing: - `ProjectPanel` runtime + four SwiftUI tab views (Files, Targets, Build Settings, Schemes) routed through `PanelContentView`. - Session persistence as `SessionProjectPanelSnapshot`. - `cmux project open <path-to-.xcodeproj-or-.xcworkspace>` CLI, backed by `project.open` JSON-RPC method on `TerminalController`. - Debug RPCs (`project.set_tab`, `project.set_selected_target`, `project.set_selected_file`, `project.set_settings_filter`, `project.get_state`) for headless verification. - `openFileSurfaces` now routes `.xcodeproj` / `.xcworkspace` paths to a project surface (right sidebar Files mode, drag-drop, `file.open` RPC, `cmux open <path>` CLI all benefit). UI polish notes: - Tab strip rendered as a segmented control (active tab filled accent). - Files tab: filter bar at top, single-pane when nothing selected, target-membership chips per file, default depth-1 expansion. - Targets tab: product-type icon header, dependencies list, build summary with jump to Build Settings. - Build Settings tab: Levels-style table with Setting / Effective / Target / Project columns, leading accent bar on overridden rows, in-header Target picker, search filter, "Customized only" toggle. - Schemes tab: shared + per-user schemes flattened across modules with dedup, inline shared/personal badge. - Outer pane VStack anchored to .topLeading so the Build Settings table stays at the top of the pane even when filter narrows rows. Iteration log preserved in `.iter-logs/CHANGELOG.md` (11 passes with three reviewer agents per pass: design, product, technical). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
091acc8 to
6b41ec8
Compare
Greptile SummaryThis PR adds a full Xcode-style project visualizer pane backed by a new
Confidence Score: 3/5The session restore gap means users lose their tab, scheme, configuration, and file selection every relaunch with a project pane open. Session persistence is one of the explicitly listed features of this PR, yet the restore branch silently drops four of the five persisted fields. The xcconfig += parser bug produces wrong keys for a common xcconfig pattern. Both are present-defect issues on the changed path that affect user-visible behavior today. Sources/Workspace.swift (session restore branch) and Packages/CMUXProjectModel/Sources/CMUXProjectModel/XcconfigParser.swift (assignment parsing) Important Files Changed
Sequence DiagramsequenceDiagram
participant CLI as cmux CLI
participant TC as TerminalController
participant WS as Workspace (MainActor)
participant PP as ProjectPanel (MainActor)
participant ADP as XcodeProjectAdapter (detached)
CLI->>TC: "project.open {path}"
TC->>WS: newProjectSurface(inPane:projectPath:focus:)
WS->>PP: init(projectURL:)
WS->>PP: reload()
PP->>ADP: Task.detached — adapter.load(at:)
ADP-->>PP: await applyLoaded(model)
PP->>PP: validate/reset selections
note over WS,PP: Session restore path
WS->>PP: newProjectSurface(focus:false)
WS--xPP: activeTab/selectedScheme/selectedConfig/selectedFile NOT applied
PP->>ADP: Task.detached — adapter.load(at:)
ADP-->>PP: await applyLoaded(model) sets defaults not saved state
Reviews (2): Last reviewed commit: "Fix concurrent self capture warnings in ..." | Re-trigger Greptile |
| @MainActor | ||
| public final class ProjectPanel: NSObject, Panel, ObservableObject { |
There was a problem hiding this comment.
ObservableObject/@Published instead of @Observable
ProjectPanel uses ObservableObject + @Published while all four tab views use @ObservedObject var panel: ProjectPanel. This causes whole-panel invalidation: any @Published write — settingsSearchText, selectedSchemeName, selectedTargetID, loadState, etc. — invalidates every view that holds an @ObservedObject reference, regardless of whether the changed property is used in that view's rendered subtree. With @Observable, SwiftUI tracks only the properties actually read during a view's body, so a keystroke in the Settings filter won't recompute flattenedRows in ProjectFilesTabView or the target list in ProjectTargetsTabView. The rule for new cmux-owned SwiftUI state is @Observable + @State or value snapshots.
Rule Used: Flag SwiftUI changes that can cause stale state, b... (source)
| var body: some View { | ||
| let rows = flattenedRows | ||
| VStack(alignment: .leading, spacing: 0) { |
There was a problem hiding this comment.
Full tree walk inside
body on every invalidation
let rows = flattenedRows is computed inline inside body. flattenedRows recursively walks the entire project file tree — which can span thousands of nodes in a real workspace. Because panel is @ObservedObject, any @Published change on ProjectPanel triggers a full re-render and a fresh tree walk. The same pattern appears in ProjectBuildSettingsTabView (let computedRows = rows). The fix is to switch ProjectPanel to @Observable and cache the row computation with a @State snapshot updated only when filesSearchText or collapsedNodeIDs change.
Rule Used: Flag production code that adds nested full-collect... (source)
| TextField("Filter files (e.g. AppDelegate)", text: $panel.filesSearchText) | ||
| .textFieldStyle(.plain) |
There was a problem hiding this comment.
Hardcoded English strings throughout new production SwiftUI views
None of the new user-facing text is routed through String(localized:defaultValue:). Examples across all five new view files: "Filter files (e.g. AppDelegate)" and "Select a file" here; column headers and "Customized only" in ProjectBuildSettingsTabView; "shared" / "personal" badges in ProjectSchemesTabView; "Reload project", "Loading …", "Failed:" in ProjectPanelView; and error strings in ProjectPanel.describe. Every string visible in the UI must use a stable localization key, an English defaultValue, and a matching entry in Resources/Localizable.xcstrings for every locale already in the catalog.
Rule Used: Flag production user-facing text that is not fully... (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!
| @@ -12248,6 +12266,182 @@ class TerminalController { | |||
| return result | |||
There was a problem hiding this comment.
v2ProjectSetSelectedTarget silently misses targets in non-first modules
panel.loadState.model?.modules.first is used as the only search scope. For a .xcworkspace with multiple projects, any target belonging to the second or later module will never be found; the RPC returns {"target_name": "…", "target_id": ""} and panel.selectedTargetID is set to nil. Other RPCs like v2ProjectGetState already aggregate across all modules, so callers can legitimately pass names from non-first modules. The loop should search across model.modules in order, the same way applyLoaded and ProjectBuildSettingsTabView.selectedModule do.
| return "Unsupported project at \(url.path)" | ||
| case let .parseFailure(url, reason): | ||
| return "Parse failed at \(url.path): \(reason)" | ||
| } | ||
| } | ||
| return String(describing: error) |
There was a problem hiding this comment.
parseFailure reason exposes raw XcodeProj library error strings in the UI
ProjectLoadError.parseFailure(url, reason: String(describing: error)) is constructed from the raw error thrown by XcodeProj(path:), which can include XcodeProj-internal details. It is surfaced verbatim in the load-warning pill and the failed-state label. The UI message should stay in cmux terms (e.g. "Could not parse the project file") and the upstream detail should be kept in sanitized logs or internal telemetry only.
There was a problem hiding this comment.
Actionable comments posted: 30
🤖 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 `@CLI/cmux.swift`:
- Around line 4734-4745: The single-token branch currently treats any one-word
token as a path and should instead validate it using the looksLikePath(_:)
helper: capture the original-cased first token (e.g. rawFirst = args[0]) before
any lowercasing, then when args.count == 1 call looksLikePath(rawFirst) and only
accept it as subArgs if it returns true; otherwise throw the existing CLIError
usage message for unknown project subcommands (keep the existing throw path for
multi-token cases), and ensure subArgs is set from the validated rawFirst so the
later guard uses the correct rawPath.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/BuildConfigSummary.swift`:
- Around line 12-30: Add Swift-DocC triple-slash comments for each public stored
property (id, name, scope, baseConfigurationPath, rawSettings) and the public
initializer in BuildConfigSummary so every public symbol is documented; for the
initializer (init(id:name:scope:baseConfigurationPath:rawSettings:)) include a
brief overview plus /// - Parameters: entries describing each parameter and
their meanings, and for optional baseConfigurationPath note it can be nil,
keeping all comments concise and formatted with /// so they appear in generated
docs.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectFileNode.swift`:
- Around line 11-32: Add Swift-DocC triple-slash comments for every public
symbol in ProjectFileNode: document each stored property (id, displayName,
resolvedPath, fileType, existsOnDisk, memberships) with a brief description of
its purpose and any important behavior, and document the public initializer
(init(id:displayName:resolvedPath:fileType:existsOnDisk:memberships:)) including
parameter descriptions; ensure each comment uses /// and follows the project's
DocC style so all public symbols in ProjectFileNode are documented.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectGroup.swift`:
- Around line 12-30: Add Swift-DocC triple-slash documentation for all public
stored properties and the initializer in ProjectGroup: document the purpose of
each property (id: ProjectNodeID, displayName: String, resolvedPath: URL?,
style: ProjectGroupStyle, children: [ProjectNodeKind]) with a short description
and for the initializer add a summary plus /// - Parameters: entries describing
each parameter (id, displayName, resolvedPath, style, children). Place the ///
comments immediately above the property declarations and above the public init
to satisfy the package documentation guideline.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectLoadError.swift`:
- Around line 14-19: ProjectLoadError.parseFailure currently carries raw
third-party error text which gets shown in the UI; update the surface so
user-visible text is redacted while preserving raw details for logs.
Specifically, either change XcodeProjectAdapter so it no longer constructs
parseFailure(..., reason: String(describing: error)) for UI consumption but
instead passes a generic message like "Project parsing failed" and sends
String(describing: error) to an internal logger, or modify
ProjectPanel.describe(_:) to map ProjectLoadError.parseFailure(_, _) to a safe,
generic message (e.g. "Parse failed: internal parsing error") and write the
original reason to debug logs; ensure references: ProjectLoadError.parseFailure,
XcodeProjectAdapter, ProjectPanel.describe(_:), and ProjectPanelView are updated
accordingly.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectModule.swift`:
- Around line 9-41: Add Swift-DocC triple-slash comments for every public
symbol: document each public stored property (id, displayName, rootURL,
rootGroup, targets, configurations, schemes) with a short description; add a
triple-slash doc for the public initializer ProjectModule.init(...) describing
purpose and each parameter using - Parameter name: for id, displayName, rootURL,
rootGroup, targets, configurations, schemes; and update the target(for:) doc to
include - Parameter id: description and - Returns: description explaining it
returns the matching TargetSummary or nil. Ensure comments use /// and include
concise sentences for discoverability.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectNodeKind.swift`:
- Around line 13-29: Add Swift-DocC triple-slash documentation for the public
computed properties on ProjectNodeKind: document the `id` property to state it
returns the underlying ProjectNodeID for the enum case (delegating to group.id
or file.id) and document the `displayName` property to state it returns the
user-facing name for the node (delegating to group.displayName or
file.displayName); place concise /// comments immediately above the `public var
id: ProjectNodeID` and `public var displayName: String` declarations, mention
they are stable/public and which enum cases they map to (group, file) and any
guarantees (e.g., non-empty or localized if applicable).
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/SchemeSummary.swift`:
- Around line 10-40: Add Swift-DocC triple-slash documentation for every public
symbol in this type: document each public stored property (id, name, isShared,
runTargetIDs, testTargetIDs, profileTargetID, archiveTargetID, launchArguments,
environmentVariables) with a brief one-line description of what it represents,
and add a /// doc comment for the public initializer (init(...)) describing the
initializer and each parameter (id, name, isShared, runTargetIDs, testTargetIDs,
profileTargetID, archiveTargetID, launchArguments, environmentVariables). Ensure
the comments use Swift-DocC style (///) immediately above each property and the
init, are concise, and mention optional semantics for optional properties
(profileTargetID, archiveTargetID) where applicable.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/TargetMembership.swift`:
- Around line 11-23: Add Swift-DocC triple-slash comments for each public symbol
in TargetMembership.swift: document the public stored properties targetID, role,
and compilerFlags with one-line descriptions (include that compilerFlags has a
default empty array), and document the public initializer
init(targetID:role:compilerFlags:) describing the parameters and behavior; place
the /// comments immediately above each property and the initializer and use
parameter tags or brief descriptions for each parameter to satisfy the package
documentation guideline.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/TargetSummary.swift`:
- Around line 10-34: Add Swift-DocC triple-slash comments to each public stored
property (id, displayName, productType, platforms, bundleIdentifier,
deploymentTarget, dependencies) and to the public initializer in TargetSummary
to satisfy the package documentation guideline; for each property write a
concise one-line description of its purpose/type (e.g., "/// The unique
identifier for the target.") and add a short doc-line for the init describing
parameters (using /// - Parameters: and list each parameter name and purpose) so
every public symbol in TargetSummary is documented.
In `@Packages/CMUXProjectModel/Sources/CMUXProjectModel/XcconfigParser.swift`:
- Around line 21-37: Add DocC callouts to the public APIs: update the doc
comments for parse(at:) and parseChain(_:) to use /// style and include the
required "- Parameter", "- Returns", and "- Throws" callouts where appropriate —
for parse(at: URL) document the "url" parameter, the returned [String: String]
map under "- Returns:", and the error behavior under "- Throws:"; for
parseChain(_ urls: [URL]) document the "urls" parameter and the returned merged
[String: String] under "- Returns:". Ensure the comment blocks precede the
public static func declarations for parse(at:) and parseChain(_:) and follow the
project's DocC formatting guidelines.
- Around line 39-62: The current cycle guard in parseRecursive(at:visited:)
inserts canonical into visited and never removes it, which prevents legitimate
repeated includes via different paths; make visited path-scoped by removing the
canonical URL when leaving the function (use defer { visited.remove(canonical) }
immediately after inserting canonical) so ancestors remain marked during
recursion but siblings/other branches can re-include the file; keep the existing
contains check and behavior otherwise.
In
`@Packages/CMUXProjectModel/Sources/CMUXProjectModel/XcodeProjectAdapter.swift`:
- Around line 31-47: The public API surface is undocumented: add Swift-DocC
comments for the public symbols `kind`, `init()`, `canLoad(_:)`, and `load(at:)`
in XcodeProjectAdapter so they include a short summary and the appropriate tags
(`- Parameter:` for parameters, `- Returns:` for return values, and `- Throws:`
for throwable methods); update the doc comments for `canLoad(_:)` to describe
the `url` parameter and boolean result, and for `load(at:)` to describe the
`url`, the returned `ProjectModel`, and the `ProjectLoadError` thrown, ensuring
each public symbol has a /// comment immediately above its declaration.
- Around line 23-25: Update the stale documentation to reflect current behavior:
change the comment that says build settings, schemes, and `.xcconfig` resolution
are "deliberately out of scope" to indicate that loadModule now collects these
via collectBuildConfigurations, collectSchemes, and mergedXcconfigSettings and
that ProjectModel will be populated with those artifacts; refer to the
loadModule, collectBuildConfigurations, collectSchemes, and
mergedXcconfigSettings symbols when editing the doc comment so it accurately
describes the adapter's responsibilities.
- Around line 202-204: The current construction of targetNameToID using
Dictionary(uniqueKeysWithValues:) will crash if two PBXTarget instances in
targets share the same name; change it to build the dictionary with
Dictionary(uniqueKeysWithValues: , uniquingKeysWith:) (or use
Dictionary(grouping:) and then mapValues) so duplicate names are resolved
deterministically (e.g., keep the first or last) instead of trapping; update the
code that uses targetNameToID accordingly and ensure the mapping still yields
TargetID(rawValue: $0.uuid) for each entry.
- Around line 180-193: The normalizeRawSettings helper and the resolveSetting
call must handle XcodeProj's BuildSetting enum instead of only String/[String];
update normalizeRawSettings(_ source: [String: Any]) to detect and unwrap
BuildSetting cases (e.g. .string and .array) as well as existing String/[String]
and fallback to String(describing:) only for unknowns, and update any code that
passes config.buildSettings into resolveSetting to accept BuildSetting values
(or add a conversion path) so values like PRODUCT_BUNDLE_IDENTIFIER, deployment
targets, SUPPORTED_PLATFORMS and SDKROOT are extracted from BuildSetting.string
/ BuildSetting.array rather than serialized via String(describing:
BuildSetting); target symbols: normalizeRawSettings, resolveSetting,
rawSettings, and config.buildSettings/BuildSetting.
In
`@Packages/CMUXProjectModel/Tests/CMUXProjectModelTests/XcconfigParserTests.swift`:
- Around line 8-84: Each test that creates temp files/dirs
(parsesSimpleAssignments, stripsTrailingLineComments, followsRelativeIncludes,
optionalIncludeIsIgnoredWhenMissing, parseChainMergesInDeclaredOrder,
cycleDetectionAvoidsInfiniteRecursion) must register a defer cleanup immediately
after creating the resource (e.g., after let url = try writeTempXcconfig(...) or
after creating dir/baseURL/derivedURL/a/b) so removal
(FileManager.default.removeItem(at: ...)) always runs even on throws/failures;
update each test to remove the try? cleanup call at the end and instead call
try? FileManager.default.removeItem(...) inside a defer placed right after the
resource is created.
In
`@Packages/CMUXProjectModel/Tests/CMUXProjectModelTests/XcodeProjectAdapterTests.swift`:
- Around line 10-27: The init() in XcodeProjectAdapterTests incorrectly appends
sibling bundle names inside a provided bundle path; when CMUX_PROJECT_FIXTURE
points to an .xcworkspace or .xcodeproj you must compute the sibling by using
the bundle's parent directory. Update the override branch to: read override into
base (URL), test base.pathExtension, and when one bundle is provided build the
other by calling
base.deletingLastPathComponent().appendingPathComponent("cmux.xcodeproj") or
.appendingPathComponent("cmux.xcworkspace") as needed so workspaceURL and
projectURL are siblings rather than children.
In `@Sources/ContentView.swift`:
- Around line 5825-5826: The code references the localized key
"commandPalette.kind.project" via String(localized:
"commandPalette.kind.project", defaultValue: "Project") (in
Sources/ContentView.swift, case .project) but the key is missing from the string
catalogs; add "commandPalette.kind.project" with the base English value
"Project" to every Localizable.xcstrings catalog used by the app (e.g.
Resources/Localizable.xcstrings and any module-specific Localizable.xcstrings),
and include equivalent translated values for all supported locales so the lookup
succeeds at runtime and falls back to the default only if a locale-specific
entry is missing.
In `@Sources/Panels/ProjectBuildSettingsTabView.swift`:
- Around line 27-50: Replace all hardcoded UI strings in controlsRow (e.g.,
Text("Target"), TextField placeholder "Filter settings", Toggle("Customized
only"), and the count Text("\(rowCount) settings")) with localized
string-catalog keys and use pluralized .stringsdict entries for the count.
Concretely: swap Text("Target") ->
Text(LocalizedStringKey("BuildSettings.Target")), targetPicker/ TextField
placeholder -> LocalizedStringKey("BuildSettings.FilterSettings"), Toggle label
-> LocalizedStringKey("BuildSettings.CustomizedOnly"), and change the count to
use the .stringsdict plural key (e.g. use
String.localizedStringWithFormat(NSLocalizedString("BuildSettings.SettingsCount",
comment: ""), rowCount) or
Text(LocalizedStringKey("BuildSettings.SettingsCount"), table: nil) with a
.stringsdict that defines .one/.other forms). Also update the other header
labels referenced in this view (the same keys used for "Setting", "Effective",
"Target", "Project") to their corresponding localized keys and add matching
.one/.other entries for the count in the string catalog.
In `@Sources/Panels/ProjectFilesTabView.swift`:
- Around line 101-119: The current implementation calls
Self.collectMatchingFiles(in:filter:) for each group inside the traversal,
causing repeated recursive scans; instead modify the traversal (the walk
function that emits FlattenedRow entries) to perform a single DFS per filter
pass that both detects matches and emits rows without calling
collectMatchingFiles for subtrees. Concretely: change the group-handling path to
recurse into children once, have the recursive routine return a Boolean (or
count) indicating whether any descendant matched the filter, and only when true
append the group row (using Self.presentationGroup) and the matching file rows
inline (creating FlattenedRow entries as you encounter matching files). Remove
the per-group collectMatchingFiles calls so each node is visited exactly once
per filter operation.
- Around line 42-66: The UI strings in ProjectFilesTabView are hardcoded
English; update filterBar and the file-list/detail labels to use localized keys
(e.g., replace "Filter files (e.g. AppDelegate)" in the TextField in filterBar,
and the labels "Select a file", "Path", "Type", "On disk", "Targets", "Yes",
"Missing", and "flags:" wherever they're rendered) by using SwiftUI-localized
APIs (LocalizedStringKey/Text or NSLocalizedString) with clear keys like
"Files.FilterPlaceholder", "Files.SelectFile", "Files.Path", "Files.Type",
"Files.OnDisk", "Files.Targets", "Files.Yes", "Files.Missing", "Files.Flags" and
add matching entries to the .strings catalogs; ensure you bind the placeholder
as a LocalizedStringKey for TextField and replace any hardcoded Text(...) calls
with Text("Your.Key") so translations are picked up.
In `@Sources/Panels/ProjectPanel.swift`:
- Around line 212-223: The describe(_:) helper currently returns raw parser
reasons and absolute paths from ProjectLoadError; change it to map each
ProjectLoadError case (.unreadable, .unsupported, .parseFailure) to a
user-facing, localized high-level message (use NSLocalizedString or the app's
localization helper) that does not include url.path or the raw reason, and
instead uses neutral text like "The project could not be read", "This project
format is not supported", and "The project could not be parsed"; keep the
original error/URL and reason only in debug logging/telemetry (e.g., log inside
describe or where the error is caught) so diagnostics are preserved but not
shown in the UI, and update any callers of describe(_:) in ProjectPanel to
expect the localized strings.
- Around line 67-68: selectedSchemeName (and similarly
selectedConfigurationName) uses only the display name which can collide across
modules/scopes; change the panel state to store a stable identifier (e.g.
SchemeID or a composite of scheme UUID + scope/module) instead of the plain name
and derive the human-readable name from the loaded model when rendering. Update
ProjectPanel's published properties to hold SchemeID (and ConfigurationID) and
replace usages that compare or restore by name with comparisons/lookup by the
stable ID (including the code paths referenced around lines 123–130) so
reload/reconciliation picks the exact scheme; keep a computed/display property
that maps the stored ID to the scheme's name for UI.
- Around line 14-20: The hardcoded English tab labels in the displayLabel
computed property should be localized: replace the literal returns for .files,
.targets, .buildSettings, and .schemes with String(localized:_, defaultValue:_ )
calls (e.g., String(localized: "Files", defaultValue: "Files")) using the same
keys for each case, and add corresponding entries to the app's string catalog /
Localizable strings so translations exist for those keys; update the
displayLabel implementation to return these localized strings for the enum
cases.
In `@Sources/Panels/ProjectPanelView.swift`:
- Around line 33-46: Replace all hardcoded user-facing literals in
ProjectPanelView (e.g., the Label title via panel.displayTitle, the Button help
"Reload project", the "Reload returned errors" status, picker labels used by
schemePicker and configurationPicker, loading/failure text, and both empty-state
messages referenced in the ranges called out) with localized calls using
String(localized: , defaultValue: ). Update the Label, Button.help, any
Text(...) or .help(...) usages, and picker label initializers inside the
ProjectPanelView struct and related view helpers to call String(localized:
"KEY", defaultValue: "English text") with descriptive localization keys; keep
the same semantics and pass the localized strings into schemePicker and
configurationPicker where labels are created, and ensure any status text
displayed around panel.reload() uses localized variants as well.
In `@Sources/Panels/ProjectSchemesTabView.swift`:
- Around line 52-69: The hardcoded English UI strings in ProjectSchemesTabView
(e.g., Text(scheme.isShared ? "shared" : "personal"), Text("run: ..."),
Text("test: ..."), and labels like "Visibility", "Launch arguments",
"Environment") must be replaced with localized string keys and lookup APIs;
update occurrences that reference runTargetIDs, testTargetIDs, and the
targetNames(for:in:) call to use localized keys (for example replace literal
"shared"/"personal" with localized keys like
"schemes.visibility.shared"/"schemes.visibility.personal" and use
Text(LocalizedStringKey("schemes.visibility.shared")) or String(localized:
"schemes.run_count", count: ...) as appropriate), add matching entries to the
app’s .strings/.stringsdict catalog for each key and pluralized forms, and
ensure all other hardcoded labels in the same file (lines ~93-149) follow the
same pattern.
- Around line 25-33: entries currently flattens every module's schemes and keys
rows by compositeID so the same logical scheme can appear multiple times; change
construction of entries to first group schemes by a stable scheme identity
(e.g., scheme.name or scheme.id on SchemeSummary) into a dictionary mapping that
identity to a single representative SchemeSummary plus an array of associated
ProjectModule(s), then build a uniqueEntries array of tuples like (scheme:
SchemeSummary, modules: [ProjectModule], id: String) and iterate
ForEach(uniqueEntries, id: \.id) { entry in ... } while keeping
schemeRow(entry.scheme, module: entry.modules.first) (and surface the full
modules array in the detail view) so the sidebar shows one row per scheme but
still retains module attribution for details.
In `@Sources/Panels/ProjectTargetsTabView.swift`:
- Around line 67-77: Replace all hardcoded user-facing strings in this view with
localized keys: change calls to metadata("min", ...), metadata("platforms",
...), metadata("bundle", ...), the Text("deps: \(...)") and other literal Text
labels ("Select a target", "Dependencies", "Build", "Open in Build Settings",
"\(configCount) configurations", "\(totalKeys) target overrides") to use
localization APIs; for simple labels use LocalizedStringKey or NSLocalizedString
with descriptive keys (e.g. "Targets.min", "Targets.platforms",
"Targets.bundle", "Targets.select") and for plural/counted strings add
.one/.other entries in a .stringsdict and format counts via
String.localizedStringWithFormat or use LocalizedStringKey with plural support
so the Text views and metadata(_:,_:) calls reference the localized strings and
the counts use the stringsdict keys.
In `@Sources/TerminalController.swift`:
- Around line 12369-12386: The v2ProjectSetSelectedTarget function currently
only checks the first module (panel.loadState.model?.modules.first) so targets
in other modules are missed; modify the lookup inside the v2MainSync block to
iterate/search across all modules (e.g., gather modules.flatMap { $0.targets }
or otherwise traverse panel.loadState.model?.modules) and find the first target
whose displayName matches the provided name, then set panel.selectedTargetID =
target.id and resolvedID = target.id.rawValue (or set selectedTargetID = nil if
no match) while preserving existing name handling and thread-safety in
v2MainSync.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: aac1bcff-4fe6-4a4d-b5c6-2517015b2362
📒 Files selected for processing (48)
.gitignore.iter-logs/CHANGELOG.mdCLI/cmux.swiftPackages/CMUXProjectModel/Package.resolvedPackages/CMUXProjectModel/Package.swiftPackages/CMUXProjectModel/Sources/CMUXProjectDump/main.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/BuildConfigScope.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/BuildConfigSummary.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/IdentifierTypes.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectAdapter.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectAdapterKind.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectFileNode.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectGroup.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectGroupStyle.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectLoadError.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectModel.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectModule.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectNodeKind.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/SchemeSummary.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/TargetMembership.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/TargetMembershipRole.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/TargetProductType.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/TargetSummary.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/XcconfigParser.swiftPackages/CMUXProjectModel/Sources/CMUXProjectModel/XcodeProjectAdapter.swiftPackages/CMUXProjectModel/Tests/CMUXProjectModelTests/XcconfigParserTests.swiftPackages/CMUXProjectModel/Tests/CMUXProjectModelTests/XcodeProjectAdapterTests.swiftSources/ClosedItemHistory.swiftSources/CmuxConfig.swiftSources/CmuxLifecycleEventPublishing.swiftSources/ContentView.swiftSources/Panels/FilePreviewPanel.swiftSources/Panels/FilePreviewWorkspaceOpenSupport.swiftSources/Panels/Panel.swiftSources/Panels/PanelContentView.swiftSources/Panels/ProjectBuildSettingsTabView.swiftSources/Panels/ProjectFilesTabView.swiftSources/Panels/ProjectPanel.swiftSources/Panels/ProjectPanelView.swiftSources/Panels/ProjectSchemesTabView.swiftSources/Panels/ProjectTargetsTabView.swiftSources/Search/GlobalSearchDocuments.swiftSources/SessionPersistence.swiftSources/TerminalController.swiftSources/TerminalPaneDropTargetView.swiftSources/Workspace.swiftcmux.xcodeproj/project.pbxprojcmux.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
| let subArgs: [String] | ||
| if first == "open" { | ||
| subArgs = Array(args.dropFirst()) | ||
| } else if args.count == 1 { | ||
| subArgs = args | ||
| } else { | ||
| throw CLIError(message: "Unknown project subcommand: \(first). Usage: cmux project open <path>") | ||
| } | ||
|
|
||
| guard let rawPath = subArgs.first, !rawPath.isEmpty else { | ||
| throw CLIError(message: "project open requires a path. Usage: cmux project open <path-to-.xcodeproj-or-.xcworkspace>") | ||
| } |
There was a problem hiding this comment.
Lone non-open token is silently treated as a path, masking mistyped subcommands.
The else if args.count == 1 branch accepts any single token as the path, so cmux project status (or any typo'd one-word subcommand) becomes a bogus path and surfaces a confusing "file not found"-style error from project.open instead of a clear usage error. Multi-token typos are already caught by the final else. The looksLikePath(_:) helper right below (Line 4774) can gate this cleanly.
🛠️ Proposed fix to reject non-path single tokens
let subArgs: [String]
if first == "open" {
subArgs = Array(args.dropFirst())
- } else if args.count == 1 {
+ } else if args.count == 1, looksLikePath(rawFirst) {
subArgs = args
} else {
throw CLIError(message: "Unknown project subcommand: \(first). Usage: cmux project open <path>")
}This requires capturing the original-cased first token (e.g. let rawFirst = args[0]) since looksLikePath should run against the un-lowercased value.
🤖 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 `@CLI/cmux.swift` around lines 4734 - 4745, The single-token branch currently
treats any one-word token as a path and should instead validate it using the
looksLikePath(_:) helper: capture the original-cased first token (e.g. rawFirst
= args[0]) before any lowercasing, then when args.count == 1 call
looksLikePath(rawFirst) and only accept it as subArgs if it returns true;
otherwise throw the existing CLIError usage message for unknown project
subcommands (keep the existing throw path for multi-token cases), and ensure
subArgs is set from the validated rawFirst so the later guard uses the correct
rawPath.
| public let id: BuildConfigID | ||
| public let name: String | ||
| public let scope: BuildConfigScope | ||
| public let baseConfigurationPath: URL? | ||
| public let rawSettings: [String: String] | ||
|
|
||
| public init( | ||
| id: BuildConfigID, | ||
| name: String, | ||
| scope: BuildConfigScope, | ||
| baseConfigurationPath: URL?, | ||
| rawSettings: [String: String] | ||
| ) { | ||
| self.id = id | ||
| self.name = name | ||
| self.scope = scope | ||
| self.baseConfigurationPath = baseConfigurationPath | ||
| self.rawSettings = rawSettings | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Document the public properties and initializer.
The public stored properties (Lines 12–16) and the memberwise initializer (Lines 18–24) lack the required Swift-DocC /// comments.
As per coding guidelines: "Every public symbol in any new Swift package under Packages/ must be documented with a Swift-DocC triple-slash comment at the time of writing."
🤖 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/CMUXProjectModel/Sources/CMUXProjectModel/BuildConfigSummary.swift`
around lines 12 - 30, Add Swift-DocC triple-slash comments for each public
stored property (id, name, scope, baseConfigurationPath, rawSettings) and the
public initializer in BuildConfigSummary so every public symbol is documented;
for the initializer (init(id:name:scope:baseConfigurationPath:rawSettings:))
include a brief overview plus /// - Parameters: entries describing each
parameter and their meanings, and for optional baseConfigurationPath note it can
be nil, keeping all comments concise and formatted with /// so they appear in
generated docs.
| public let id: ProjectNodeID | ||
| public let displayName: String | ||
| public let resolvedPath: URL? | ||
| public let fileType: String? | ||
| public let existsOnDisk: Bool | ||
| public let memberships: [TargetMembership] | ||
|
|
||
| public init( | ||
| id: ProjectNodeID, | ||
| displayName: String, | ||
| resolvedPath: URL?, | ||
| fileType: String?, | ||
| existsOnDisk: Bool, | ||
| memberships: [TargetMembership] | ||
| ) { | ||
| self.id = id | ||
| self.displayName = displayName | ||
| self.resolvedPath = resolvedPath | ||
| self.fileType = fileType | ||
| self.existsOnDisk = existsOnDisk | ||
| self.memberships = memberships | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Document the public properties and initializer.
The public stored properties (Lines 11–16) and the memberwise initializer (Lines 18–24) lack the required Swift-DocC /// comments.
As per coding guidelines: "Every public symbol in any new Swift package under Packages/ must be documented with a Swift-DocC triple-slash comment at the time of writing."
🤖 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/CMUXProjectModel/Sources/CMUXProjectModel/ProjectFileNode.swift`
around lines 11 - 32, Add Swift-DocC triple-slash comments for every public
symbol in ProjectFileNode: document each stored property (id, displayName,
resolvedPath, fileType, existsOnDisk, memberships) with a brief description of
its purpose and any important behavior, and document the public initializer
(init(id:displayName:resolvedPath:fileType:existsOnDisk:memberships:)) including
parameter descriptions; ensure each comment uses /// and follows the project's
DocC style so all public symbols in ProjectFileNode are documented.
| public let id: ProjectNodeID | ||
| public let displayName: String | ||
| public let resolvedPath: URL? | ||
| public let style: ProjectGroupStyle | ||
| public let children: [ProjectNodeKind] | ||
|
|
||
| public init( | ||
| id: ProjectNodeID, | ||
| displayName: String, | ||
| resolvedPath: URL?, | ||
| style: ProjectGroupStyle, | ||
| children: [ProjectNodeKind] | ||
| ) { | ||
| self.id = id | ||
| self.displayName = displayName | ||
| self.resolvedPath = resolvedPath | ||
| self.style = style | ||
| self.children = children | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Document the public properties and initializer.
The public stored properties (Lines 12–16) and the memberwise initializer (Lines 18–24) lack the required Swift-DocC /// comments.
As per coding guidelines: "Every public symbol in any new Swift package under Packages/ must be documented with a Swift-DocC triple-slash comment at the time of writing."
🤖 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/CMUXProjectModel/Sources/CMUXProjectModel/ProjectGroup.swift` around
lines 12 - 30, Add Swift-DocC triple-slash documentation for all public stored
properties and the initializer in ProjectGroup: document the purpose of each
property (id: ProjectNodeID, displayName: String, resolvedPath: URL?, style:
ProjectGroupStyle, children: [ProjectNodeKind]) with a short description and for
the initializer add a summary plus /// - Parameters: entries describing each
parameter (id, displayName, resolvedPath, style, children). Place the ///
comments immediately above the property declarations and above the public init
to satisfy the package documentation guideline.
| /// The artifact exists and is the right kind, but parsing failed. | ||
| /// | ||
| /// The underlying reason is rendered as a string because adapter | ||
| /// implementations wrap third-party errors (XcodeProj, libxml2, etc.) | ||
| /// whose types are not part of this package's public API surface. | ||
| case parseFailure(URL, reason: String) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find where parseFailure is constructed (reason source) and where it's rendered to users.
rg -nP -C3 '\bparseFailure\s*\(' --type=swift
rg -nP -C3 '\bProjectLoadError\b' --type=swift -g '!**/Tests/**'Repository: manaflow-ai/cmux
Length of output: 10325
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect UI rendering of ProjectLoadError, especially parseFailure(reason:)
sed -n '180,260p' Sources/Panels/ProjectPanel.swift
# Inspect ProjectLoadError definition around parseFailure
sed -n '1,120p' Packages/CMUXProjectModel/Sources/CMUXProjectModel/ProjectLoadError.swift
# Inspect adapter construction of parseFailure(reason:) more narrowly
sed -n '1,140p' Packages/CMUXProjectModel/Sources/CMUXProjectModel/XcodeProjectAdapter.swiftRepository: manaflow-ai/cmux
Length of output: 8913
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find where ProjectPanel.describe(_:) is called / displayed
rg -n "ProjectPanel\.describe\(_" -S Sources/Panels/ProjectPanel.swift Sources -g'*.swift'
rg -n "describe\(_ error: Error\)" Sources/Panels/ProjectPanel.swift
rg -n "Parse failed at" -S Sources/Panels/ProjectPanel.swift Sources -g'*.swift'
# Trace ProjectLoadError handling in UI
rg -n "ProjectLoadError\." -S Sources -g'*.swift'
rg -n "parseFailure\(" -S Sources Packages -g'*.swift'Repository: manaflow-ai/cmux
Length of output: 42
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate how ProjectPanel.describe(_:) is used (so we know if it's user-facing)
rg -n "private static func describe\(_ error: Error\)" Sources/Panels/ProjectPanel.swift
rg -n "describe\(_ error: Error\)" Sources -g'*.swift'
rg -n "Parse failed at" Sources -g'*.swift'
rg -n "Cannot read" Sources -g'*.swift'
rg -n "Unsupported project at" Sources -g'*.swift'
# Find likely call sites that present panel error strings
rg -n "triggerFlash|WorkspaceAttentionFlashReason|alert|NSAlert|error.*reason|show.*error|display.*error" Sources -g'*.swift'Repository: manaflow-ai/cmux
Length of output: 50372
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Narrowly find where the ProjectPanel error string (including parseFailure reason) is used/displayed.
rg -n "Parse failed at" -S Sources -g'*.swift' --max-count 50
rg -n "Cannot read \\(url\\.path\\)|Unsupported project at \\(url\\.path\\)" -S Sources -g'*.swift' --max-count 50
# Look for the private static describe(_:) being invoked indirectly.
rg -n "describe\\(.*\\)\\s*\\)" -S Sources/Panels/ProjectPanel.swift -g'*.swift' --max-count 50
rg -n "ProjectPanel\\.describe" -S Sources -g'*.swift' --max-count 50
# Trace ProjectPanel error plumbing (load failure -> string -> UI).
rg -n "ProjectLoadError|load\\(at:.*\\)|catch.*ProjectLoadError|catch.*ProjectLoadError" -S Sources -g'*.swift' --max-count 80Repository: manaflow-ai/cmux
Length of output: 162
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the surrounding code where describe(_:) is defined and any nearby error UI usage.
sed -n '160,320p' Sources/Panels/ProjectPanel.swift
# Find any call sites within ProjectPanel.swift for the describe(_:) helper.
rg -n "describe\(_ error: Error\)|ProjectPanel\.describe|describe\(" Sources/Panels/ProjectPanel.swift -SRepository: manaflow-ai/cmux
Length of output: 3389
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Trace where lastLoadError is set and how it is rendered/displayed.
rg -n "lastLoadError" Sources/Panels/ProjectPanel.swift
# Inspect the render/UI portion near the load error usage (small windows around call sites).
sed -n '60,140p' Sources/Panels/ProjectPanel.swift
sed -n '140,240p' Sources/Panels/ProjectPanel.swift
sed -n '240,360p' Sources/Panels/ProjectPanel.swift
sed -n '360,520p' Sources/Panels/ProjectPanel.swiftRepository: manaflow-ai/cmux
Length of output: 7463
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the load state enum and where the .failed(lastLoadError) string is rendered.
rg -n "ProjectPanelLoadState" Sources/Panels/ProjectPanel.swift
rg -n "case \.failed" Sources/Panels/ProjectPanel.swift Sources -g'*.swift'
# Inspect surrounding code in ProjectPanel.swift where loadState is consumed (UI/state mapping).
sed -n '1,160p' Sources/Panels/ProjectPanel.swift
sed -n '520,760p' Sources/Panels/ProjectPanel.swiftRepository: manaflow-ai/cmux
Length of output: 7010
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "lastLoadError" Sources -g'*.swift'
rg -n "ProjectPanelLoadState|case \\.failed\\(let" Sources -g'*.swift' --max-count 80
# Focus on Project-related SwiftUI views/panels
rg -n "ProjectPanel.*loadState|loadState.*ProjectPanel|switch .*loadState" Sources/Panels -g'*.swift' --max-count 80Repository: manaflow-ai/cmux
Length of output: 1685
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect UI rendering of project load errors.
sed -n '1,120p' Sources/Panels/ProjectPanelView.swift
sed -n '120,260p' Sources/Panels/ProjectPanelView.swiftRepository: manaflow-ai/cmux
Length of output: 8045
Sanitize ProjectLoadError.parseFailure’s reason before showing it in the Project UI.
XcodeProjectAdapter constructs parseFailure(..., reason: String(describing: error)), and ProjectPanel.describe(_:) interpolates that reason into the returned string ("Parse failed at \(url.path): \(reason)"). ProjectPanelView then renders that string directly in both "Reload returned errors: \(error)" and "Failed: \(reason)", so raw third-party parse details can leak to users.
Update the adapter and/or ProjectPanel.describe(_:) to redact/replace reason with a user-safe, generic message, and keep the raw string only for internal logging if needed.
🤖 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/CMUXProjectModel/Sources/CMUXProjectModel/ProjectLoadError.swift`
around lines 14 - 19, ProjectLoadError.parseFailure currently carries raw
third-party error text which gets shown in the UI; update the surface so
user-visible text is redacted while preserving raw details for logs.
Specifically, either change XcodeProjectAdapter so it no longer constructs
parseFailure(..., reason: String(describing: error)) for UI consumption but
instead passes a generic message like "Project parsing failed" and sends
String(describing: error) to an internal logger, or modify
ProjectPanel.describe(_:) to map ProjectLoadError.parseFailure(_, _) to a safe,
generic message (e.g. "Parse failed: internal parsing error") and write the
original reason to debug logs; ensure references: ProjectLoadError.parseFailure,
XcodeProjectAdapter, ProjectPanel.describe(_:), and ProjectPanelView are updated
accordingly.
| Label(panel.displayTitle, systemImage: "hammer.fill") | ||
| .font(.system(size: 13, weight: .semibold)) | ||
| .help(panel.projectURL.path) | ||
| schemePicker | ||
| configurationPicker | ||
| Spacer(minLength: 0) | ||
| Button { | ||
| panel.reload() | ||
| } label: { | ||
| Image(systemName: "arrow.clockwise") | ||
| .font(.system(size: 11, weight: .semibold)) | ||
| } | ||
| .buttonStyle(.plain) | ||
| .help("Reload project") |
There was a problem hiding this comment.
Localize the new panel chrome and status copy.
This view hardcodes the new visible strings in English (Reload project, Reload returned errors, picker labels, loading/failure text, and both empty states). These should all come from String(localized:defaultValue:) keys instead of raw literals.
As per coding guidelines, production user-facing Swift text must use localized APIs with matching translated string-catalog entries for every supported locale.
Also applies to: 48-70, 102-176, 216-241
🤖 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 `@Sources/Panels/ProjectPanelView.swift` around lines 33 - 46, Replace all
hardcoded user-facing literals in ProjectPanelView (e.g., the Label title via
panel.displayTitle, the Button help "Reload project", the "Reload returned
errors" status, picker labels used by schemePicker and configurationPicker,
loading/failure text, and both empty-state messages referenced in the ranges
called out) with localized calls using String(localized: , defaultValue: ).
Update the Label, Button.help, any Text(...) or .help(...) usages, and picker
label initializers inside the ProjectPanelView struct and related view helpers
to call String(localized: "KEY", defaultValue: "English text") with descriptive
localization keys; keep the same semantics and pass the localized strings into
schemePicker and configurationPicker where labels are created, and ensure any
status text displayed around panel.reload() uses localized variants as well.
| let entries: [(module: ProjectModule, scheme: SchemeSummary, compositeID: String)] = model.modules.flatMap { module in | ||
| module.schemes.map { scheme in | ||
| (module: module, scheme: scheme, compositeID: "\(module.id.rawValue)|\(scheme.name)") | ||
| } | ||
| } | ||
| ScrollView { | ||
| VStack(alignment: .leading, spacing: 0) { | ||
| ForEach(entries, id: \.compositeID) { entry in | ||
| schemeRow(entry.scheme, module: entry.module) |
There was a problem hiding this comment.
Deduplicate schemes before rendering the sidebar.
entries flattens every module’s schemes and keys each row by module.id|scheme.name, so the same scheme can appear multiple times across a workspace. That conflicts with the intended shared/per-user dedup behavior and makes the sidebar diverge from the single selected-scheme model. Merge the rows by stable scheme identity first, then carry module attribution separately in the detail view.
🤖 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 `@Sources/Panels/ProjectSchemesTabView.swift` around lines 25 - 33, entries
currently flattens every module's schemes and keys rows by compositeID so the
same logical scheme can appear multiple times; change construction of entries to
first group schemes by a stable scheme identity (e.g., scheme.name or scheme.id
on SchemeSummary) into a dictionary mapping that identity to a single
representative SchemeSummary plus an array of associated ProjectModule(s), then
build a uniqueEntries array of tuples like (scheme: SchemeSummary, modules:
[ProjectModule], id: String) and iterate ForEach(uniqueEntries, id: \.id) {
entry in ... } while keeping schemeRow(entry.scheme, module:
entry.modules.first) (and surface the full modules array in the detail view) so
the sidebar shows one row per scheme but still retains module attribution for
details.
| Text(scheme.isShared ? "shared" : "personal") | ||
| .font(.system(size: 9, weight: .medium)) | ||
| .padding(.horizontal, 4) | ||
| .padding(.vertical, 1) | ||
| .background( | ||
| RoundedRectangle(cornerRadius: 3) | ||
| .fill((scheme.isShared ? Color.accentColor : Color.orange).opacity(0.18)) | ||
| ) | ||
| .foregroundStyle(scheme.isShared ? Color.accentColor : Color.orange) | ||
| } | ||
| HStack(spacing: 8) { | ||
| if !scheme.runTargetIDs.isEmpty { | ||
| Text("run: \(targetNames(for: scheme.runTargetIDs, in: module))") | ||
| .font(.system(size: 10)) | ||
| .foregroundStyle(.secondary) | ||
| } | ||
| if !scheme.testTargetIDs.isEmpty { | ||
| Text("test: \(scheme.testTargetIDs.count)") |
There was a problem hiding this comment.
Localize the Schemes tab copy.
The new badges, labels, and empty-state text (shared, personal, run, test, Visibility, Launch arguments, Environment, etc.) are hardcoded English strings. These should all come from localized string-catalog keys.
As per coding guidelines, production user-facing Swift text must use localized APIs with matching translated string-catalog entries for every supported locale.
Also applies to: 93-149
🤖 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 `@Sources/Panels/ProjectSchemesTabView.swift` around lines 52 - 69, The
hardcoded English UI strings in ProjectSchemesTabView (e.g.,
Text(scheme.isShared ? "shared" : "personal"), Text("run: ..."), Text("test:
..."), and labels like "Visibility", "Launch arguments", "Environment") must be
replaced with localized string keys and lookup APIs; update occurrences that
reference runTargetIDs, testTargetIDs, and the targetNames(for:in:) call to use
localized keys (for example replace literal "shared"/"personal" with localized
keys like "schemes.visibility.shared"/"schemes.visibility.personal" and use
Text(LocalizedStringKey("schemes.visibility.shared")) or String(localized:
"schemes.run_count", count: ...) as appropriate), add matching entries to the
app’s .strings/.stringsdict catalog for each key and pluralized forms, and
ensure all other hardcoded labels in the same file (lines ~93-149) follow the
same pattern.
| HStack(spacing: 8) { | ||
| if let deploy = target.deploymentTarget { | ||
| metadata("min", deploy) | ||
| } | ||
| if !target.platforms.isEmpty { | ||
| metadata("platforms", target.platforms.joined(separator: ",")) | ||
| } | ||
| if let bundle = target.bundleIdentifier { | ||
| metadata("bundle", bundle) | ||
| } | ||
| Text("deps: \(target.dependencies.count)") |
There was a problem hiding this comment.
Localize the Targets tab labels and pluralized counts.
Strings like min, platforms, bundle, deps, Select a target, Dependencies, Build, Open in Build Settings, and the \(configCount) configurations / \(totalKeys) target overrides labels are hardcoded. These should move to localized keys, with .one / .other variants for the count-based copy.
Based on learnings, pluralized Swift strings in this repo should use explicit .one / .other localization keys. As per coding guidelines, production user-facing Swift text must use localized APIs with matching translated string-catalog entries for every supported locale.
Also applies to: 125-129, 146-160, 190-205
🤖 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 `@Sources/Panels/ProjectTargetsTabView.swift` around lines 67 - 77, Replace all
hardcoded user-facing strings in this view with localized keys: change calls to
metadata("min", ...), metadata("platforms", ...), metadata("bundle", ...), the
Text("deps: \(...)") and other literal Text labels ("Select a target",
"Dependencies", "Build", "Open in Build Settings", "\(configCount)
configurations", "\(totalKeys) target overrides") to use localization APIs; for
simple labels use LocalizedStringKey or NSLocalizedString with descriptive keys
(e.g. "Targets.min", "Targets.platforms", "Targets.bundle", "Targets.select")
and for plural/counted strings add .one/.other entries in a .stringsdict and
format counts via String.localizedStringWithFormat or use LocalizedStringKey
with plural support so the Text views and metadata(_:,_:) calls reference the
localized strings and the counts use the stringsdict keys.
| private func v2ProjectSetSelectedTarget(params: [String: Any]) -> V2CallResult { | ||
| guard let (_, panel) = v2ResolveProjectPanel(params: params) else { | ||
| return .err(code: "not_found", message: "Project surface not found", data: nil) | ||
| } | ||
| let name = v2String(params, "name") | ||
| var resolvedID: String? | ||
| v2MainSync { | ||
| if let name, !name.isEmpty, | ||
| let module = panel.loadState.model?.modules.first, | ||
| let target = module.targets.first(where: { $0.displayName == name }) { | ||
| panel.selectedTargetID = target.id | ||
| resolvedID = target.id.rawValue | ||
| } else { | ||
| panel.selectedTargetID = nil | ||
| } | ||
| } | ||
| return .ok(["target_name": name ?? "", "target_id": resolvedID ?? ""]) | ||
| } |
There was a problem hiding this comment.
Target name matching only searches the first module.
Lines 12377-12378 search only panel.loadState.model?.modules.first for a target matching the given name. In multi-module projects (e.g., a workspace containing multiple .xcodeproj entries), targets in subsequent modules will not be found, making them unreachable via this RPC.
🔍 Proposed fix to search all modules
private func v2ProjectSetSelectedTarget(params: [String: Any]) -> V2CallResult {
guard let (_, panel) = v2ResolveProjectPanel(params: params) else {
return .err(code: "not_found", message: "Project surface not found", data: nil)
}
let name = v2String(params, "name")
var resolvedID: String?
v2MainSync {
if let name, !name.isEmpty,
- let module = panel.loadState.model?.modules.first,
- let target = module.targets.first(where: { $0.displayName == name }) {
+ let model = panel.loadState.model {
+ let allTargets = model.modules.flatMap(\.targets)
+ if let target = allTargets.first(where: { $0.displayName == name }) {
- panel.selectedTargetID = target.id
- resolvedID = target.id.rawValue
+ panel.selectedTargetID = target.id
+ resolvedID = target.id.rawValue
+ }
} else {
panel.selectedTargetID = nil
}
}
return .ok(["target_name": name ?? "", "target_id": resolvedID ?? ""])
}🤖 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 `@Sources/TerminalController.swift` around lines 12369 - 12386, The
v2ProjectSetSelectedTarget function currently only checks the first module
(panel.loadState.model?.modules.first) so targets in other modules are missed;
modify the lookup inside the v2MainSync block to iterate/search across all
modules (e.g., gather modules.flatMap { $0.targets } or otherwise traverse
panel.loadState.model?.modules) and find the first target whose displayName
matches the provided name, then set panel.selectedTargetID = target.id and
resolvedID = target.id.rawValue (or set selectedTargetID = nil if no match)
while preserving existing name handling and thread-safety in v2MainSync.
Replace `await MainActor.run { guard let self else { return }; ... }`
with `await self?.applyLoaded(model)` / `await self?.applyLoadError(...)`.
Hops back to the main actor via the @MainActor-isolated methods,
satisfies Sendable closure capture rules, and clears the warning-budget
red on tests-build-and-lag.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
| } | ||
| applySessionPanelMetadata(snapshot, toPanelId: toolPanel.id) | ||
| return toolPanel.id | ||
| case .project: | ||
| guard let projectPath = snapshot.project?.projectPath, | ||
| let projectPanel = newProjectSurface( | ||
| inPane: paneId, | ||
| projectPath: projectPath, | ||
| focus: false | ||
| ) else { | ||
| return nil | ||
| } | ||
| applySessionPanelMetadata(snapshot, toPanelId: projectPanel.id) |
There was a problem hiding this comment.
Session snapshot state fields are serialized but never restored
SessionProjectPanelSnapshot captures activeTab, selectedSchemeName, selectedConfigurationName, and selectedNodePath, but the session-restore path only uses projectPath to reconstruct the panel. The other four fields are written to disk and silently discarded on restore — every session open lands on Files tab with no scheme/config/file selection, even if the user saved a different state. Since projectPanel.reload() runs asynchronously via a detached task, the snapshot values can be applied to the panel immediately after newProjectSurface returns and applyLoaded will validate (and keep or nil) each one when the load completes.
Ignoring AI reviewer per author request; required CI is green.
Summary
Adds a new `project` pane type that opens a `.xcodeproj` or `.xcworkspace` and renders it the way Xcode does: workspace + project navigator, targets table, Build Settings Levels view, and schemes. The pane hides low-value Xcode plumbing (raw pbxproj UUIDs,
xcuserdata, container blueprint strings) while keeping the model honest about what's resolved vs derived.CMUXProjectModelwith anXcodeProjectAdapter(tuist/XcodeProj-backed) and anXcconfigParser(handles#include/#include?, cycle detection, line comments). 15 Swift Testing tests, green in 58ms.ProjectPanelruntime + four SwiftUI tab views (Files, Targets, Build Settings, Schemes) wired through the existingPanelplumbing, session persistence, and Workspace surface lifecycle.cmux project open <path-to-.xcodeproj-or-.xcworkspace>CLI subcommand, backed byproject.openJSON-RPC method. Debug RPCs (project.set_tab,project.set_selected_target,project.set_selected_file,project.set_settings_filter,project.get_state) added for headless verification..xcodeprojor.xcworkspacepath through any existing cmux file-open path (right sidebar Files mode, drag-drop,file.openRPC,cmux open <path>CLI) now routes to a project surface instead of falling through to FilePreview.What's in the pane
Files — workspace + project group tree with target-membership chips per file. Filter bar at the top with magnifier + clear + match count. Filter mode auto-expands matching groups. Detail strip slides in when a file is selected; single-pane otherwise.
Targets — table of targets with product type, platforms, deployment target, bundle id (resolved via xcconfig chain), dependency count. Detail panel shows product-type icon header, metadata grid, dependencies list, and a build summary with "Open in Build Settings" jump.
Build Settings — Levels-style table with Setting / Effective / Target / Project columns. In-header Target picker, search filter, "Customized only" toggle, settings count. Target overrides marked with a leading accent bar + accent value cell. Inherited rows dimmed.
Schemes — shared + per-user schemes flattened across modules with dedup, single shared/personal badge in row + detail header. Detail shows Run / Test / Profile / Archive target attribution, launch args, environment.
Iteration history
Built end-to-end in this branch over 11+ iterations driven by three reviewer agents per pass (design/UX, product/PM, technical/Swift). Each pass screenshotted all four tabs, surfaced top fixes, applied the highest-impact ones, rebuilt, verified. Full log under `.iter-logs/CHANGELOG.md` in the worktree (committed for repro).
Highlights from the loop:
let rows = ...hoist in body; chrome scheme/config pickers flatMap across modules with dedup;applyLoadedvalidates persisted selections; schemeblueprintIdentifierno longer fabricates fake TargetIDs..xcodeproj/.xcworkspacerouting throughopenFileSurfaces.Open backlog (intentional deferrals, called out in CHANGELOG.md)
Test plan
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.Summary by cubic
Adds an Xcode-style project pane that opens
.xcodeprojand.xcworkspacefiles and shows Files, Targets, Build Settings, and Schemes. Routes these files to the new pane across the app and adds CLI/JSON‑RPC controls.New Features
CMUXProjectModelwithXcodeProjectAdapterandXcconfigParser(15 tests).ProjectPanelwith tabs (Files, Targets, Build Settings, Schemes), focus/search, and session persistence (SessionProjectPanelSnapshot).cmux project open <path>and JSON‑RPC:project.open,project.set_tab,project.set_scheme,project.set_configuration,project.set_selected_target,project.set_selected_file,project.set_settings_filter,project.get_state..xcodeproj/.xcworkspacevia sidebar, drag‑drop,file.openRPC, orcmux open <path>now loads the project pane.Bug Fixes
ProjectPanel.reloadby switching toapplyLoaded/applyLoadErroron the main actor.Written for commit 84bbf3a. Summary will update on new commits.
Summary by CodeRabbit
New Features
cmux project openCLI command (JSON or human-readable output).Tests
Documentation
Chores