@mmerterden/multi-agent-pipeline 10.0.6 → 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.
Files changed (53) hide show
  1. package/CHANGELOG.md +143 -0
  2. package/README.md +5 -2
  3. package/docs/FIGMA_PIPELINE.md +12 -0
  4. package/docs/features.md +2 -0
  5. package/package.json +1 -1
  6. package/pipeline/agents/android-architect.md +3 -3
  7. package/pipeline/agents/backend-architect.md +3 -3
  8. package/pipeline/agents/code-reviewer.md +4 -4
  9. package/pipeline/agents/ios-architect.md +3 -3
  10. package/pipeline/agents/security-auditor.md +3 -3
  11. package/pipeline/commands/multi-agent/dev-autopilot.md +3 -3
  12. package/pipeline/commands/multi-agent/dev-local-autopilot.md +2 -2
  13. package/pipeline/commands/multi-agent/dev-local.md +3 -3
  14. package/pipeline/commands/multi-agent/dev.md +9 -9
  15. package/pipeline/commands/multi-agent/help.md +10 -10
  16. package/pipeline/commands/multi-agent/refs/channels/jira.md +1 -0
  17. package/pipeline/commands/multi-agent/refs/cross-cli-contract.md +7 -3
  18. package/pipeline/commands/multi-agent/refs/features/model-fallback.md +36 -18
  19. package/pipeline/commands/multi-agent/refs/phases/operations.md +15 -5
  20. package/pipeline/commands/multi-agent/refs/phases/phase-0-init.md +16 -2
  21. package/pipeline/commands/multi-agent/refs/phases/phase-1-analysis.md +8 -3
  22. package/pipeline/commands/multi-agent/refs/phases/phase-2-planning.md +1 -1
  23. package/pipeline/commands/multi-agent/refs/phases/phase-4-review.md +30 -8
  24. package/pipeline/commands/multi-agent/resume.md +4 -1
  25. package/pipeline/commands/multi-agent.md +5 -5
  26. package/pipeline/lib/fetch-confluence.sh +2 -2
  27. package/pipeline/lib/fetch-crashlytics.sh +2 -2
  28. package/pipeline/lib/fetch-fortify.sh +1 -1
  29. package/pipeline/lib/fetch-swagger.sh +1 -1
  30. package/pipeline/lib/figma-screenshot.sh +2 -2
  31. package/pipeline/preferences-template.json +8 -1
  32. package/pipeline/schemas/agent-state.schema.json +8 -0
  33. package/pipeline/schemas/figma-project-config.schema.json +39 -0
  34. package/pipeline/schemas/prefs.schema.json +3 -3
  35. package/pipeline/scripts/cost-table.json +0 -6
  36. package/pipeline/scripts/fixtures/install-layout.tsv +2 -2
  37. package/pipeline/scripts/phase-tracker.sh +7 -0
  38. package/pipeline/scripts/smoke-model-fallback.sh +20 -12
  39. package/pipeline/scripts/validate-state.mjs +108 -0
  40. package/pipeline/scripts/write-state.mjs +15 -4
  41. package/pipeline/skills/figma-android/README.md +3 -1
  42. package/pipeline/skills/figma-common/README.md +8 -1
  43. package/pipeline/skills/figma-common/figma-bottom-sheets/SKILL.md +152 -0
  44. package/pipeline/skills/figma-common/figma-evolve-component/SKILL.md +61 -0
  45. package/pipeline/skills/figma-common/figma-navigation/SKILL.md +156 -0
  46. package/pipeline/skills/figma-common/figma-overlays/SKILL.md +142 -0
  47. package/pipeline/skills/figma-common/figma-ui-patterns/SKILL.md +1 -0
  48. package/pipeline/skills/figma-common/figma-ui-patterns/patterns/animated-gradient-border.md +116 -0
  49. package/pipeline/skills/figma-ios/figma-to-component/SKILL.md +15 -0
  50. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3d-patterns.md +31 -0
  51. package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4b-view.md +10 -0
  52. package/pipeline/skills/figma-ios/figma-to-component/reference/accessibility.md +55 -1
  53. package/pipeline/skills/figma-ios/figma-to-component/reference/orchestrator-discipline.md +1 -1
@@ -0,0 +1,142 @@
1
+ ---
2
+ name: figma-overlays
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
+ user-invocable: true
5
+ allowed-tools: Read, Glob, Grep
6
+ status: wip-v5
7
+ sourced-from: upstream/figma-overlays
8
+ ---
9
+
10
+ # /figma-overlays - Overlay Pattern Integration
11
+
12
+ ## Purpose
13
+
14
+ Consumed by the figma-to-swiftui pipeline (Phase 3D detection, Phase 4B view) and available ad-hoc, this skill defines how transient/blocking UI is presented **without a component owning the presentation surface**. Overlays counterpart of `/figma-form-integration` / `/figma-price-integration`: native-SwiftUI default + a config-driven hook for projects that ship an overlay center.
15
+
16
+ Four channels: **toast** (transient, non-blocking), **loading** (blocking HUD), **alert** (blocking dialog), **modal** (data-driven card). Rich/interactive content is a local view-modifier, never routed as an erased view.
17
+
18
+ **Generic by design.** Default is stock SwiftUI. If `figma-config.json` declares `ui.overlaySystem`, route through that DI'd center by name instead - same rules, project vocabulary.
19
+
20
+ ---
21
+
22
+ ## Strict Rules (non-negotiable)
23
+
24
+ 1. **A component does not present its own app-level overlays.** A reusable component emits an intent (`onSaved`, `output: .removed`) or exposes state; the **caller** (scene/VM/coordinator) shows the toast/alert/modal. A component owning a global toast/alert makes it un-reusable and double-fires when composed.
25
+ 2. **Overlay requests are pure data, not erased views.** Toast/alert/modal content is a value (title, style, actions), mapped to a design-system view at the host. **Never** pass `AnyView` through an overlay center. Rich/interactive content uses a **local** `.modal/.sheet { }` view modifier declared at the owning view.
26
+ 3. **Loading is ref-counted with a min-show.** Pair every begin with an end (or use a `withLoading { }` wrapper); enforce a ~300ms minimum visible so it doesn't flash. Never leave a HUD un-ended on an error path.
27
+ 4. **Presentable from non-view code.** Post-action feedback originates in a VM/coordinator; the overlay API must be callable there (a DI'd center or a bound state), not only from SwiftUI `body`.
28
+ 5. **Native SwiftUI is the default; a custom center is opt-in via config.** Only use a project overlay center when `ui.overlaySystem.mode == "custom"`.
29
+
30
+ ---
31
+
32
+ ## When to Invoke This Skill
33
+
34
+ 1. **Phase 3D - overlay affordance/pattern:** the design shows a toast/snackbar, a spinner/HUD or blocking dim, an alert dialog (1-3 buttons), or a data-driven modal card; or a component action needs post-action feedback.
35
+ 2. **Phase 4B - View:** the view triggers feedback after an action, or renders a dim/HUD/alert/modal.
36
+ 3. **Ad-hoc:** "show a confirmation", "flash a message", "block the screen while loading", "pop a dialog", "feedback after save".
37
+
38
+ **Not an overlay (guards):**
39
+ - A bottom sheet / half-sheet / drawer *surface* → `/figma-bottom-sheets`.
40
+ - Screen-to-screen routing / tabs / deep links → `/figma-navigation`.
41
+ - An inline banner that is part of the component's own layout (not app-level, not dismissible-over-content) → plain component content.
42
+
43
+ ---
44
+
45
+ ## Native-first architecture (default)
46
+
47
+ ```
48
+ Scene / VM (caller)
49
+ | owns overlay state, calls after an action
50
+ v
51
+ SwiftUI presentation modifiers on the scene root:
52
+ toast → .overlay(alignment:) of a transient message view + auto-dismiss task
53
+ loading → a ref-counted @Observable LoadingState → dim + ProgressView, min-show
54
+ alert → .alert(_:isPresented:) / .confirmationDialog (roles: cancel/destructive/default)
55
+ modal → .sheet(item:) driven by an Identifiable data value → host maps data → card
56
+ rich → local .sheet/.fullScreenCover { InteractiveContent() } at the owning view
57
+ ```
58
+
59
+ - **Toast:** newest-replaces (single visible), `hug`≈2s / `fill`≈5s auto-dismiss; anchor above any bottom bar. Model as a small value + an `.overlay`.
60
+ - **Loading:** an observable counter (`begin`/`end`, `withLoading`) with min-show; dim + `ProgressView`.
61
+ - **Alert:** native `.alert` / `.confirmationDialog` with proper button roles; data-driven.
62
+ - **Modal card:** `.sheet(item:)` with an `Identifiable` request value; the host builds the design-system card from data (no `AnyView`).
63
+ - **Over a bottom sheet:** mount the toast/HUD host on the sheet's own content (a root-level host sits *behind* a presented sheet).
64
+
65
+ Tokens per `reference/ui` + figma-config token patterns; no literals.
66
+
67
+ ## Config hook - project-supplied overlay center
68
+
69
+ When `figma-config.json` declares:
70
+ ```jsonc
71
+ "ui": { "overlaySystem": {
72
+ "mode": "custom",
73
+ "center": "OverlayCenter", // DI'd protocol callable from VM/coordinator
74
+ "surfaces": ["root", "currentScene"] // independent render surfaces (optional)
75
+ }}
76
+ ```
77
+ apply the SAME rules via the project's center (names from config, do NOT hardcode):
78
+ - Inject `any {center}` (with a Noop default for previews/tests); call `.toast/.withLoading/.alert/.modal` from the VM.
79
+ - Keep the center **UI-framework-free**: it stores data; a host maps data → design-system component. If you reach for `AnyView`, use a local `.modal { }` view modifier instead.
80
+ - Choose the surface from the caller's context (`root` vs `currentScene`); over-a-sheet fires on `currentScene`.
81
+
82
+ If `mode` is absent or `native`, use the native-first section.
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
+
98
+ ---
99
+
100
+ ## Integration with Phase 3D
101
+
102
+ Record overlays in `04b_component_architecture.md` as **caller intents**, not component-owned UI:
103
+
104
+ ```markdown
105
+ ## Overlays
106
+ | Trigger | Channel | Owner | Note |
107
+ |---|---|---|---|
108
+ | Row "Sil" tapped | alert (confirm) | caller | component emits .removeRequested(id) |
109
+ | Save succeeded | toast (success) | caller VM | component emits .saved |
110
+ | Async fetch | loading HUD | caller VM | withLoading around the call |
111
+ ```
112
+
113
+ Property classification addition:
114
+
115
+ | Classification | When | Swift Pattern |
116
+ |---|---|---|
117
+ | Overlay intent | Component action needs app-level feedback/confirm | `output`/`onX` closure on the View (caller shows the overlay) |
118
+ | Local rich modal | Interactive content owned by this view | local `.sheet/.modal { }` at the owning view, caller state |
119
+
120
+ ## Integration with Phase 4B
121
+ - Never present an app-level toast/alert/modal from inside a reusable component - emit the intent.
122
+ - Use native `.alert`/`.confirmationDialog` for 1-3 button dialogs; `.sheet(item:)` for data modal cards.
123
+ - Ref-count loading; guarantee `end` on all paths; min-show to avoid flash.
124
+ - If `ui.overlaySystem.mode == custom`, inject and call `{center}`; keep it data-only.
125
+ - Verify on the simulator: toast above the bottom bar, HUD min-show, alert roles, data modal, overlays over a sheet.
126
+
127
+ ---
128
+
129
+ ## When NOT to Use Overlay Integration
130
+ - Bottom-sheet surface → `/figma-bottom-sheets`. Routing/tabs/deep links → `/figma-navigation`.
131
+ - A static inline banner that is part of the component layout → plain content.
132
+ - Form validation error shown inline on a field → `/figma-form-integration` (field state), not an overlay.
133
+
134
+ ## Checklist - Before Leaving the Skill
135
+ ```
136
+ [ ] Reusable component emits intent/state; caller owns the overlay (no self-presentation)
137
+ [ ] Overlay content is data, not AnyView; rich content uses a local .sheet/.modal { }
138
+ [ ] Loading is ref-counted with min-show; end guaranteed on error paths
139
+ [ ] Alerts use native roles (cancel/destructive/default) unless a design card is required
140
+ [ ] Default native SwiftUI; custom center only when figma-config ui.overlaySystem.mode == custom
141
+ [ ] Over-a-sheet feedback mounts on the sheet's content surface, not the root
142
+ ```
@@ -94,6 +94,7 @@ Bullet list of other slugs in this skill (or in `figma-price-integration`, `core
94
94
 
95
95
  | Slug | Pattern | Status |
96
96
  |------|---------|--------|
97
+ | `animated-gradient-border` | Snake/halo gradient stroke crawling the perimeter (promo/featured/premium cards) - angular-gradient stroke + animatable progress, corner-radius match, Reduce-Motion gate. Native-SwiftUI recipe; uses a project modifier if one ships. | Ready |
97
98
  | `carousel-banner` | Auto-advancing paged banner - single item visible, swipe/tap paging, `CarouselGroup` indicator, configurable interval + loop + pause-on-interaction. | Ready |
98
99
  | `scalloped-vstack` | Scalloped VStack (boarding-pass silhouette) - vertical card with semicircular edge cuts + dashed perforation line between segments. `CoreUI.ScallopedVStack`. | Ready |
99
100
 
@@ -0,0 +1,116 @@
1
+ # Animated Gradient Border (Snake / Halo Border)
2
+
3
+ ## When to Use
4
+
5
+ A component's frame has a stroked border whose color fades around the perimeter as if a **snake / halo of gradient light crawls around the edge** — continuous, looping, usually 2–4pt wide, with a radius matching the host's corner radius. Figma signals: promo banners, premium/featured cards, upsell surfaces, "lit up" states of otherwise calm cards; a spec named "animated / glowing / gradient border" or an endless-loop prototype on the stroke.
6
+
7
+ **Not** this pattern when:
8
+ - The border is a single, *static* color → `.stroke(...)` / `.overlay(RoundedRectangle().stroke(...))` directly.
9
+ - The gradient is **on the fill**, not the stroke → that's an angular/linear gradient background.
10
+ - It's a **state-transition** pulse (red on error) → `.animation(...)` with color interpolation, not a crawling gradient.
11
+ - The stroke is **dashed/dotted** → different `StrokeStyle`; this pattern draws a solid stroke.
12
+
13
+ ## Minimal API
14
+
15
+ If the project already ships a gradient-border modifier (some design systems do — check `reference/ui` / the repo), use it and do NOT re-implement the math. Otherwise use this generic modifier shape:
16
+
17
+ ```swift
18
+ view
19
+ .background(fill)
20
+ .clipShape(RoundedRectangle(cornerRadius: radius))
21
+ .animatedGradientBorder(
22
+ colors: [start.opacity(0.6), middle, end.opacity(0.6)], // faded → solid → faded
23
+ lineWidth: 2,
24
+ cornerRadius: radius, // MUST match the host corner radius
25
+ isAnimated: !reduceMotion,
26
+ duration: 3.0
27
+ )
28
+ ```
29
+
30
+ **Parameter intent:** 3 colors read best (faded→solid→faded); `lineWidth` 1–3pt from tokens; `cornerRadius` must equal the host's; `isAnimated:false` renders a static stroke; `duration` 1.5 urgent / 3 calm / 5+ ambient.
31
+
32
+ ## Technique
33
+
34
+ A self-contained native-SwiftUI recipe (no external package):
35
+
36
+ 1. **Overlay, don't replace.** Draw the stroke in an `.overlay` of a `RoundedRectangle` so the host keeps its own background/fill.
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 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
+ 4. **Halo shape** = the gradient stop distribution (a bright band with faded shoulders); keep the fade symmetric for the "snake" read.
40
+ 5. **Static shortcut.** `isAnimated == false` → render the same stroke frozen (or a single-color stroke).
41
+
42
+ ## Gotchas
43
+
44
+ 1. **Corner-radius mismatch is the #1 bug** — the host `cornerRadius` and the border `cornerRadius` must be equal, or the stroke floats off the rounded path.
45
+ 2. **Apply the border after `.background`/`.clipShape`** so it overlays the current bounds and clips to the rounded shape.
46
+ 3. **Reduce Motion:** read `@Environment(\.accessibilityReduceMotion)` at the call site and pass `isAnimated: !reduceMotion`. A perpetually animating border must respect it.
47
+ 4. **Perf on long lists:** a forever-repeating animation per row costs frames and never pauses off-screen. Measure with Instruments before shipping it on `LazyVStack`/`List` rows.
48
+ 5. **`onAppear` restarts on every reappear** — don't rely on animation state surviving a re-mount.
49
+ 6. **`lineWidth` ≥ `cornerRadius`** produces blobby corners; keep `lineWidth < cornerRadius` for clean corners.
50
+ 7. **Use semantic tokens, not raw hex**, so the border follows light/dark + theme (`reference/ui` + figma-config token patterns).
51
+
52
+ ## Reference Snippet
53
+
54
+ Drop-in preview — pure SwiftUI, no external dependency:
55
+
56
+ ```swift
57
+ import SwiftUI
58
+
59
+ struct AnimatedGradientBorder: ViewModifier {
60
+ var colors: [Color]
61
+ var lineWidth: CGFloat = 2
62
+ var cornerRadius: CGFloat = 12
63
+ var isAnimated: Bool = true
64
+ var duration: Double = 3.0
65
+ @State private var progress: CGFloat = 0
66
+
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.)
71
+ content.overlay(
72
+ RoundedRectangle(cornerRadius: cornerRadius)
73
+ .stroke(
74
+ AngularGradient(
75
+ gradient: Gradient(colors: colors + [colors.first ?? .clear]),
76
+ center: .center,
77
+ angle: .degrees(isAnimated ? 360 * Double(progress) : 0)
78
+ ),
79
+ lineWidth: lineWidth
80
+ )
81
+ )
82
+ .onAppear {
83
+ guard isAnimated else { return }
84
+ withAnimation(.linear(duration: duration).repeatForever(autoreverses: false)) {
85
+ progress = 1
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ extension View {
92
+ func animatedGradientBorder(colors: [Color], lineWidth: CGFloat = 2,
93
+ cornerRadius: CGFloat = 12, isAnimated: Bool = true,
94
+ duration: Double = 3.0) -> some View {
95
+ modifier(AnimatedGradientBorder(colors: colors, lineWidth: lineWidth,
96
+ cornerRadius: cornerRadius, isAnimated: isAnimated,
97
+ duration: duration))
98
+ }
99
+ }
100
+
101
+ #Preview {
102
+ Text("Featured")
103
+ .padding(16)
104
+ .frame(maxWidth: .infinity, alignment: .leading)
105
+ .background(Color(.systemBackground))
106
+ .clipShape(RoundedRectangle(cornerRadius: 12))
107
+ .animatedGradientBorder(colors: [.blue.opacity(0.6), .blue, .blue.opacity(0.6)],
108
+ cornerRadius: 12)
109
+ .padding()
110
+ }
111
+ ```
112
+
113
+ ## Related Patterns
114
+
115
+ - `carousel-banner` — promo banners often pair an auto-advancing pager with this border on the active card.
116
+ - Price callouts — premium-price surfaces sit inside a card wearing this border; compose it at the card level, not on the price view (see `reference/ui` / `figma-price-integration`).
@@ -113,6 +113,21 @@ Bundled in `.instructions/figma/figma-to-swiftui/reference/`. Include relevant p
113
113
  | `reference/viewinspector.md` | 5A |
114
114
  | `reference/snapshot-testing.md` | 5D |
115
115
 
116
+ ## Complementary integration skills
117
+
118
+ Cross-cutting skills in `figma-common/` that feed Phase 3D / 4A / 4B when the design triggers them (see `reference/orchestrator-discipline.md` Rule 4). Native-SwiftUI-first; project specifics come from `figma-config` `ui.*` hooks.
119
+
120
+ | Skill | Phase 3D trigger | Purpose |
121
+ |-------|------------------|---------|
122
+ | `/figma-form-integration` | form-capable sub-components | FormField/FormSection modeling |
123
+ | `/figma-price-integration` | currency+amount / Price instance | PriceRepresentable modeling |
124
+ | `/figma-ui-patterns` | scroll/motion/shape signal | reusable technique recipes (incl. `animated-gradient-border`) |
125
+ | `/figma-navigation` | tab/back/push/deep-link affordance | headless Output; native NavigationStack or `ui.navigationSystem` |
126
+ | `/figma-overlays` | toast/HUD/alert/data-modal | caller-owned; native `.alert`/`.sheet(item:)` or `ui.overlaySystem` |
127
+ | `/figma-bottom-sheets` | sheet/drawer/pinned-CTA/multi-step | sheet content; native `.presentationDetents` or `ui.sheetSystem` |
128
+
129
+ Related workflow: `/figma-evolve-component` reconciles + extends an already-implemented component (drift-heal + additive extend, human-gated) - distinct from a fresh build here, `/figma-mend` (rebuild), and `/figma-fix` (review bug).
130
+
116
131
  ---
117
132
 
118
133
  ## Dispatch Procedure
@@ -278,6 +278,37 @@ Configuration then carries `public let section: ComponentName.Section` plus stat
278
278
 
279
279
  **Architecture output (atomic, 1 field):** Configuration (or View init) carries the FormField directly, e.g. `public let terms: CheckboxFormField`.
280
280
 
281
+ ### 1.5.4 Interaction Pattern Detection (navigation / overlays / bottom sheets)
282
+
283
+ Beyond content patterns, detect **interaction affordances** the component must expose without owning the presentation/routing. Each maps to a cross-cutting integration skill (native-SwiftUI default; a project-supplied system only when `figma-config` declares `ui.<system>`). The rule is the same in all three: the component emits a typed intent (Output closure) or takes a caller-owned binding - it never routes, never presents an app-level surface, never owns the sheet.
284
+
285
+ **Navigation pattern** → read `/figma-navigation` (`.instructions/figma/figma-navigation/SKILL.md`).
286
+ - **Detect:** a tab/section switcher that changes top-level content; a nav bar / header with a **back** or close affordance; a row/card variant implying a **push/detail** (chevron, "Detaylar", disclosure); a link/button whose label implies leaving the screen; a URL landing surface.
287
+ - **Architecture output:** a navigation **Output** (`output: (Output) -> Void` / `onSelect: (ID) -> Void`) OR a caller-owned `selection: Binding<Tab>` OR a plain deep-link entry param. Labels/icons are static (localized) Configuration copy; only the *intent* is a closure. Never a hardcoded `NavigationLink(destination:)`.
288
+ - **Guard:** an in-place segmented filter (no screen change) is a plain `Binding<Enum>`, not navigation.
289
+
290
+ **Overlay pattern** → read `/figma-overlays` (`.instructions/figma/figma-overlays/SKILL.md`).
291
+ - **Detect:** a toast/snackbar, a spinner/HUD or blocking dim, an alert dialog (1-3 buttons), a data-driven modal card; or a component action needing post-action feedback (saved/deleted/error).
292
+ - **Architecture output:** the component emits an **intent** (`.saved` / `.removeRequested(id)`); the caller (scene/VM) shows the toast/alert/modal. Overlay content is data, never `AnyView`; rich/interactive content is a local `.sheet/.modal { }` at the owning view. Loading is ref-counted with min-show.
293
+ - **Guard:** a static inline banner that is part of the component's own layout is plain content, not an overlay.
294
+
295
+ **Bottom-sheet pattern** → read `/figma-bottom-sheets` (`.instructions/figma/figma-bottom-sheets/SKILL.md`).
296
+ - **Detect:** the component is (or reveals) a bottom sheet / half-sheet / drawer, a **pinned bottom CTA** bar, a resizable pull-up panel, a **content-sized** sheet, or a **multi-step** in-sheet flow.
297
+ - **Architecture output:** the component is sheet **content**; the caller owns `isPresented`/`item` + detents. A pinned CTA is `.safeAreaInset(edge:.bottom)`, not a presented sheet. If the component *reveals* a child sheet, it emits the trigger intent and the caller presents.
298
+ - **Guard:** an inline expandable/disclosure within the page flow is plain component content.
299
+
300
+ **Property classification additions:**
301
+
302
+ | Classification | When | Swift Pattern |
303
+ |---|---|---|
304
+ | Navigation output | push/select/back affordance | `output`/`onSelect` closure on the View |
305
+ | Tab/selection binding | switches top-level content | `selection: Binding<Tab>` |
306
+ | Overlay intent | action needs app-level feedback/confirm | `output`/`onX` closure (caller shows overlay) |
307
+ | Sheet content | component lives inside a sheet | plain component; caller adds `.presentationDetents(...)` |
308
+ | Pinned CTA | sticky bottom action bar | `.safeAreaInset(edge:.bottom)` at the screen |
309
+
310
+ Record detected interaction patterns in `04b_component_architecture.md` under **Navigation / Overlays / Bottom Sheet** headings (see each skill's "Integration with Phase 3D" for the table shape).
311
+
281
312
  ### 1.5.2 Image Asset Classification
282
313
 
283
314
  Figma designs contain images that are either **static assets** (bundled in the app) or **dynamic content** (loaded remotely at runtime). Classify each image in the design context.
@@ -552,6 +552,16 @@ if isExpanded {
552
552
 
553
553
  Do NOT use `DisclosureGroup` - it has limited styling control. Build the expand/collapse manually for pixel-perfect Figma match.
554
554
 
555
+ ### 4.7.4 Interaction pattern rendering (navigation / overlays / bottom sheets)
556
+
557
+ When the architecture identifies an **interaction pattern** (section1.5.4 in Phase 3D), read the matching skill and follow its "Integration with Phase 4B". The component **emits intent / takes a caller-owned binding - it never routes, presents an app-level overlay, or owns the sheet surface.** Native SwiftUI is the default; use a project system only when `figma-config` declares `ui.<system>`.
558
+
559
+ - **Navigation** (`/figma-navigation`): emit a typed `output`/`onSelect` for push/detail/back; system back or `@Environment(\.dismiss)`; tab via a caller-owned `Binding`. Never hardcode `NavigationLink(destination: ConcreteScreen())` in a reusable component.
560
+ - **Overlays** (`/figma-overlays`): the component emits `.saved` / `.removeRequested(id)`; the caller shows the toast/alert/modal. Native `.alert`/`.confirmationDialog` for 1-3 button dialogs; `.sheet(item:)` for a data modal card; rich content via a local `.sheet/.modal { }`. Loading is ref-counted with min-show. No `AnyView` through a center.
561
+ - **Bottom sheets** (`/figma-bottom-sheets`): author the component as sheet **content**; the caller owns `isPresented`/`item` + `.presentationDetents([...])`. A pinned CTA is `.safeAreaInset(edge:.bottom)`, not a presented sheet. Multi-step = `NavigationStack` inside the sheet (native) or the project engine's nav sheet (custom).
562
+
563
+ If `figma-config` sets `ui.navigationSystem`/`ui.overlaySystem`/`ui.sheetSystem` to `custom`, use that project's types (router/route enum/scene container, overlay center, branded/headless sheet modifiers) resolved from config - do not hardcode their names.
564
+
555
565
  ### 4.8 @ViewBuilder for complex conditional views
556
566
 
557
567
  ```swift
@@ -1,6 +1,60 @@
1
1
  # ACCESSIBILITY INTEGRATION
2
2
 
3
- SwiftUI accessibility implementation guide.
3
+ SwiftUI accessibility implementation guide. **Minimalism first, then correctness** — decide *whether* to annotate before deciding *what*. The key-type names below (`LocalizedAccessibilityStringKey`, `UITestingIdentifierKey`) are the project's generated a11y/testing-id key types; a project without them uses runtime strings.
4
+
5
+ ---
6
+
7
+ ## Core principle — accessibility is for vision-impaired users
8
+
9
+ Every annotation must pass one test:
10
+
11
+ > **"Does a vision-impaired person need this information to navigate or use the app?"** YES → add it. NO → don't; it's noise.
12
+
13
+ Visual state (colors, gradients, tier badges) is information sighted users consume *visually*. A colored "Success"/"Elite"/"Classic" indicator with visible text is just the text; the color is decorative, not actionable. Do **not** generate a per-enum-variant a11y key for state the user already knows.
14
+
15
+ ## How VoiceOver already works (don't duplicate it)
16
+
17
+ VoiceOver reads the **accessibility tree**, which SwiftUI builds automatically:
18
+ - `Text("Hello")` is already read — `.accessibilityLabel("Hello")` / `.accessibilityAddTraits(.isStaticText)` are redundant.
19
+ - `Button("Save"){}` already reads "Save, Button" — label + trait are redundant.
20
+ - `Image(decorative:)` is already hidden.
21
+ - `.accessibilityElement(children: .combine)` merges an `HStack`/`VStack`'s child text into one stop — you do NOT then add `.accessibilityLabel`.
22
+ - `.accessibilityLabel` **overrides** (replaces child text), it does not supplement — harmful when the child text was already correct.
23
+
24
+ **The question for EVERY modifier: "what breaks if I remove this?"** If nothing breaks, the modifier should not exist.
25
+
26
+ ## Decision hierarchy (apply in order, STOP as soon as resolved)
27
+
28
+ 1. **Does it need ANY a11y work?** SwiftUI reads it correctly as-is → add NOTHING.
29
+ 2. **Decorative elements?** → `.accessibilityHidden(true)` on those only.
30
+ 3. **Group children?** → `.accessibilityElement(children: .combine)` — then STOP.
31
+ 4. **Combined text wrong/missing?** → ONLY then add `.accessibilityLabel`.
32
+ 5. **Interactive state VoiceOver can't infer** (custom toggle, not SwiftUI `Toggle`) → `.accessibilityValue`.
33
+ 6. **A trait SwiftUI can't infer** (custom tappable, not `Button`) → `.accessibilityAddTraits`.
34
+ 7. **Last resort** → author a localized a11y key.
35
+
36
+ Most components stop at step 2 or 3. Reaching step 4+ requires a written justification.
37
+
38
+ ## State-necessity decision tree (per variant/boolean)
39
+
40
+ 1. **Interactive?** (Toggle/Checkbox/Selection/Switch) → YES: generate state value keys. NO → 2.
41
+ 2. **Visible text already communicates the state?** → YES: skip (redundant). NO → 3.
42
+ 3. **Non-interactive display component?** (Badge/Tag/Chip/Status/Label) → YES: skip state keys; use `.accessibilityElement(children: .combine)` only, no `.accessibilityLabel`. NO → 4.
43
+ 4. **Trivially derivable from an enum at runtime?** → YES: use `state.localized`, skip the separate key. NO: author a key.
44
+
45
+ **Worked example — a non-interactive `LabelBadge` (Success | Warning | Error | …):** not interactive → visible text communicates intent → it's a display component ⇒ do NOT generate `LabelBadge.SuccessState` etc. Combine children; the badge text is read naturally.
46
+
47
+ ## Anti-patterns (never)
48
+
49
+ | Anti-pattern | Why wrong |
50
+ |---|---|
51
+ | `.isButton` on a `Button` / `.isStaticText` on a `Text` | SwiftUI already provides it |
52
+ | `.accessibilityRemoveTraits(.isButton)` with no Button | removing a trait that was never there |
53
+ | `.accessibilityLabel` duplicating visible text | overrides SwiftUI's correct reading with a string you now maintain |
54
+ | `.accessibilityLabel("\(text), \(state)")` on a non-interactive badge | the state name is a color cue, not information |
55
+ | A separate key per enum variant | key bloat for trivially translatable names |
56
+ | `.accessibilityHint` when the label is self-explanatory ("Close") | VoiceOver already prepends "double tap to" |
57
+ | Modifiers "just in case" | every modifier is cost with no value |
4
58
 
5
59
  ---
6
60
 
@@ -55,7 +55,7 @@ If you must write code directly (after 2+ retries), read the phase instruction f
55
55
 
56
56
  ## Rule 4: Skills Are Complementary, Not Dismissable
57
57
 
58
- `/figma-form-integration`, `/figma-ui-patterns`, `/figma-price-integration`, `/coreui-forms` - these skills feed INTO phases. They augment Phase 3D, Phase 4A, Phase 4B with domain-specific patterns.
58
+ `/figma-form-integration`, `/figma-ui-patterns`, `/figma-price-integration`, `/figma-navigation`, `/figma-overlays`, `/figma-bottom-sheets`, `/coreui-forms` - these skills feed INTO phases. They augment Phase 3D, Phase 4A, Phase 4B with cross-cutting patterns (content: forms/price/ui-patterns; interaction: navigation/overlays/bottom-sheets). Each is native-SwiftUI-first and reads project specifics from `figma-config` (`ui.*` hooks) - generic across SwiftUI projects, not tied to any one app.
59
59
 
60
60
  When the user says "check /figma-form-integration":
61
61
  - That is a directive to integrate, not a suggestion to evaluate.