docs/plans/2026-04-12-action-display-presentation.md
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Replace the scattered Action button display logic with a unified semantic presentation pipeline so predefined actions, custom bindings, Logi-branded actions, recording prompts, and unbound state all render consistently.
Architecture: Introduce ActionPresentation, ActionDisplayResolver, and ActionDisplayRenderer. Keep ButtonTableCellView responsible only for local state and interaction, while resolution and rendering move into dedicated helpers.
Tech Stack: Swift, AppKit NSPopUpButton, existing SystemShortcut, BrandTag, ButtonTableCellView, and xcodebuild test
Files:
MosTests/ButtonBindingTests.swiftStep 1: Add resolver-focused expectations in existing tests or lightweight presentation helpers
Cover at least:
custom::⌘⇧4 resolves to the same display title as the predefined screenshot action[Logi] text badgesStep 2: Run focused tests and confirm they fail before implementation
Run:
xcodebuild test -project Mos.xcodeproj -scheme Debug -destination 'platform=macOS' -only-testing:MosTests/ButtonBindingTests CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY=''
Expected: failures around missing unified presentation behavior.
Step 3: Commit nothing yet
Do not commit in red state.
ActionPresentation and ActionDisplayResolverFiles:
Mos/Windows/PreferencesWindow/ButtonsView/ActionDisplayResolver.swiftMos/Shortcut/SystemShortcut.swiftMos/Windows/PreferencesWindow/ButtonsView/RecordedEvent.swiftMosTests/ButtonBindingTests.swiftStep 1: Define the presentation model
Add:
struct ActionPresentation { ... }
enum ActionPresentationKind { ... }
Keep it small and semantic.
Step 2: Implement resolver entry points
Add a resolver API shaped roughly like:
func resolve(
shortcut: SystemShortcut.Shortcut?,
customBindingName: String?,
isRecording: Bool
) -> ActionPresentation
Step 3: Move semantic decisions into the resolver
Resolver should decide:
SystemShortcut.displayShortcut(matchingBindingName:)Step 4: Run focused tests
Run:
xcodebuild test -project Mos.xcodeproj -scheme Debug -destination 'platform=macOS' -only-testing:MosTests/ButtonBindingTests CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY=''
Expected: semantic-resolution tests pass.
Step 5: Commit
git add Mos/Windows/PreferencesWindow/ButtonsView/ActionDisplayResolver.swift Mos/Shortcut/SystemShortcut.swift Mos/Windows/PreferencesWindow/ButtonsView/RecordedEvent.swift MosTests/ButtonBindingTests.swift
git commit -m "refactor(buttons): resolve action display semantics centrally"
ActionDisplayRendererFiles:
Mos/Windows/PreferencesWindow/ButtonsView/ActionDisplayRenderer.swiftMos/Components/BrandTag.swiftMos/Windows/PreferencesWindow/ButtonsView/ButtonTableCellView.swiftMosTests/ButtonBindingTests.swiftStep 1: Move placeholder rendering responsibilities out of the cell
Create a renderer API shaped roughly like:
func render(
_ presentation: ActionPresentation,
into popupButton: NSPopUpButton
)
Step 2: Centralize final visual output
Renderer should handle:
Step 3: Keep cell-specific drawing helpers only if still useful
If badge image generation stays in the cell today, move it behind the renderer so the cell no longer decides when to use it.
Step 4: Run focused tests
Run:
xcodebuild test -project Mos.xcodeproj -scheme Debug -destination 'platform=macOS' -only-testing:MosTests/ButtonBindingTests CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY=''
Expected: renderer-backed display tests pass.
Step 5: Commit
git add Mos/Windows/PreferencesWindow/ButtonsView/ActionDisplayRenderer.swift Mos/Components/BrandTag.swift Mos/Windows/PreferencesWindow/ButtonsView/ButtonTableCellView.swift MosTests/ButtonBindingTests.swift
git commit -m "refactor(buttons): centralize action display rendering"
ButtonTableCellView to the new pipelineFiles:
Mos/Windows/PreferencesWindow/ButtonsView/ButtonTableCellView.swiftMosTests/ButtonBindingTests.swiftStep 1: Replace branch-specific refresh logic
Change refreshActionDisplay() to:
Step 2: Remove or inline obsolete helpers
Delete or stop using logic that is now duplicated by the resolver/renderer:
resolvedDisplayShortcut()displayCustomBinding(_:)setCustomTitle(...)Keep only the minimal helpers that are still view plumbing.
Step 3: Validate recording transitions
Ensure:
Step 4: Run focused tests
Run:
xcodebuild test -project Mos.xcodeproj -scheme Debug -destination 'platform=macOS' -only-testing:MosTests/ButtonBindingTests CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY=''
Expected: presentation flow is green end-to-end.
Step 5: Commit
git add Mos/Windows/PreferencesWindow/ButtonsView/ButtonTableCellView.swift MosTests/ButtonBindingTests.swift
git commit -m "refactor(buttons): route action display through presenter pipeline"
Files:
Step 1: Run full suite
Run:
xcodebuild test -project Mos.xcodeproj -scheme Debug -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY=''
Step 2: Verify key UI outcomes manually
Sanity-check these UI states:
Step 3: Commit any final fixups
If verification requires small fixups, commit them separately with a focused message.
After implementation and verification:
Do not skip verification before claiming success.