@mmerterden/multi-agent-pipeline 10.2.0 → 10.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -14,6 +14,32 @@ Internal file-layout changes that don't affect the slash-command surface are sti
14
14
 
15
15
  ---
16
16
 
17
+ ## [10.3.0] - 2026-07-02
18
+
19
+ Android (Jetpack Compose) parity for the interaction skills shipped in 10.2.0,
20
+ plus two fixes from a review pass. The cross-cutting integration adapters are
21
+ now genuinely dual-platform, honoring the figma-common "dispatched by both iOS
22
+ and Android" contract.
23
+
24
+ ### Added
25
+
26
+ - **Android (Jetpack Compose) sections** in `figma-navigation`, `figma-overlays`,
27
+ and `figma-bottom-sheets`. Same emit-intent / caller-owns rules and the same
28
+ `figma-config` `ui.*` hooks; native-first per platform (Navigation Compose;
29
+ Snackbar / AlertDialog / Dialog + state-driven loading; `ModalBottomSheet` +
30
+ `rememberModalBottomSheetState`). The Android orchestrator uses the Compose
31
+ section, the iOS orchestrator the SwiftUI section. `figma-evolve-component`
32
+ wording made platform-neutral (SwiftUI or Compose).
33
+
34
+ ### Fixed
35
+
36
+ - **`animated-gradient-border` reference snippet** now animates the
37
+ `AngularGradient(angle:)` on a static shape instead of `rotationEffect` on the
38
+ stroked rounded-rect (which spun the whole shape — glitchy corners).
39
+ - **Genericized the sheet corner-radius example token** (`.Radius.n12` →
40
+ `.CornerRadius.radius12`) so no app-specific token leaks into the generic
41
+ pipeline.
42
+
17
43
  ## [10.2.0] - 2026-07-02
18
44
 
19
45
  Generic SwiftUI interaction coverage for the figma-to-swiftui pipeline. Three
package/README.md CHANGED
@@ -15,9 +15,9 @@
15
15
  | Copilot skills (dash-form `multi-agent-*`) | 34 |
16
16
  | Platforms (native: Claude Code, Copilot CLI · adapter: Cursor, Antigravity, VS Code Copilot Chat, Codex CLI) | 6 |
17
17
  | Store-compliance skills (`apple-archive-compliance`, `google-play-compliance`) | 2 |
18
- | Figma skills (iOS + Android + Common) | 37 |
18
+ | Figma skills (iOS + Android + Common) | 41 |
19
19
  | External skill catalog (`shared/external/`) | 143 |
20
- | Total `SKILL.md` files across all groups | 217 |
20
+ | Total `SKILL.md` files across all groups | 221 |
21
21
  | Smoke suites | 108 |
22
22
  | Golden-task fixtures (`pipeline/eval/golden-tasks/`) | 8 |
23
23
  | Eval-triage fixtures | 11 |
@@ -154,6 +154,8 @@ slows down the install.
154
154
 
155
155
  ## What's new
156
156
 
157
+ - **v10.3.0** (2026-07-02) - Android (Jetpack Compose) parity for the 10.2.0 interaction skills: `figma-navigation` / `figma-overlays` / `figma-bottom-sheets` now carry both a SwiftUI and a Compose section (Navigation Compose; Snackbar/AlertDialog/Dialog + state-driven loading; `ModalBottomSheet`), same emit-intent rules and `ui.*` config hooks on both platforms. Plus a review pass: corrected the `animated-gradient-border` snippet (animate `AngularGradient(angle:)` on a static shape, not `rotationEffect`) and genericized the sheet corner-radius example token.
158
+ - **v10.2.0** (2026-07-02) - Generic SwiftUI interaction coverage for figma-to-swiftui: three new cross-cutting integration skills (`figma-navigation`, `figma-overlays`, `figma-bottom-sheets`) + a reconcile-and-extend workflow (`figma-evolve-component`), all native-SwiftUI-first with an optional per-project `ui.*` config hook (so the same capabilities work on any SwiftUI codebase, no app-specific coupling). Phase 3D dev detection (§1.5.4) and Phase 4 review both consume them; `figma-to-swiftui` accessibility reference enriched with the VoiceOver-minimalism decision tree; new `animated-gradient-border` UI pattern. Figma-common skills 27 → 31, total figma skills 37 → 41.
157
159
  - **v10.1.0** (2026-06-20) - Fable 5 retired (no longer available): the five heavy agent personas plus Phase 1 / Phase 2 / Phase 4 reviewer-1 + triage and `--dev` fast mode now route to Opus (top available tier); fallback ladder is `opus -> sonnet`. Per-task cost guard (`costBudget`) now defaults on in warn mode. Token economy: Phase 4 shared cache prefix + single-repo diff cap, Phase 1 light Explore tier for small bugfixes, triage prior-art injection capped.
158
160
  - **v10.0.0** (2026-06-12) - quality major driven by a 10-category self-audit + competitive sweep: validator gates wired into phases 1/2/4 (fails closed), Phase 3 code-simplifier diff-shrink pass, Phase 4 lesson memory loop, Phase 2 cross-artifact consistency gate, skill frontmatter linter (repaired 31 SKILL.md), installer `--dry-run` + unknown-flag rejection, timeout-guarded smoke runner, adapter family deduplicated (~490 lines), CI hardening (blocking lint, npm audit, shellcheck), tag-driven npm release automation, CHANGELOG split (310KB -> 40KB).
159
161
  - **v9.10.2** (2026-06-11) - live per-phase token narration on completion; tracker mandates per-phase cost lines in the final report.
@@ -8,6 +8,8 @@ End-to-end guide for generating production-ready UI components from Figma design
8
8
  - **Platform-agnostic orchestration** — classification happens once in Phase 0; the orchestrator picks the right code generator for iOS or Android based on `figmaConfig.project.platform`.
9
9
  - **Multi-repo aware** — components span multiple repos (tokens in `common`, view in `components`, docs in `wiki`). The orchestrator sequences writes correctly.
10
10
  - **Non-blocking side effects** — wiki generation + Jira sync run as augmentations; failures log and Phase 7 continues.
11
+ - **Cross-cutting integration skills** — Phase 3D detects content patterns (`figma-form-integration`, `figma-price-integration`, `figma-ui-patterns`) and interaction patterns (`figma-navigation`, `figma-overlays`, `figma-bottom-sheets`) and dispatches the matching skill. All are **native-SwiftUI-first**; a project's own navigation/overlay/sheet system is used only when `figmaConfig.ui.{navigationSystem,overlaySystem,sheetSystem}` declares one (absent → stock SwiftUI: `NavigationStack`, `.alert`/`.sheet(item:)`, `.presentationDetents`). Generic across SwiftUI codebases.
12
+ - **Evolve an existing component** — `figma-evolve-component` reconciles a shipped component against current Figma (drift-heal) and additively extends it for a new need, behind a mandatory human gate — distinct from a fresh build, `figma-mend` (rebuild), and `figma-fix` (review bug).
11
13
 
12
14
  ## How it runs
13
15
 
@@ -67,6 +69,16 @@ Minimum viable config:
67
69
 
68
70
  Android projects swap the `build` section to `{ "gradleModule": ":components:button", "variant": "debug" }` and set `project.platform` to `"android"`.
69
71
 
72
+ **Optional `ui` block — UI interaction systems.** Consumed by the `figma-navigation` / `figma-overlays` / `figma-bottom-sheets` skills and Phase 4 review. Omit it (or set `mode: "native"`) and the pipeline generates stock SwiftUI (`NavigationStack`, `.alert`/`.sheet(item:)`, `.sheet`+`presentationDetents`). Set `mode: "custom"` to route to a project-supplied system by type name:
73
+
74
+ ```json
75
+ "ui": {
76
+ "navigationSystem": { "mode": "custom", "router": "AppRouter", "routeEnum": "AppRoute", "sceneType": "SceneContent" },
77
+ "overlaySystem": { "mode": "custom", "center": "OverlayCenter" },
78
+ "sheetSystem": { "mode": "custom", "brandedModifier": "bottomSheet", "detentType": "SheetDetentType" }
79
+ }
80
+ ```
81
+
70
82
  ## The issue → Jira → wiki triad
71
83
 
72
84
  When a GitHub issue triggers the pipeline and has no Jira link:
package/docs/features.md CHANGED
@@ -238,6 +238,8 @@ Pipeline learns behavioral signals (feedback corrections, project constraints, e
238
238
 
239
239
  Full component generation pipeline from a Figma URL. Generates `{Name}Configuration.swift`, `{Name}View.swift`, `{Name}+Modifiers.swift`, `{Name}.figma.swift`, `FIGMA.md` with variant matrix. 14-item pre-commit checklist covers design tokens, accessibility, tests, Code Connect.
240
240
 
241
+ Cross-cutting integration skills feed Phase 3D detection + Phase 4B implementation when the design triggers them — content: `figma-form-integration`, `figma-price-integration`, `figma-ui-patterns`; interaction: `figma-navigation`, `figma-overlays`, `figma-bottom-sheets`. Each is **native-SwiftUI-first** and reads project specifics (token namespaces, component paths, and UI systems) from `figma-config` — including the optional `ui.navigationSystem` / `ui.overlaySystem` / `ui.sheetSystem` hooks (absent → stock SwiftUI). So the same capabilities work on any SwiftUI codebase, not just one app. `figma-evolve-component` reconciles an existing component against current Figma (drift-heal) and additively extends it, behind a human gate.
242
+
241
243
  ### UI Bug Hunter + Audits
242
244
 
243
245
  Automated visual testing and compliance audits via direct Bash (no MCP server dependency):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmerterden/multi-agent-pipeline",
3
- "version": "10.2.0",
3
+ "version": "10.3.0",
4
4
  "description": "8-phase AI development pipeline with full orchestration on Claude Code, Copilot CLI, Cursor, Antigravity, and VS Code Copilot Chat. Analysis, planning, TDD, CLI-aware parallel review with consensus surfacing + Opus triage, default-FAIL evidence gates, secret + intent guards, per-phase cost ledger, persistent learnings memory, wiki generation, commit automation. Token-preserving uninstall.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -16,7 +16,9 @@ Platform-specific skills for generating Jetpack Compose components from Figma de
16
16
 
17
17
  ## What's NOT here (yet)
18
18
 
19
- The platform-**agnostic** skills - `figma-commit`, `figma-iterate`, `figma-issue`, `figma-review`, `figma-setup`, `figma-validate`, `figma-utility`, `figma-fix`, `figma-mend`, `figma-ui-patterns`, `figma-remote-mcp-auth`, `figma-cli-*`, `performance-*`, etc. - live under [`pipeline/skills/figma-common/`](../figma-common/) (WS-7b ✓). Both iOS and Android orchestrators dispatch there without duplication.
19
+ The platform-**agnostic** skills - `figma-commit`, `figma-iterate`, `figma-issue`, `figma-review`, `figma-setup`, `figma-validate`, `figma-utility`, `figma-fix`, `figma-mend`, `figma-evolve-component`, `figma-ui-patterns`, `figma-form-integration`, `figma-price-integration`, `figma-navigation`, `figma-overlays`, `figma-bottom-sheets`, `figma-remote-mcp-auth`, `figma-cli-*`, `performance-*`, etc. - live under [`pipeline/skills/figma-common/`](../figma-common/) (WS-7b ✓). Both iOS and Android orchestrators dispatch there without duplication.
20
+
21
+ The cross-cutting **integration adapters** (`figma-form-integration`, `figma-price-integration`, `figma-navigation`, `figma-overlays`, `figma-bottom-sheets`) carry both an iOS (SwiftUI) and an Android (Jetpack Compose) section - the Android orchestrator uses the Compose section; the same emit-intent / caller-owns rules and the `figma-config` `ui.*` hooks apply on both platforms.
20
22
 
21
23
  ## Routing
22
24
 
@@ -39,7 +39,7 @@ Shared skills that both `pipeline/skills/figma-ios/` (SwiftUI) and `pipeline/ski
39
39
  - `figma-form-integration` - Form protocol adapter (shared UX)
40
40
  - `figma-price-integration` - Price protocol adapter (shared UX)
41
41
 
42
- **Cross-cutting interaction adapters (native-SwiftUI-first; project system via `figma-config` `ui.*`):**
42
+ **Cross-cutting interaction adapters (dual-platform: iOS SwiftUI + Android Jetpack Compose; native-first; project system via `figma-config` `ui.*`):**
43
43
 
44
44
  - `figma-navigation` - Navigation pattern: headless Output vs self-route; native `NavigationStack` or `ui.navigationSystem`
45
45
  - `figma-overlays` - Overlay pattern: toast/HUD/alert/data-modal; native `.alert`/`.sheet(item:)` or `ui.overlaySystem`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: figma-bottom-sheets
3
- description: "Bottom-sheet / detent pattern integration for Figma-to-SwiftUI components — half-sheets, drawers, pinned bottom CTAs, resizable pull-up panels, content-sized sheets, and multi-step in-sheet flows. Native SwiftUI (.sheet + presentationDetents/dragIndicator/backgroundInteraction) by default; an optional project-supplied detent-sheet system when figma-config declares one. Reach for this when a design shows a bottom sheet, half-sheet, bottom drawer, snap points, a sticky footer button, or a wizard-in-a-sheet."
3
+ description: "Bottom-sheet / detent pattern integration for Figma-to-UI components (iOS SwiftUI + Android Jetpack Compose) — half-sheets, drawers, pinned bottom CTAs, resizable pull-up panels, content-sized sheets, and multi-step in-sheet flows. Native by default (SwiftUI .sheet+presentationDetents; Compose ModalBottomSheet + rememberModalBottomSheetState); an optional project-supplied detent-sheet system when figma-config declares one. Reach for this when a design shows a bottom sheet, half-sheet, bottom drawer, snap points, a sticky footer button, or a wizard-in-a-sheet."
4
4
  user-invocable: true
5
5
  allowed-tools: Read, Glob, Grep
6
6
  status: wip-v5
@@ -74,7 +74,7 @@ When `figma-config.json` declares:
74
74
  "headlessModifiers": ["modalSheet", "dockedSheet", "expandableSheet", "detentNavigationSheet"],
75
75
  "detentType": "SheetDetentType", // detent enum
76
76
  "backdropType": "Backdrop", // backdrop enum
77
- "cornerRadiusToken": ".Radius.n12" // branded top-corner token
77
+ "cornerRadiusToken": ".CornerRadius.radius12" // example: your branded top-corner radius token
78
78
  }}
79
79
  ```
80
80
  apply the SAME rules via the project's engine (names from config, do NOT hardcode):
@@ -86,6 +86,21 @@ apply the SAME rules via the project's engine (names from config, do NOT hardcod
86
86
 
87
87
  If `mode` is absent or `native`, use the native-first section.
88
88
 
89
+ ## Android (Jetpack Compose)
90
+
91
+ Same rules, Compose vocabulary. Default is Material3; a project sheet engine is used only when `ui.sheetSystem.mode == "custom"`.
92
+
93
+ - **Half / expandable / drawer:** `ModalBottomSheet(onDismissRequest = { showSheet = false }, sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = false)) { Content() }`. The caller owns `var showSheet by remember` and the `SheetState`; the Figma-authored composable is the **content**.
94
+ - **Detents / snap points:** Material3 exposes partial + expanded (`skipPartiallyExpanded`, `SheetValue`); for finer snap points use an `AnchoredDraggable`-based sheet. Don't hard-code pixel heights - derive from content / fractions.
95
+ - **Content-sized:** let the content wrap-height; the sheet grows to content then scrolls (inner `Column`/`LazyColumn` with `verticalScroll`).
96
+ - **Pinned bottom CTA (part of the screen, not a sheet):** `Scaffold(bottomBar = { CTA() })` - reserves space, screen stays interactive. Not a `ModalBottomSheet`.
97
+ - **Multi-step in-sheet flow:** host a nested `NavHost` (or step state) inside the sheet content, resizing per step; each step is a headless composable emitting events (see `/figma-navigation`).
98
+ - **Overlays over the sheet:** show `Snackbar`/`AlertDialog` scoped to the sheet content (`/figma-overlays`), not the root.
99
+
100
+ **Config hook:** `ui.sheetSystem.mode == "custom"` → use the project's `{brandedModifier}` / headless composables + `{detentType}` / `{backdropType}` and radius from `{cornerRadiusToken}`, resolved from config.
101
+
102
+ Corner radius / grabber / insets from tokens (`MaterialTheme.shapes` / figma-config), not literals.
103
+
89
104
  ---
90
105
 
91
106
  ## Integration with Phase 3D
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: figma-evolve-component
3
- description: "On-demand reconcile-and-extend for an ALREADY-implemented SwiftUI component. Heals drift from current Figma (stale variants/props/structure, design-token drift, a detached/overridden deviation) AND extends the component non-destructively to cover a need the design can't express — always behind a mandatory human gate. Reach for this when an existing component drifted from its current design or doesn't cover a need. Not for building from scratch (figma-to-swiftui / figma-mend) or fixing one review bug (figma-fix)."
3
+ description: "On-demand reconcile-and-extend for an ALREADY-implemented UI component (iOS SwiftUI or Android Jetpack Compose). Heals drift from current Figma (stale variants/props/structure, design-token drift, a detached/overridden deviation) AND extends the component non-destructively to cover a need the design can't express — always behind a mandatory human gate. Reach for this when an existing component drifted from its current design or doesn't cover a need. Not for building from scratch (figma-to-component / figma-mend) or fixing one review bug (figma-fix)."
4
4
  user-invocable: true
5
5
  argument-hint: <component | figma-url> [--report-only]
6
6
  allowed-tools: Task, Read, Glob, Grep, Bash, Edit, Write, AskUserQuestion
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: figma-navigation
3
- description: "Navigation pattern integration for Figma-to-SwiftUI components. Guides how a component reaches other screens, tabs, or deep links without coupling to a router — headless screens that emit a typed Output, native NavigationStack/navigationDestination by default, an optional project-supplied navigation system (router + route enum + scene container) when figma-config declares one. Reach for this when a design embeds a tab bar, nav bar / back button, a push/detail affordance, or a deep-link entry point."
3
+ description: "Navigation pattern integration for Figma-to-UI components (iOS SwiftUI + Android Jetpack Compose). Guides how a component reaches other screens, tabs, or deep links without coupling to a router — headless screens that emit a typed Output/event, native NavigationStack (iOS) / Navigation Compose (Android) by default, an optional project-supplied navigation system when figma-config declares one. Reach for this when a design embeds a tab bar, nav bar / back button, a push/detail affordance, or a deep-link entry point."
4
4
  user-invocable: true
5
5
  allowed-tools: Read, Glob, Grep
6
6
  status: wip-v5
@@ -89,6 +89,20 @@ then apply the SAME rules, but express them in the project's vocabulary resolved
89
89
 
90
90
  If `mode` is absent or `native`, use the native-first section above. Resolve types via `tools/resource-utility` / repo grep only when the config points at real files.
91
91
 
92
+ ## Android (Jetpack Compose)
93
+
94
+ Same rules, Compose vocabulary. Default is stock **Navigation Compose**; a project system is used only when `ui.navigationSystem.mode == "custom"`.
95
+
96
+ - **Push/pop within a flow:** caller owns `NavHostController` + `NavHost`; the composable emits events via lambdas (`onSelect: (Id) -> Unit`), never calls `navController.navigate(...)` itself. Prefer type-safe routes (`@Serializable` route objects, `navigation-compose` 2.8+).
97
+ - **Back:** system back / `navController.popBackStack()`; intercept only with `BackHandler { }` when the screen owns the gesture. A custom top-bar back emits `onBack` / calls the caller's pop.
98
+ - **Tabs:** state-hoisted selection (`var selected by rememberSaveable`) bound to a `NavigationBar`; the composable reads/writes the hoisted state, never a global.
99
+ - **Deep links:** `navDeepLink { uriPattern = ... }` on the destination, or the caller parses the URI → route and applies it to the same `NavController`. The composable never parses URIs.
100
+ - **Present another screen modally:** caller-owned `var showX by remember` → `Dialog`/`ModalBottomSheet` (see `/figma-bottom-sheets`).
101
+
102
+ **Config hook:** `ui.navigationSystem.mode == "custom"` → use the project's `{router}` / `{routeEnum}` (and a screen container if `{sceneType}` is set) resolved from config; cross-graph jumps go through the DI'd `{navigatorProtocol}`.
103
+
104
+ Tokens/theme via `MaterialTheme` + the project's figma-config token patterns.
105
+
92
106
  ---
93
107
 
94
108
  ## Integration with Phase 3D
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: figma-overlays
3
- description: "Overlay pattern integration for Figma-to-SwiftUI components — toasts/snackbars, blocking loading HUDs, alert dialogs, and data-driven modal cards presented from anywhere without the component owning presentation. Native SwiftUI (.alert/.confirmationDialog/.overlay + a ref-counted loading + data-driven .sheet) by default; an optional project-supplied overlay center when figma-config declares one. Reach for this when a design shows a toast/snackbar, a spinner/HUD, an alert, or a modal card, or when giving post-action feedback (saved/deleted/error) from a view model."
3
+ description: "Overlay pattern integration for Figma-to-UI components (iOS SwiftUI + Android Jetpack Compose) — toasts/snackbars, blocking loading HUDs, alert dialogs, and data-driven modal cards presented from anywhere without the component owning presentation. Native by default (SwiftUI .alert/.confirmationDialog/.overlay + ref-counted loading + .sheet; Compose Snackbar/AlertDialog/Dialog + state-driven loading); an optional project-supplied overlay center when figma-config declares one. Reach for this when a design shows a toast/snackbar, a spinner/HUD, an alert, or a modal card, or when giving post-action feedback (saved/deleted/error) from a view model."
4
4
  user-invocable: true
5
5
  allowed-tools: Read, Glob, Grep
6
6
  status: wip-v5
@@ -81,6 +81,20 @@ apply the SAME rules via the project's center (names from config, do NOT hardcod
81
81
 
82
82
  If `mode` is absent or `native`, use the native-first section.
83
83
 
84
+ ## Android (Jetpack Compose)
85
+
86
+ Same rules, Compose vocabulary. Default is stock Compose/Material3; a project overlay system is used only when `ui.overlaySystem.mode == "custom"`.
87
+
88
+ - **Toast/snackbar:** `SnackbarHostState` hoisted at a `Scaffold(snackbarHost = { SnackbarHost(state) })`; the composable emits an intent and the caller calls `snackbarHostState.showSnackbar(...)` from a coroutine. (Prefer an in-app `Snackbar` over the platform `android.widget.Toast`.)
89
+ - **Loading:** a state-driven overlay — `if (uiState.isLoading) Box(Modifier.fillMaxSize()) { CircularProgressIndicator() }`; back the flag with a ref-count in the ViewModel + a min-show so it doesn't flash.
90
+ - **Alert:** `AlertDialog(onDismissRequest, confirmButton, dismissButton)` — data-driven from state, proper confirm/dismiss roles.
91
+ - **Modal card:** data-driven `Dialog { }` / `ModalBottomSheet { }` where a host maps the request value → a Material component (no arbitrary erased content passed through a "center").
92
+ - **From a ViewModel:** expose events (`SharedFlow`/`Channel`) or state; the composable collects and shows the overlay. Keep the VM UI-framework-free.
93
+
94
+ **Config hook:** `ui.overlaySystem.mode == "custom"` → inject/collect the project's `{center}` and render its Material host; keep it data-only.
95
+
96
+ Rich/interactive content stays a local composable (`Dialog`/`ModalBottomSheet` declared at the owning composable), not routed through a center.
97
+
84
98
  ---
85
99
 
86
100
  ## Integration with Phase 3D
@@ -35,7 +35,7 @@ A self-contained native-SwiftUI recipe (no external package):
35
35
 
36
36
  1. **Overlay, don't replace.** Draw the stroke in an `.overlay` of a `RoundedRectangle` so the host keeps its own background/fill.
37
37
  2. **Angular gradient stroked along the shape.** `RoundedRectangle(cornerRadius:).stroke(AngularGradient(...), lineWidth:)`. The angular gradient's rotation is driven by an animated angle so the bright spot travels the perimeter.
38
- 3. **Animatable progress 0→1.** Rotate the gradient with `.rotationEffect(.degrees(360 * progress))` on a masked stroke, or animate the `AngularGradient`'s `angle`. Start in `.onAppear` with `withAnimation(.linear(duration:).repeatForever(autoreverses:false)) { progress = 1 }`.
38
+ 3. **Animatable progress 0→1 drives the gradient's `angle`.** Animate the `AngularGradient(angle:)` on a **static** shape — `.degrees(360 * progress)` so only the bright band travels. Do NOT `.rotationEffect` the stroked shape (that spins the whole rounded-rect). Start in `.onAppear` with `withAnimation(.linear(duration:).repeatForever(autoreverses:false)) { progress = 1 }`.
39
39
  4. **Halo shape** = the gradient stop distribution (a bright band with faded shoulders); keep the fade symmetric for the "snake" read.
40
40
  5. **Static shortcut.** `isAnimated == false` → render the same stroke frozen (or a single-color stroke).
41
41
 
@@ -65,15 +65,19 @@ struct AnimatedGradientBorder: ViewModifier {
65
65
  @State private var progress: CGFloat = 0
66
66
 
67
67
  func body(content: Content) -> some View {
68
+ // Animate the AngularGradient's `angle` on a STATIC shape — the bright
69
+ // band sweeps the perimeter. (Do NOT rotationEffect the shape: that spins
70
+ // the whole rounded-rect, corners and all.)
68
71
  content.overlay(
69
72
  RoundedRectangle(cornerRadius: cornerRadius)
70
73
  .stroke(
71
- AngularGradient(gradient: Gradient(colors: colors + [colors.first ?? .clear]),
72
- center: .center),
74
+ AngularGradient(
75
+ gradient: Gradient(colors: colors + [colors.first ?? .clear]),
76
+ center: .center,
77
+ angle: .degrees(isAnimated ? 360 * Double(progress) : 0)
78
+ ),
73
79
  lineWidth: lineWidth
74
80
  )
75
- .rotationEffect(.degrees(isAnimated ? 360 * Double(progress) : 0))
76
- .mask(RoundedRectangle(cornerRadius: cornerRadius).stroke(lineWidth: lineWidth))
77
81
  )
78
82
  .onAppear {
79
83
  guard isAnimated else { return }