@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.
- package/CHANGELOG.md +143 -0
- package/README.md +5 -2
- package/docs/FIGMA_PIPELINE.md +12 -0
- package/docs/features.md +2 -0
- package/package.json +1 -1
- package/pipeline/agents/android-architect.md +3 -3
- package/pipeline/agents/backend-architect.md +3 -3
- package/pipeline/agents/code-reviewer.md +4 -4
- package/pipeline/agents/ios-architect.md +3 -3
- package/pipeline/agents/security-auditor.md +3 -3
- package/pipeline/commands/multi-agent/dev-autopilot.md +3 -3
- package/pipeline/commands/multi-agent/dev-local-autopilot.md +2 -2
- package/pipeline/commands/multi-agent/dev-local.md +3 -3
- package/pipeline/commands/multi-agent/dev.md +9 -9
- package/pipeline/commands/multi-agent/help.md +10 -10
- package/pipeline/commands/multi-agent/refs/channels/jira.md +1 -0
- package/pipeline/commands/multi-agent/refs/cross-cli-contract.md +7 -3
- package/pipeline/commands/multi-agent/refs/features/model-fallback.md +36 -18
- package/pipeline/commands/multi-agent/refs/phases/operations.md +15 -5
- package/pipeline/commands/multi-agent/refs/phases/phase-0-init.md +16 -2
- package/pipeline/commands/multi-agent/refs/phases/phase-1-analysis.md +8 -3
- package/pipeline/commands/multi-agent/refs/phases/phase-2-planning.md +1 -1
- package/pipeline/commands/multi-agent/refs/phases/phase-4-review.md +30 -8
- package/pipeline/commands/multi-agent/resume.md +4 -1
- package/pipeline/commands/multi-agent.md +5 -5
- package/pipeline/lib/fetch-confluence.sh +2 -2
- package/pipeline/lib/fetch-crashlytics.sh +2 -2
- package/pipeline/lib/fetch-fortify.sh +1 -1
- package/pipeline/lib/fetch-swagger.sh +1 -1
- package/pipeline/lib/figma-screenshot.sh +2 -2
- package/pipeline/preferences-template.json +8 -1
- package/pipeline/schemas/agent-state.schema.json +8 -0
- package/pipeline/schemas/figma-project-config.schema.json +39 -0
- package/pipeline/schemas/prefs.schema.json +3 -3
- package/pipeline/scripts/cost-table.json +0 -6
- package/pipeline/scripts/fixtures/install-layout.tsv +2 -2
- package/pipeline/scripts/phase-tracker.sh +7 -0
- package/pipeline/scripts/smoke-model-fallback.sh +20 -12
- package/pipeline/scripts/validate-state.mjs +108 -0
- package/pipeline/scripts/write-state.mjs +15 -4
- package/pipeline/skills/figma-android/README.md +3 -1
- package/pipeline/skills/figma-common/README.md +8 -1
- package/pipeline/skills/figma-common/figma-bottom-sheets/SKILL.md +152 -0
- package/pipeline/skills/figma-common/figma-evolve-component/SKILL.md +61 -0
- package/pipeline/skills/figma-common/figma-navigation/SKILL.md +156 -0
- package/pipeline/skills/figma-common/figma-overlays/SKILL.md +142 -0
- package/pipeline/skills/figma-common/figma-ui-patterns/SKILL.md +1 -0
- package/pipeline/skills/figma-common/figma-ui-patterns/patterns/animated-gradient-border.md +116 -0
- package/pipeline/skills/figma-ios/figma-to-component/SKILL.md +15 -0
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3d-patterns.md +31 -0
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4b-view.md +10 -0
- package/pipeline/skills/figma-ios/figma-to-component/reference/accessibility.md +55 -1
- 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
|
|
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.
|