Restore search-result row highlight in CmuxSettingsUI#5012
Conversation
The SPM settings reimplementation kept the search-to-scroll navigation but dropped the pulse that flashed an accent border on the navigated-to row, so clicking a search hit silently jumped without confirming which row matched. Legacy SettingsView drove this through an environment highlight state plus a per-row TimelineView border; the package's rows were never anchored, so even the scroll-to-center no-oped. Port the legacy affordance: SettingsSearchHighlight adds the highlight state, environment plumbing, and the pulsing-border modifier. Each SettingsCardRow now resolves the cmux.json path it already declares via configurationReview into the sidebar/search anchor id through a new SettingsSearchIndex.anchorID(forSettingsPath:), tags itself with that id, and pulses when it is the active highlight target. The settings root injects the index and arms the highlight on every highlighting navigation request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughRows can declare search anchors (explicit or derived from config paths); SettingsSearchIndex maps dotted paths to curated anchor ids; the window resolves an anchor, scrolls to it, and triggers a timed pulsing highlight on the matching row. ChangesSettings search highlighting
Sequence DiagramsequenceDiagram
participant User
participant SettingsWindowRoot
participant SettingsSearchIndex
participant ScrollView
participant SettingsCardRow
participant SettingsSearchHighlightModifier
User->>SettingsWindowRoot: navigateTo(settings path)
SettingsWindowRoot->>SettingsSearchIndex: anchorID(forSettingsPath:)
SettingsWindowRoot->>ScrollView: scrollTo(anchorID)
SettingsWindowRoot->>SettingsSearchHighlightModifier: set highlight state (anchor, token, startedAt)
SettingsCardRow->>SettingsSearchHighlightModifier: matched anchor -> render pulsing accent
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error, 1 warning)
✅ Passed checks (16 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryRestores the search-result row highlight in
Confidence Score: 4/5Safe to merge for the highlight and reopen fixes; the The highlight implementation is solid — the finite
Important Files Changed
Reviews (16): Last reviewed commit: "Remove temporary window-state debug prob..." | Re-trigger Greptile |
| func body(content: Content) -> some View { | ||
| content | ||
| .background { | ||
| if matches(highlightState) { | ||
| TimelineView(.animation) { context in | ||
| let opacity = highlightOpacity(at: context.date, for: highlightState) | ||
| RoundedRectangle(cornerRadius: 8, style: .continuous) | ||
| .fill(Color.accentColor.opacity(opacity * 0.24)) | ||
| .overlay( | ||
| RoundedRectangle(cornerRadius: 8, style: .continuous) | ||
| .stroke(Color.accentColor.opacity(opacity), lineWidth: 2.5) | ||
| ) | ||
| .shadow(color: Color.accentColor.opacity(opacity * 0.24), radius: 8, x: 0, y: 0) | ||
| } | ||
| // Restart the animation when the user re-navigates to | ||
| // the same anchor: a changing token forces a fresh | ||
| // TimelineView identity so `startedAt` re-seeds. | ||
| .id(highlightState.token) | ||
| } | ||
| } |
There was a problem hiding this comment.
TimelineView(.animation) keeps a live display-link after fade-out
Once elapsed >= 5.9 the highlightOpacity function always returns 0, yet highlightedSearchAnchorID is never cleared after the animation completes — only on the next navigation. So matches(highlightState) stays true and the TimelineView(.animation) keeps firing at display-refresh rate (~120fps on ProMotion), calling highlightOpacity every frame and drawing RoundedRectangle shapes with opacity(0) indefinitely. The display link for the targeted row remains live until the user clicks another search result or closes the window. Consider clearing the anchor id after the animation window via a one-shot Task { try? await Task.sleep(for: .seconds(6)); highlightedSearchAnchorID = nil } gated by the same token, or by deriving visibility from elapsed in a TimelineSchedule.explicit schedule that terminates after the last keyframe.
Rule Used: Flag new blocking or timing-based synchronization ... (source)
There was a problem hiding this comment.
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/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift`:
- Around line 18-28: Add Swift-DocC triple-slash comments for every public
symbol listed: provide a one-sentence summary for the
SettingsSearchHighlightState struct and its public init, and add similar
one-sentence DocC comments for EnvironmentValues.settingsSearchHighlightState,
EnvironmentValues.settingsSearchIndex, View.settingsSearchAnchor(_:) and
View.settingsSearchAnchors(_:); place the comments immediately above each
declaration, follow the example one-sentence summary pattern used elsewhere in
the file, and ensure each public symbol has at least one clear descriptive
sentence.
In
`@Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift`:
- Around line 73-82: Consolidate the three separate `@State` vars
(highlightedSearchAnchorID, searchHighlightToken, searchHighlightStartedAt) into
a single `@State` value (e.g. highlightState: SettingsSearchHighlightState) and
update all places that read or write them (including SettingsCardRow access via
\.settingsSearchHighlightState) to use highlightState.anchorID, .token, and
.startedAt; replace the three-step update pattern with a single assignment like
highlightState = SettingsSearchHighlightState(anchorID: anchorID, token:
highlightState.token + 1, startedAt: Date()) so updates are atomic and there are
no dangling mutable side channels.
🪄 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: 676a3b57-0ed8-430e-bfb4-da920cce78b4
📒 Files selected for processing (5)
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Chrome/SettingsCardRow.swiftPackages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swiftPackages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchIndex.swiftPackages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swiftPackages/CmuxSettingsUI/Tests/CmuxSettingsUITests/SettingsSearchIndexTests.swift
The legacy in-app settings (Sources/SettingsNavigation.swift) still ships in the app target and declares a same-named SettingsSearchHighlightState plus View/EnvironmentValues extensions. The app target imports CmuxSettingsUI, so the package's public copies made every unqualified use (settingsSearchAnchor(s), the highlight-state init) ambiguous and broke the Release/Debug app builds. None of these symbols are consumed outside the package, so make them internal; the public API stays limited to SettingsSearchIndex.anchorID(forSettingsPath:). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift (1)
30-34: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick winRemove the unneeded explicit initializer.
Swift automatically synthesizes memberwise initializers for structs. Since all properties are
letconstants, the synthesized initializer is identical to the explicit one. Removing it reduces code and resolves the SwiftLint warning.♻️ Proposed fix
let startedAt: Date? - - init(anchorID: String?, token: Int, startedAt: Date?) { - self.anchorID = anchorID - self.token = token - self.startedAt = startedAt - } }🤖 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/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift` around lines 30 - 34, Remove the explicit initializer init(anchorID: String?, token: Int, startedAt: Date?) from the struct (the synthesized memberwise initializer already matches since all properties are let constants); delete the init(...) method so Swift uses the automatically generated memberwise initializer and the SwiftLint warning is resolved.
🤖 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.
Outside diff comments:
In
`@Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift`:
- Around line 30-34: Remove the explicit initializer init(anchorID: String?,
token: Int, startedAt: Date?) from the struct (the synthesized memberwise
initializer already matches since all properties are let constants); delete the
init(...) method so Swift uses the automatically generated memberwise
initializer and the SwiftLint warning is resolved.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7434d90a-97b7-47d3-95fa-cf2b3af8d31d
📒 Files selected for processing (1)
Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swift
Greptile flagged that TimelineView(.animation) keeps a live display link firing at refresh rate forever after the pulse fades, since the highlight state isn't cleared until the next navigation. Switch to a finite .explicit schedule covering only the pulse window so the TimelineView stops requesting frames once the fade completes (no Task.sleep, matching the no-sleep-in-app-code rule). A row that scrolls in mid-pulse still renders the correct frame; a row whose pulse already elapsed renders the final invisible frame once and schedules nothing further. Also consolidate the three highlight @State properties into a single SettingsSearchHighlightState value (CodeRabbit nitpick). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed review feedback in e606214:
Note: the |
Two gaps from dogfooding:
1. The "Sidebar Branch Layout" row never highlighted because its row
path (configurationReview .json("sidebar.branchLayout")) didn't match
the curated entry's synonym dotted token (sidebar.branchVerticalLayout,
the internal defaults key), so anchorID(forSettingsPath:) returned nil.
Add sidebar.branchLayout to the synonyms. Add
SettingsRowAnchorResolutionTests, which asserts every configReview
path declared in Sections/*.swift resolves to a real indexed entry —
this is what caught branchLayout and guards the whole bridge.
2. Section headers didn't pulse when their section search hit was
clicked. SettingsSectionHeader now takes its SettingsSectionID and
applies a highlight-only modifier (settingsSearchHighlight, no extra
scroll .id since the enclosing section already owns section:<raw>).
applyScrollNavigation now arms the highlight for section hits too
(anchorID == sectionID), not just deep row hits. The Browser >
Import Browser Data subsection gets the same treatment.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two issues from dogfooding: (1) search surfaced raw catalog keys with no UI (e.g. account.welcomeShown); (2) several results didn't highlight because their row didn't resolve to the curated entry id. Root cause was a leaky path bridge plus a catalog back-fill: - The index back-filled every SettingCatalog key as a search hit even when no row existed. Drop the back-fill: search now indexes only sections + curated entries (which mirror detail-pane rows), and anchorID(forSettingsPath:) no longer has a setting:<path> fallback. - Many curated entries' synonyms omitted the row's exact cmux.json path (or used the internal key, e.g. sidebar.branchVerticalLayout vs the row's sidebar.branchLayout), so rows resolved to the wrong/nonexistent id. Add the exact path token to every such entry (~20). - Rows with no single cmux.json key (Theme/App Icon pickers, Account card, Reset All, Open cmux.json, Documentation, Enable Browser, Dock, global hotkey enable/recorder, shortcut chords/reset/list, import block + hint, File Drops) now carry an explicit searchAnchorID / settingsSearchAnchors equal to their curated id. SettingsRowAnchorResolutionTests is now bidirectional and exhaustive: forward (every row configReview path resolves to a real entry) and inverse (every curated search result is reachable from a row anchor), so a future drift in either direction fails CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffing the curated table against the legacy SettingsNavigation index (still in the app target) surfaced settings legacy made searchable that the SPM rewrite dropped even though the rows still exist in the detail pane: Desktop Notifications, Terminal Config, Browsing History, and Reset Palette. Re-add curated entries and anchor each row so they search and highlight. (fork-conversation-default stays out: the new UI dropped that row.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Enumerating every search result (per-letter sweep) surfaced 13 entries whose search label differed from the row in the detail pane, e.g. "Disable cmux Browser" vs the row's "Enable cmux Browser", "Workspace Presentation Mode" vs "Minimal Mode", and the global-hotkey recorder showing "Global Hotkey" (duplicating the section row) instead of "Show/Hide All Windows". Align each curated title with its row's label so the search hit reads the same as the setting it scrolls to. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…highlight # Conflicts: # Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Default.swift
Clicking a sidebar section while scrolled far away (e.g. jumping to Keyboard Shortcuts from the Browser/Import area) sometimes left the section header mid- or bottom-viewport instead of pinned at the top. The LazyVStack only realizes off-screen sections as the first scroll nears them, so a single scrollTo lands before the target's geometry is known. Re-issue the scroll on the next runloop tick (generation-guarded) so the header reliably settles at the top once realized. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A subagent audit of the search<->row mapping surfaced issues the data tests missed because the test path-list assumed a row existed: - Phantom result: "HTTP Hosts Allowed in Embedded Browser" (setting:browser:http-allowlist) is a custom block, not a SettingsCardRow, so it had no anchor — its search hit highlighted nothing. Tag the block with the explicit anchor; correct the test (it's not a .json row, so move it to the explicit-anchor list). - Integration entries (claude-code, claude-path, ripgrep-path, subagent-notifications, cursor, gemini) were filed under .account in search but their rows live in AutomationSection. Move the curated entries to .automation so the search result's section/icon matches the row's real home. - Five search titles didn't match their row labels: warn-before-closing -tab-x-button, hidden-webview-discard, hidden-webview-discard-delay, badge, import-hint. Align to the row text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second independent audit found two anchor collisions: the agent-hibernation entry's synonyms listed all three sub-paths (enabled/idleSeconds/maxLiveTerminals) and the search-engine entry listed the custom name/url paths, so every one of those always- or conditionally-rendered rows resolved to the SAME anchor id. Multiple rows then carried the same SwiftUI .id, making proxy.scrollTo ambiguous and the highlight land on the wrong row. Keep only the primary dotted path on each entry (agentHibernation.enabled, defaultSearchEngine); the sub-field rows intentionally carry no search anchor (the entry's hit lands on the primary row). Drop those sub-paths from the row-anchor test and add rowAnchorsAreUniqueAcrossRows so a future synonym that reintroduces a collision fails CI. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-3 audit: de-colliding agent-hibernation/search-engine by dropping their sub-paths left four real detail rows unsearchable (Hibernate After Idle Seconds, Max Live Agent Terminals, Custom Search Engine Name, Custom Search URL). Give each its own curated entry with a unique anchor so every row is independently searchable + highlightable with no shared .id. Re-add the four paths to the row-anchor test; the uniqueness guard confirms no collision. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Delete unused settingsSearchAnchor(_:) singular (no call sites in the package; rows use settingsSearchAnchors, headers use settingsSearchHighlight). - Add a written justification for the two-pass DispatchQueue.main.async scroll (package concurrency rule): it's runloop-tick sequencing to let the LazyVStack realize the target between scrollTo passes, with no async-native equivalent that preserves the layout-pass timing. - Document why SettingsRowAnchorResolutionTests' path list is a hand-maintained contract (SwiftUI rows can't be reflected) and how the reachability + uniqueness tests bound the drift. Feature-parity review (independent agent) reports CLEAN; these are code-quality/convention cleanups only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User caught a real dead search hit: "Custom Search Engine Name" appears in search but its row only renders when the engine is Custom (default is Google), so clicking it highlighted nothing. Same class: Custom Search URL and Socket Password (mode defaults to cmuxOnly, not password). These are conditional sub-fields, not standalone settings. Remove their curated entries and fold their search terms into the always-visible parent (Default Search Engine / Socket Control Mode), so searching those terms highlights a real, visible row instead of a dead one. Update the row-anchor test + its doc to exclude these conditional sub-controls (host-whitelist/external-patterns/http-allowlist stay — link toggles default ON, so those rows are visible by default). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These scratch screenshots from local verification were swept in by a git add -A and shouldn't be in the PR. Delete them and gitignore artifacts/ so local dogfood output never gets committed again. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A setting hit issued scrollTo(section, .top) and scrollTo(row, .center) in the same tick; the section-top scroll could win, so a row near the bottom of a long section (Gemini CLI Integration in Automation) landed off-screen below the header instead of centered. Scroll to exactly one target: section hits pin the header to the top, row hits center the row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Root cause of deep-row search hits landing at the top (Automation, Terminal, any below-the-fold section): in the detail LazyVStack only realized sections register their row .ids, so scrollTo(rowID) for a row in an off-screen section finds no id and no-ops, stranding the view at the top. Scrolling to exactly the row (my prior change) made it worse — the section never realized. Fix: two ticks in order — tick 1 scrolls to the SECTION (its body is an eager Group, so realizing the section registers every row id); tick 2, after that layout pass, scrolls to the specific row and centers it (or re-pins the header for a section hit). Now Gemini CLI Integration, Suppress Subagent Notifications, Resume Commands, and any deep row center correctly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the LazyVStack + nested two-tick scroll with an eager VStack and one scrollTo call. The double-scroll only existed to work around LazyVStack realization: a row's .id isn't registered until its section is realized, so scrollTo to a deep row in an off-screen section (Gemini, Suppress Subagent, Resume Commands, any below-the-fold row) no-oped and stranded the view at the top. With an eager stack every anchor is always registered, so a single scrollTo resolves any row regardless of scroll position. The remaining hop off the current update is a main-actor Task, not DispatchQueue.main .async (forbidden by the package's concurrency policy). Section hits pin the header to .top; row hits center the row. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Typing ":all" in settings search (DEBUG builds only) returns every indexed entry, sections and settings alike, so the search → scroll → highlight path can be walked one row at a time during dogfooding. The sentinel is matched against the raw query before tokenization and is compiled out of Release builds. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Settings open jumped to ~2s after the detail stack went eager: the keyboard-shortcuts card builds ~166 AppKit-backed recorder rows, and eager rendering instantiated all of them on window open. Wrap just that ForEach in a LazyVStack so the rows realize as the section scrolls into view. The per-shortcut rows aren't search anchors (only the enclosing card is), so they don't need an eagerly-registered .id — every scroll/highlight target stays addressable while open latency drops back to near-instant. Every other section is a handful of rows, so this is the only list that needed it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Instrument show()/configure()/performFocus() to capture whether a "Settings didn't open" repro is a failed window creation (SwiftUI single-Window recreate race) or a created-but-not-fronted window (focus/ordering race). Body-only + one private helper; compiled out of Release, and cmuxApp.swift is untouched so the build stays incremental. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SwiftUI's single Window scene hides (orders out) the settings window on close instead of destroying it, and openWindow(id:) then no-ops because the scene still owns that hidden window. existingWindow() filtered on isVisible, so after a close it returned nil and show() fell through to the dead openWindow() call — the window never came back. Reproduced deterministically: first open works, every reopen-after-close logged nsappMatches=1 [vis=0] followed by no configure/focus. Return the lingering window regardless of visibility so show() re-fronts it via makeKeyAndOrderFront. First-ever open still creates it through openWindow(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| @Test | ||
| func debugSentinelReturnsEveryEntry() { | ||
| let index = SettingsSearchIndex(catalog: SettingCatalog()) | ||
| let all = index.match(":all") | ||
| #expect(all.count == index.entries.count) | ||
| #expect(Set(all.map(\.id)) == Set(index.entries.map(\.id))) | ||
| // A normal query is still filtered, so the sentinel isn't just | ||
| // "everything always". | ||
| #expect(index.match("copy on select").count < index.entries.count) | ||
| } |
There was a problem hiding this comment.
Debug-only test not guarded by
#if DEBUG
debugSentinelReturnsEveryEntry tests the #if DEBUG-only branch inside SettingsSearchIndex.match(_:). Without a #if DEBUG guard on the test itself, running swift test -c release causes match(":all") to fall through to the normal tokenizer — ":" is a punctuation character and gets stripped, leaving the token "all", which matches only a subset of entries. Both #expect(all.count == index.entries.count) and the Set equality assertion will fail, breaking the release test suite.
The reopen-after-close diagnosis is done; drop the dogfood-loop logging in show()/configure()/performFocus(). The fix (existingWindow returning the hidden window) and the pre-existing settings.window.show log stay. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai both prior actionable comments are already resolved in the current branch:
Please re-review. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check SettingsWindowScene.swift for `@State` consolidation
rg -n '`@State`|searchHighlight|SettingsSearchHighlightState' Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Scene/SettingsWindowScene.swift
echo "---"
# Check SettingsSearchHighlight.swift for access control (public vs internal)
rg -n 'public |internal |struct SettingsSearchHighlight|SettingsSearchHighlightState|settingsSearchIndex|settingsSearchAnchors|settingsSearchAnchor' Packages/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/SettingsSearchHighlight.swiftLength of output: 2248
Both prior actionable comments are resolved. The remaining open item is the flaky universal-LTO link in the If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! |
Findings already addressed (single @State consolidation; symbols made internal so public-DocC ask is moot). Per maintainer instruction, disregarding the AI review to merge.
What
When you search in Settings and click a result, the navigated-to row should flash an accent-colored rounded border (the "find me" pulse). The legacy in-app settings window did this; the SPM reimplementation (
CmuxSettingsUI) kept the search-to-scroll path but dropped the highlight, and the rows were never anchored, so even the scroll-to-center quietly no-oped.How
Ports the legacy affordance into the package:
SettingsSearchHighlight.swiftaddsSettingsSearchHighlightState, its environment key, thesettingsSearchAnchor(s)view modifiers, and the pulsing-borderTimelineViewmodifier (same fade curve as legacy).SettingsSearchIndex.anchorID(forSettingsPath:)maps a row's dotted cmux.json path to the sidebar/search anchor id. The map is built automatically from the curated entries' dotted synonym tokens, so there's no second hand-maintained id table.SettingsCardRowresolves the path it already declares viaconfigurationReviewinto that anchor id, tags itself with.id(...), and pulses when it's the active target.SettingsWindowRootinjects the index and arms the highlight (anchor + token + start time) on every highlighting navigation request, mirroring legacyapplySettingsNavigation.No new keyboard shortcuts, no app-target changes; the behavior lives entirely in the settings package.
Test
swift testonCmuxSettingsUIcovers the new resolver: the "Show Branch + Directory in Sidebar" path resolves to its sidebar hit anchor, resolved anchors always correspond to a real indexed entry, and unknown paths resolve tonil.Visual one-to-one parity with the legacy highlight is verified by dogfooding a tagged build (search a query, click a hit, confirm the row scrolls into view and pulses the accent border).
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmithwith what you need. Autofix is disabled.Summary by cubic
Restores the search-result highlight in
CmuxSettingsUIso clicking a result scrolls to and pulses the exact row or section. Also fixes the Settings window not reopening after close.Bug Fixes
cmux.jsonpaths to stable anchors; explicit anchors for action/picker/custom rows; fixed the "Sidebar Branch Layout" synonym; restored anchors for Terminal Config, Desktop Notifications, Browsing History, and Reset Palette; titles match row labels.artifacts/.Refactors
settingsSearchAnchor(_:); highlight state/index symbols are internal to avoid app-target clashes.SettingsRowAnchorResolutionTestswith anchor-uniqueness checks; extendedSettingsSearchIndexTests; added DEBUG-only ':all' search sentinel; lazy-loaded the keyboard-shortcuts list for faster open.Written for commit beb34cc. Summary will update on new commits.
Summary by CodeRabbit
New Features
Tests
Content