Skip to content

Add global scripts and color picker for custom scripts#305

Merged
sbertix merged 5 commits into
mainfrom
sbertix/global-scripts
May 9, 2026
Merged

Add global scripts and color picker for custom scripts#305
sbertix merged 5 commits into
mainfrom
sbertix/global-scripts

Conversation

@sbertix
Copy link
Copy Markdown
Collaborator

@sbertix sbertix commented May 9, 2026

Summary

  • Global scripts — a new GlobalSettings.globalScripts: [ScriptDefinition] shared across every repository, surfaced in the toolbar Script Menu, command palette, and runScript deeplinks. Repo scripts win on ID collision via [ScriptDefinition].merged(repo:global:). Settings has a new "Global Scripts" pane reachable via supacode://settings/scripts and supacode settings scripts.
  • Color picker for custom scriptsRepositoryColor moved to SupacodeSettingsShared/Models so ScriptDefinition.tintColor can use the same palette as repo customization, and the new ColorSwatchRow is reused across both surfaces. The old TerminalTabTintColor enum is retired.

Notable invariants and hardening

  • kind = .custom for globals is enforced in two layers (decode + constructor); a hand-edited kind: "run" global cannot hijack the toolbar's primary "Run" slot.
  • KeyedDecodingContainer.decodeLossyArrayIfPresent(forKey:) lossy-decodes script arrays — element failures are dropped + logged (with PII-safe messages); array-shape failures don't poison the whole settings file.
  • TerminalLayoutSnapshot.TabSnapshot.tintColor decodes lossily so a future tint vocab change can't destroy a saved layout.
  • Empty-command scripts surface in the palette as "Configure: …" entries that route to the appropriate settings pane.
  • Toolbar ScriptMenu uses a display-only fingerprint as .id — renames invalidate the NSMenu cache without per-keystroke command rebuilds.
  • VoiceOver: every swatch (Default, predefined, Custom) reports the .isSelected trait so screen readers track the visible selection ring.
  • Both script settings panes auto-scroll to a newly-added section.

Test plan

  • make build-app clean
  • make check (format + lint) clean
  • Targeted suites pass: AppFeatureRunScriptTests, AppFeatureDeeplinkTests, RepositoryColorTests, RepositorySettingsScriptTests, SettingsFeatureTests, TerminalLayoutSnapshotTests, CommandPaletteFeatureTests
  • Manual: add a global script via Settings → confirm it appears in the toolbar Menu of every repository
  • Manual: add a repo-level script with the same ID as a global → confirm repo wins
  • Manual: empty a configured script's command → confirm it surfaces as "Configure: …" in the palette and routes to the right pane
  • Manual: pick a custom hex tint, restart the app → confirm the tint round-trips
  • Manual: remove a global script → confirm the alert dismisses cleanly and doesn't reappear when reopening Settings

sbertix added 5 commits May 9, 2026 00:20
Introduce a "Scripts" section in app-level settings that defines `.custom`
scripts available to all repos, alongside the existing per-repo scripts.
Globals merge into the toolbar menu under a "Global" section and into the
command palette. Toolbar exposes separate "Manage Repo Scripts…" and
"Manage Global Scripts…" entries. Both editors now render via
`PlainTextEditor` (moved into `SupacodeSettingsFeature`).
- ScriptMenuIdentity is now a Hashable struct hashing full ScriptDefinitions
  so renames and field edits invalidate the toolbar NSMenu cache.
- Filter empty-command globals from the toolbar's "Global" section so
  half-configured entries don't pollute every repo's menu.
- Collapse visibleGlobalScripts to Array(allScripts.dropFirst(...)) and
  let merged() own the "repo wins on ID collision" rule.
- Add AppFeature.State.resolveScript(id:) and use it from runNamedScript.
- decodeLossyArrayIfPresent KeyedDecodingContainer extension subsumes the
  ad-hoc decodeScriptsLossily helper and emits SupaLogger breadcrumbs on
  malformed entries / arrays.
- Compare global script IDs as a Set so a pure reorder doesn't prune
  command-palette recency.
- ColorSwatchRow: collapse a11y onto a single accessibilityElement on the
  Custom swatch and trim the multi-paragraph rationale comments.
- AGENTS.md: enforce "decode + constructor are load-bearing for .custom;
  merge order is semantic, not a security guard."
- Tests: palette-routed collision test + ScriptDefinition tintColor
  legacy-rawValue decode regression.
- TabSnapshot decodes tintColor lossily so a custom-hex value persisted on
  this build doesn't nuke an entire saved layout when read by an older
  build that only knows the named-color enum. AGENTS.md flags the
  one-way migration.
- Lossy element decode no longer interpolates the full DecodingError —
  release os.Logger uses privacy: .public, so the prior message could
  leak user-defined script names and command bodies.
- Default and predefined color swatches gain the .isSelected a11y trait
  so VoiceOver matches the visible selection ring on every swatch.
- RepositorySettingsFeature.removeScript shares the global-script
  warning copy ("Any running instance keeps running…") and explicitly
  clears state.alert on confirmation so the alert doesn't reappear when
  switching settings panes or reopening the window.
- ScriptMenuIdentity uses a display-only fingerprint (id, name, icon,
  tint, has-command) instead of hashing whole ScriptDefinitions; editing
  a command body no longer churns the toolbar Menu identity.
- Empty-command scripts surface in the command palette as
  "Configure: …" entries and route to the appropriate Settings pane
  instead of being filtered out and silently dropped.
- AppFeature.State.init seeds globalScripts so allScripts isn't empty
  before the first settingsChanged delegate fires.
- GlobalScriptsSettingsView scrolls the newly added section into view
  so adding a global gives visible feedback when the form is taller
  than the window.
- RepositoryColor predefined-case ordering aligned with the UI palette.
- AGENTS.md/GlobalSettings comment marks the .custom kind normalization
  as intentionally one-way to deter "fix" PRs.
- RepositoryScriptsSettingsView wraps its Form in ScrollViewReader and
  scrolls the newly-added section into view, mirroring the global pane
  so adding a script gives the same feedback in both surfaces.
- AGENTS.md: tighten the toolbar visibleGlobalScripts bullet to describe
  policy ("drop shadowed and empty-command globals") rather than mechanics.
@sbertix sbertix enabled auto-merge (squash) May 9, 2026 21:10
@sbertix sbertix merged commit c7891f9 into main May 9, 2026
2 checks passed
@sbertix sbertix deleted the sbertix/global-scripts branch May 9, 2026 21:13
@tuist
Copy link
Copy Markdown

tuist Bot commented May 10, 2026

🛠️ Tuist Run Report 🛠️

Builds 🔨

Scheme Status Duration Commit
supacode 41.0s b9d02ed44

marvtub added a commit to marvtub/supacode that referenced this pull request May 11, 2026
Aligns the prompt's color picker with the post-supabitapp#305 world: drop the
customColor SwiftUI Color mirror and the .selectColor action from
WorktreeCreationPromptFeature, and bind directly to the shared
ColorSwatchRow(color: $store.color) component instead of the now-removed
RepositoryColorSwatchRow shim. Trims the matching test coverage and
swaps the explicit NSColorPanel.shared.orderOut(nil) cleanup for the
shared .dismissSystemColorPanelOnDisappear() modifier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
marvtub added a commit to marvtub/supacode that referenced this pull request May 11, 2026
Aligns the prompt's color picker with the post-supabitapp#305 world: drop the
customColor SwiftUI Color mirror and the .selectColor action from
WorktreeCreationPromptFeature, and bind directly to the shared
ColorSwatchRow(color: $store.color) component instead of the now-removed
RepositoryColorSwatchRow shim. Trims the matching test coverage and
swaps the explicit NSColorPanel.shared.orderOut(nil) cleanup for the
shared .dismissSystemColorPanelOnDisappear() modifier.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
marvtub added a commit to marvtub/supacode that referenced this pull request May 12, 2026
…erenced

supabitapp#305 moved `RepositoryColor` from the app target to `SupacodeSettingsShared`,
so files in `supacode/Domain` that hold the type need the explicit import.
Previously masked because the type was visible in the same module; the rebase
on top of `main` surfaces it as a compile error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
marvtub added a commit to marvtub/supacode that referenced this pull request May 12, 2026
…entTests

Mirrors the import already present in the other customization test files
and matches the runtime fix in 78d0072. `RepositoryColor` cases like `.red`
and `.blue` need the explicit import after supabitapp#305 moved the type out of the
app target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant