@mmerterden/multi-agent-pipeline 12.2.0 → 12.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.
@@ -10,7 +10,7 @@ description: "v3 canonical template for /multi-agent:analysis. 23 main sections
10
10
 
11
11
  > **Language**: This file is read as a system prompt. Prose stays English. Example tables and headings carry bilingual TR / EN scaffolds; the runtime renderer picks the right column per `outputLanguage`.
12
12
 
13
- > **Locked decisions that govern this template**: 1 (one feature per run), 2 (omission rule), 3 (citation discipline), 4 (forward-looking spec), 7 (humanizer punctuation policy), 8 (standards binding), 9 (per-platform output split), 11 (repo-evidence reuse-first), 12 (Gherkin), 13 (Goals + Non-Goals), 14 (SVG default), 15 (Files-to-Add tag), 16 (API response variants), 17 (screenshots embedded), 18 (all Figma variants), 19 (8-locale full coverage), 20 (References at bottom), 21 (platform-agnostic + Pass B), 22 (convention extraction), 23 (fallback risk), 24 (Pass B footnote mandatory), 28 (SwiftUI Preview block iOS), 29 (variant usage explicit), 30 (analysis self-contained pipeline-wide MCP forbidden outside analysis phase).
13
+ > **Locked decisions that govern this template**: the canonical numbered list lives in `pipeline/commands/multi-agent/analysis/SKILL.md` (cite decisions by label, not by a number duplicated here, to avoid drift). The template's structure is shaped chiefly by: one-feature-per-run, section omission rule, citation discipline (incl. annotation-as-copy), forward-looking spec, humanizer punctuation policy, standards binding, per-platform output split, repo-evidence reuse-first, Figma 3-tier access, Gherkin user stories, Goals + Non-Goals paired, SVG default, Files-to-Add tag, API response variants exhaustive, screenshots embedded, all Figma variants drilled, localization mode (ownership-aware), References at bottom, platform-agnostic + Pass B render, convention extraction, Pass B footnote mandatory, Lite mode, SwiftUI Preview block (iOS), variant usage explicit, analysis self-contained (no MCP downstream), and business-rule to acceptance-criterion to test traceability.
14
14
 
15
15
  ## Mode selection - Full vs Lite
16
16
 
@@ -45,6 +45,8 @@ feature: <FeatureName>
45
45
  platform: ios | android | backend | frontend
46
46
  language: tr | en
47
47
  mode: full | lite
48
+ ui_tests: true | false
49
+ a11y_depth: basic | full
48
50
  generated: <ISO 8601 UTC timestamp>
49
51
  jira: <JIRA_KEY>-<id>
50
52
  branch: <branch-name>
@@ -58,6 +60,8 @@ template_version: v3
58
60
  ---
59
61
  ```
60
62
 
63
+ `ui_tests` and `a11y_depth` record the Phase 0 Step 5a opt-ins (defaults `false` / `basic`) so the coverage choice is auditable and the pre-dispatch validator can enforce it: `ui_tests: true` requires Section 15.6, `a11y_depth: full` requires the Section 16.2 walkthrough.
64
+
61
65
  The dev pipeline (`/multi-agent:dev`) reads `platform` to verify file match, `evidence_digest` to detect stale specs, and `mode` to know which section set to expect.
62
66
 
63
67
  ## Omission rules
@@ -71,15 +75,15 @@ Locked 2 - no `TBD` / `Not applicable` placeholder. Sections with zero evidence
71
75
  | 6 Component Inventory | No Figma URL AND no UI primitive evidence in repo |
72
76
  | 7 Design Tokens | Sections 5 and 6 both omitted |
73
77
  | 8 Asset Inventory | No new visual asset planned AND no existing asset modified |
74
- | 10 Localization | No Figma text layer AND no localization key file in repo |
78
+ | 10 Localization | No Figma annotation AND no Figma text layer AND no localization key file in repo |
75
79
  | 11 Analytics | No Firebase input AND no analytics call site in repo |
76
80
  | 12 Deeplink/Push | No deeplink handler in repo AND no push payload binding AND spec silent |
77
81
  | 15 Test Plan | Backend-only run with no contract testing planned (rare) |
78
82
  | 16 Accessibility | Backend-only (no UI surface) |
79
83
  | 17 Security | No PII / auth / health data touched (rare, document explicit decision) |
80
- | 18 Rollout | Lite mode |
81
- | 19 Alternatives | Lite mode AND zero significant architectural decision points |
82
- | 22 Glossary | Lite mode |
84
+ | 18 Rollout | Lite mode. In Full mode render the compact checklist only (not prose); drop if the pipeline's own commit/PR/rollout phase already covers it and no staged rollout / feature flag applies |
85
+ | 19 Alternatives | Default drop. Render ONLY when a real architectural decision with 2+ genuine options exists; a thin or invented alternatives list is worse than none (a real decision point already lives in Section 20) |
86
+ | 22 Glossary | Default drop. Render ONLY on explicit request or when the doc uses more than 5 domain-specific terms a new reader would not know; otherwise define terms inline |
83
87
  | 23 Changelog | Lite mode (optional even in Full for small revisions) |
84
88
 
85
89
  Sections 1, 2, 4, 9, 13, 14, 20, 21 are never omitted.
@@ -95,10 +99,10 @@ Every Pass B-rendered cell carries a footnote pointing to its evidence source:
95
99
  Examples:
96
100
 
97
101
  ```
98
- | State holder | `PassengerFlightViewModel` ^[C2 high: 8 örnek; en yakın StandaloneLoginViewModel.swift] |
102
+ | State holder | `UserProfileViewModel` ^[C2 high: 8 örnek; en yakın StandaloneLoginViewModel.swift] |
99
103
  | Test naming | `@Test func fetch_validInput_returnsIdle()` ^[C4 medium: 4 mevcut örnek, çoğunluk Swift Testing] |
100
- | Identifier | `passengerFlight.continueButton` ^[C5 high: ui-testing-identifiers.json dot notation] |
101
- | State model | sealed enum `PassengerFlightUIState` ^[C3 fallback: conventions-defaults.md:C3-iOS, repo evidence none] |
104
+ | Identifier | `userProfile.continueButton` ^[C5 high: ui-testing-identifiers.json dot notation] |
105
+ | State model | sealed enum `UserProfileUIState` ^[C3 fallback: conventions-defaults.md:C3-iOS, repo evidence none] |
102
106
  ```
103
107
 
104
108
  Footnote-less cells fail the dispatch gate.
@@ -132,13 +136,17 @@ Never omitted. Locked 13 - both columns mandatory. A row in only one column is n
132
136
  ## 2. Hedefler ve Karşı Hedefler <!-- TR -->
133
137
  ## 2. Goals and Non-Goals <!-- EN -->
134
138
 
139
+ ### İş kapsamı / Business scope
140
+
141
+ <1-2 sentences: what this feature covers and its boundary, in domain language. Names the platforms in scope (from the provided repos) and what is explicitly out of scope.>
142
+
135
143
  | Hedef / Goal | Karşı Hedef / Non-Goal |
136
144
  |---|---|
137
145
  | <goal 1 citing source> | <non-goal 1 citing source> |
138
146
  | <goal 2> | <non-goal 2> |
139
147
  ```
140
148
 
141
- Each non-goal must say what it does NOT cover, not a vague "out of scope".
149
+ Each non-goal must say what it does NOT cover, not a vague "out of scope" (AI cannot infer exclusion from omission - state it positively). The scope line frames the goals below; detailed rules live in Section 4.4.
142
150
 
143
151
  ## 3. Akış Diyagramı / Flow Chart
144
152
 
@@ -217,7 +225,7 @@ And <secondary outcome>
217
225
  Given <precondition>
218
226
  But <conflicting state>
219
227
  When <action>
220
- Then <prevention code shown, e.g. PP-211>
228
+ Then <business-rule code shown, e.g. ERR-211>
221
229
  And <user guidance>
222
230
  \`\`\`
223
231
 
@@ -228,9 +236,18 @@ Given <precondition>
228
236
  When <action>
229
237
  Then <expected outcome>
230
238
  \`\`\`
239
+
240
+ ### 4.4 İş Kuralları / Business Rules
241
+
242
+ Locked 31 - the traceability spine. Every business rule is deterministic and testable (same input always yields the same outcome) and carries a stable id `BR-<slug>-NN`. Each rule maps to at least one acceptance criterion written Given / When / Then. In Full mode each acceptance criterion is then realized as unit-test scenarios in Section 15.1; in Lite mode Section 15 is not rendered, so the acceptance criterion stands alone here (it is itself the testable statement) and the 15.1 mapping is deferred to a later Full analysis or to `/multi-agent:dev`. Rules come from the spec / Confluence / Jira / annotations, never invented; a rule with no source cites `[label TBD - see Open Questions]` and adds a Section 20 row.
243
+
244
+ | BR ID | Kural / Rule (deterministic, testable) | Kaynak / Source | Kabul kriteri / Acceptance criterion (Given/When/Then) |
245
+ |---|---|---|---|
246
+ | BR-<slug>-01 | <rule statement> | [Confluence:<pageId>:<heading>] / [Figma annotation <nodeId>] / file:line | Given <state>, When <action>, Then <binary outcome> |
247
+ | BR-<slug>-02 | <rule statement> | <source> | Given ..., When ..., Then ... (+ >=1 failure-mode criterion) |
231
248
  ```
232
249
 
233
- Each scenario cites the matching prevention code (Section 9) when applicable. Plain-prose user stories without Given/When/Then are rejected by the renderer.
250
+ Each scenario in 4.1-4.3 cites the matching `BR-<slug>-NN` id (and, when a server code applies, the Section 9.4 code). Plain-prose user stories without Given/When/Then are rejected by the renderer.
234
251
 
235
252
  ## 5. Tasarım Referansı / Design Reference
236
253
 
@@ -266,6 +283,24 @@ Token references for each variant (no raw numbers).
266
283
  Tier in use: <1 (MCP) | 2 (REST) | 3 (user screenshot)>
267
284
  Screenshots attached: <count>
268
285
  Auth retries used: <count>
286
+
287
+ ### 5.4 Yerleşim ve Kaydırma / Layout and Scroll
288
+
289
+ Precise-enough-to-reproduce layout. Spacing/padding are token names (no raw numbers, Locked bound to Section 7). One row per meaningful component or frame region.
290
+
291
+ | Bileşen / Component | Node | Auto Layout (yön/wrap) | Padding (kenar/edge -> token) | Boşluk / Gap (token) | Hizalama / Alignment | Boyut / Sizing (fixed/hug/fill) | Konum / Position (constraint) |
292
+ |---|---|---|---|---|---|---|---|
293
+ | <name> | <nodeId> | vertical | top Spacing.spacing16 / others Spacing.spacing12 | Spacing.spacing8 | leading / center | width fill, height hug | pinned top |
294
+
295
+ **Scroll and container behavior** (a static Figma frame cannot express this - state it explicitly for both the AI and the human):
296
+
297
+ - Kaydırma kabı / Scroll container: <which region scrolls; single vertical scroll vs nested>
298
+ - Sabit / Sticky: <header/footer/CTA pinned while content scrolls, or none>
299
+ - İçerik kenar boşlukları / Content insets: <token or safe-area-relative>
300
+ - Güvenli alan / Safe area: <edges respected: top notch / bottom home indicator / status bar>
301
+ - Klavye / Keyboard avoidance: <inset-adjust vs scroll-into-view vs none>
302
+
303
+ Capture order (tokens never hardcoded): `mcp__claude_ai_Figma__get_metadata` (tree) -> `mcp__claude_ai_Figma__get_variable_defs` (resolve spacing/color/type tokens) -> render -> `mcp__claude_ai_Figma__get_screenshot` (visual check). Run per section/frame for fidelity, not per whole page.
269
304
  ```
270
305
 
271
306
  Frame gallery images are uploaded to Confluence as multipart attachments and referenced via `<ac:image><ri:attachment ri:filename="..." /></ac:image>` (Locked 17). Section URL drilling enumerates all child frames automatically via `mcp__claude_ai_Figma__get_metadata` or `figma-screenshot.sh --section`.
@@ -287,6 +322,18 @@ Per-platform projection.
287
322
 
288
323
  Locked 11 - existence is resolved against `evidence.codeConnect[]` (the Code Connect index from Phase 1b.1) first: a design `nodeId` (or its `fileKey`) that matches an index entry emits a `reuse` row (`Mevcut/Existing = yes`, pointing at the bound component + path), because figma-to-swiftui already built and bound it. A `nodeId` with no Code Connect match falls back to the Phase 1b `uiComponents` heuristic; only when neither finds a candidate does the row become a new component (`Existing = no`, added to Section 14).
289
324
 
325
+ **Code Connect snippet is an existence oracle, not spec truth.** The Code Connect index answers "does this component already exist and is it bound", nothing more. A Code Connect snippet's contents are NOT authoritative for copy or for what the new screen needs - they describe what the codebase currently maps. Drift between a snippet and the current design is logged ONCE as a design-system-maintenance note in Section 20 (Open Questions), never as a per-string verification row.
326
+
327
+ ### 6.X Varyant / prop kapsamı - Variant / property coverage
328
+
329
+ Completeness audit: every property of a consumed Figma component gets exactly one disposition, so the spec proves it examined the whole component surface. An unmapped remainder is a Section 20 Risk.
330
+
331
+ | Bileşen / Component | Özellik / Property | Karar / Disposition | Not / Note |
332
+ |---|---|---|---|
333
+ | <Name> | <figmaProperty> | implement | <mapped concept / param> |
334
+ | <Name> | <figmaProperty> | skip | <reason not used on this screen> |
335
+ | <Name> | <figmaProperty> | defer | <open question ref> |
336
+
290
337
  ## 7. Tasarım Token'ları / Design Tokens
291
338
 
292
339
  Per-platform projection. No magic numbers, no raw hex, no raw `.font()`.
@@ -315,9 +362,20 @@ Per-platform projection. No magic numbers, no raw hex, no raw `.font()`.
315
362
  |---|---|
316
363
  | .h6 | <where> |
317
364
  | .body1 | <where> |
365
+
366
+ ### 7.4 Karanlık Mod / Dark Mode
367
+
368
+ Rendered when the design ships a dark variant (else drop with note `(N/A: no dark variant)`). Each row is a semantic token PAIR, never a literal hue - the token resolves per scheme. Re-verify contrast in dark; a light-passing pair can fail dark.
369
+
370
+ | Rol / Role | Light token | Dark token | Not / Note (elevation, asset variant, contrast) |
371
+ |---|---|---|---|
372
+ | surface/base | Color.Surface.base | Color.Surface.base (adaptive) | iOS systemBackground; Android tonal elevation (no shadow in dark) |
373
+ | content/primary | Color.Content.primary | Color.Content.primary | contrast >= 4.5:1 rechecked in dark |
374
+ | border/subtle | Color.Border.subtle | Color.Border.subtle | - |
375
+ | <named asset> | icon@light | icon@dark | asset-catalog appearance variant |
318
376
  ```
319
377
 
320
- Per-platform projection translates token names: iOS uses `.Spacing.spacingN` enum; Android uses `MaterialTheme.spacing.medium` or design system equivalent; Frontend uses CSS variables `--spacing-md` or Tailwind classes; Backend section omitted.
378
+ Per-platform projection translates token names: iOS uses `.Spacing.spacingN` enum + adaptive `Color(.systemBackground)` / asset appearances; Android uses `MaterialTheme.spacing.medium` + Material 3 color roles with tonal elevation in dark; Frontend uses CSS variables `--spacing-md` / `--color-*` per `prefers-color-scheme`; Backend section omitted.
321
379
 
322
380
  ## 8. Asset Envanteri / Asset Inventory
323
381
 
@@ -379,40 +437,63 @@ List every HTTP status code one by one.
379
437
  | 422 | business rule violated | `{"code":"FP-031"}` | <UI> |
380
438
  | 500 | server error | `{"code":"GEN-500"}` | <UI> |
381
439
 
382
- ### 9.4 Prevention catalog (when applicable)
440
+ ### 9.4 Business-rule catalog (when applicable)
383
441
 
384
442
  | Kod / Code | Anlam / Meaning | UI konumu / UI location |
385
443
  |---|---|---|
386
444
  | FP-011 | Invalid format | <screen> |
387
- | PP-211 | Empty result set | <screen> |
445
+ | ERR-211 | Empty result set | <screen> |
388
446
  ```
389
447
 
390
448
  ## 10. Lokalizasyon Anahtarları / Localization Keys
391
449
 
392
- Per-platform projection. Locked 19 - 8-locale full coverage mandatory for new keys.
450
+ Per-platform projection. Locked 20 - shape depends on the project `figma-config` `localization.ownership`. The locale set comes from `localization.locales` (default `tr, en, ar, de, es, fr, it, ru`); never hardcode a locale list.
451
+
452
+ ### Mode A - `in-repo` (default): full per-locale grid
453
+
454
+ New keys carry filled cells for every configured locale.
393
455
 
394
456
  ```markdown
395
457
  ## 10. Lokalizasyon Anahtarları <!-- TR -->
396
458
  ## 10. Localization Keys <!-- EN -->
397
459
 
398
- Desteklenen diller: TR, EN, AR, DE, ES, FR, RU, ZH
460
+ Desteklenen diller / Supported locales: <localization.locales, e.g. TR, EN, AR, DE, ES, FR, IT, RU>
399
461
 
400
- | Key | TR | EN | AR | DE | ES | FR | RU | ZH | Source | Status |
401
- |---|---|---|---|---|---|---|---|---|---|---|
402
- | <key 1> | <tr> | <en> | <ar> | <de> | <es> | <fr> | <ru> | <zh> | Figma <nodeId> | new |
403
- | <key 2> | <tr> | <en> | <ar> | <de> | <es> | <fr> | <ru> | <zh> | repo:Localizable.xcstrings | reuse |
404
- | <key 3> | <tr> | <en> | [bekleniyor: çeviri ekibi] | [bekleniyor: çeviri ekibi] | ... | ... | ... | ... | Figma | partial |
462
+ | Key | <loc1> | <loc2> | ... | Source | Status |
463
+ |---|---|---|---|---|---|
464
+ | <key 1> | <val> | <val> | ... | Figma annotation <nodeId> | new |
465
+ | <key 2> | <val> | <val> | ... | repo:Localizable.xcstrings | reuse |
466
+ | <key 3> | <tr> | <en> | [bekleniyor: çeviri ekibi] / [pending: translation team] | Figma annotation <nodeId> | partial |
405
467
  ```
406
468
 
407
- Status values:
408
-
409
469
  | Status | Meaning |
410
470
  |---|---|
411
- | reuse | Existing namespace, no new translation needed |
412
- | new | New key, all 8 locales must be filled |
413
- | partial | TR + EN filled, remaining 6 locales pending translation team. Dispatch report flags `i18n_pending: <count>` as blocker. |
471
+ | reuse | Existing key, no new translation needed |
472
+ | new | New key, all configured locales must be filled |
473
+ | partial | Base locales filled, rest pending translation team. Dispatch flags `i18n_pending: <count>` as blocker. |
474
+
475
+ ### Mode B - `externally-owned`: keys + ownership, values deferred
476
+
477
+ Per-locale values are owned by `localization.authoringPipeline` and are NOT hand-filled here (they would be overwritten by the owning system and were never the analyst's to write). List the key, its status, the copy source, and the `localization.baseLanguage` value only.
414
478
 
415
- Per-platform projection:
479
+ ```markdown
480
+ ## 10. Lokalizasyon Anahtarları <!-- TR -->
481
+ ## 10. Localization Keys <!-- EN -->
482
+
483
+ Sahiplik / Ownership: <localization.authoringPipeline> (per-locale values authored there)
484
+ Temel dil / Base language: <localization.baseLanguage>
485
+
486
+ | Key (<keyPattern>) | Durum / Status | Kopya kaynağı / Copy source | Temel değer / Base value | Sahiplik / Ownership |
487
+ |---|---|---|---|---|
488
+ | <Namespace.Case> | new | Figma annotation <nodeId> | <base-language value> | <authoringPipeline> |
489
+ | <Namespace.Case> | reuse | existing catalog | (existing) | <authoringPipeline> |
490
+ ```
491
+
492
+ Gate (Mode B): the base-language value is present (sourced from the Figma annotation per Locked 3) AND the ownership reference is cited. A key missing its base-language value is a blocker - at runtime a missing base value renders the raw `Namespace.Case` key on screen. Fabricated per-locale values are rejected.
493
+
494
+ Label-authoring contract (Mode B): the analysis names each label and its base copy; it does NOT write per-locale values or hand-edit the owning system's files. A `new` key is a pure addition (auto-mergeable by the authoring pipeline). A `reuse` key that needs a copy change is an EDIT - flag it as review-gated (the owning pipeline requires approval) and never silently overwrite. The base copy always comes from the Figma annotation; when the annotation is missing a language the authoring pipeline expects, emit a Section 20 row rather than inventing it.
495
+
496
+ Per-platform projection (both modes):
416
497
 
417
498
  | Platform | File format | Key convention | Example |
418
499
  |---|---|---|---|
@@ -426,15 +507,16 @@ Per-platform projection:
426
507
  ```markdown
427
508
  ## 11. Analytics
428
509
 
429
- | Event | Parametreler / Params | Tetiklenme / Trigger | Source | Status |
510
+ | Event | Parametreler / Params | Tetiklenme / Trigger (story / rule) | Source | Status |
430
511
  |---|---|---|---|---|
431
- | <event_name> | <param1, param2> | <when> | <Firebase schema / repo / Confluence> | new |
432
- | <event_name> | <params> | <when> | <source> | reuse |
512
+ | <event_name> | <param1, param2> | 4.1 happy path / BR-<slug>-01 | <Firebase schema / repo / Confluence> | new |
513
+ | <event_name> | <params> | <story or BR id> | <source> | reuse |
433
514
 
515
+ Each event's Trigger cites the user story (Section 4) or business rule (`BR-` id, Section 4.4) it fires on, so the analytics plan traces to behavior (Locked 31).
434
516
  PII redaction: <list of fields hashed before telemetry>
435
517
  ```
436
518
 
437
- Locked 11 - direct-match events emit reuse rows. Locked 17 - PII fields (PNR, surname, ffp number) hashed before emit.
519
+ Locked 11 - direct-match events emit reuse rows. Locked 17 - PII fields (email, phone, national ID) hashed before emit.
438
520
 
439
521
  ## 12. Deeplink ve Push Notification / Deeplink and Push
440
522
 
@@ -530,7 +612,7 @@ Locked 21 - platform-agnostic concept layer rendered with repo conventions.
530
612
  <rendered navigator interface per Pass B>
531
613
  \`\`\`
532
614
 
533
- ### 13.6 DI registration
615
+ ### 13.5.1 DI registration
534
616
 
535
617
  \`\`\`<lang>
536
618
  <rendered DI register line per Pass B>
@@ -548,28 +630,28 @@ One preview entry per variant matters for Xcode Canvas and snapshot test alignme
548
630
  | Default | LTR Dark | dark mode regression check |
549
631
  | Default | RTL Light | RTL layout regression |
550
632
  | Default | Dynamic Type accessibilityLarge | scaling regression |
551
- | Error state | LTR Light | empty state, prevention message |
633
+ | Error state | LTR Light | empty state, error message |
552
634
  | Loading state | LTR Light | skeleton or spinner |
553
- | Edge case (e.g. long copy, single passenger) | LTR Light | optional, when distinct from canonical |
635
+ | Edge case (e.g. long copy, single item) | LTR Light | optional, when distinct from canonical |
554
636
 
555
637
  Preview snippet (Pass B fills variant list per Phase 1c convention):
556
638
 
557
639
  \`\`\`swift
558
640
  #Preview("Default") {
559
- PassengerFlightView(viewModel: .preview(.idle))
641
+ UserProfileView(viewModel: .preview(.idle))
560
642
  }
561
643
 
562
- #Preview("Error - PP-211") {
563
- PassengerFlightView(viewModel: .preview(.error(.PP_211)))
644
+ #Preview("Error - ERR-211") {
645
+ UserProfileView(viewModel: .preview(.error(.PP_211)))
564
646
  }
565
647
 
566
648
  #Preview("Dark") {
567
- PassengerFlightView(viewModel: .preview(.idle))
649
+ UserProfileView(viewModel: .preview(.idle))
568
650
  .preferredColorScheme(.dark)
569
651
  }
570
652
 
571
653
  #Preview("RTL") {
572
- PassengerFlightView(viewModel: .preview(.idle))
654
+ UserProfileView(viewModel: .preview(.idle))
573
655
  .environment(\.layoutDirection, .rightToLeft)
574
656
  }
575
657
  \`\`\`
@@ -610,11 +692,22 @@ Per-platform projection.
610
692
  ## 15. Test Planı <!-- TR -->
611
693
  ## 15. Test Plan <!-- EN -->
612
694
 
613
- ### 15.1 Birim testleri / Unit tests
695
+ ### 15.1 Birim testleri / Unit tests (kural bazlı / rule-driven)
614
696
 
615
- | Test | Hedef / Target |
616
- |---|---|
617
- | <test name per C4 convention> | <what it verifies> |
697
+ Locked 31 - one sub-table per business rule from Section 4.4. Enumerate the cases: happy, boundary (min/max, off-by-one), error/failure, empty/nil. Each row is Given / When / Then plus a framework-correct test name that traces back to the `BR-` id. Boundary rows collapse into one parameterized test.
698
+
699
+ Framework per platform (from Phase 1c conventions; these are the modern defaults): iOS Swift Testing (`@Test`, `@Suite`, `@Test(arguments:)` for boundary tables, `#expect` / `#require`); Android JUnit5 + MockK (`coEvery` / `coVerify`) + Turbine (`flow.test { awaitItem() }`) + coroutines-test (`runTest`, `StandardTestDispatcher`); Backend pytest (parametrize); Frontend Vitest.
700
+
701
+ **BR-<slug>-01**
702
+
703
+ | Senaryo / Case | Given | When | Then | Test adı / name |
704
+ |---|---|---|---|---|
705
+ | happy | <state> | <action> | <expected> | `func rule_validInput_returnsExpected()` ^[C4 <conf>] |
706
+ | boundary | <edge state> | <action> | <expected> | `@Test(arguments:)` parametrized |
707
+ | error | <failure state> | <action> | <error/emission> | `func rule_failure_emitsError()` |
708
+ | empty/nil | <empty state> | <action> | <default/guard> | `func rule_empty_returnsIdle()` |
709
+
710
+ (Repeat one sub-table per rule. A rule with no unit-test row fails the dispatch gate, Locked 31.)
618
711
 
619
712
  ### 15.2 Görsel regresyon / Snapshot tests
620
713
 
@@ -636,11 +729,11 @@ Cihaz boyutları / Device sizes: <list>
636
729
 
637
730
  ### 15.4 Kabul senaryoları / Acceptance scenarios
638
731
 
639
- Gherkin scenarios from Section 4 mapped to UI test names.
732
+ Section 4 Gherkin scenarios + their `BR-` ids mapped to test names.
640
733
 
641
- | Senaryo | UI test |
642
- |---|---|
643
- | 4.1 happy path | <XCUITest / Espresso / Playwright name> |
734
+ | Senaryo | BR ID | Test |
735
+ |---|---|---|
736
+ | 4.1 happy path | BR-<slug>-01 | <unit or UI test name> |
644
737
 
645
738
  ### 15.5 Performans bütçesi / Performance budget
646
739
 
@@ -649,6 +742,16 @@ Gherkin scenarios from Section 4 mapped to UI test names.
649
742
  | Cold start to ready | <ms> |
650
743
  | Scroll FPS | 60 |
651
744
  | Memory peak | <MB> |
745
+
746
+ ### 15.6 UI test senaryoları / UI test scenarios (OPTIONAL)
747
+
748
+ Rendered ONLY when `state.analysisSpec.options.uiTests == true` (Phase 0 Step 5a opt-in); otherwise omitted. UI tests are the most expensive tier - reserve for high-risk / revenue-connected cross-screen flows, not per-element coverage. Selectors are stable ids (iOS `accessibilityIdentifier`, Android `Modifier.testTag` / `contentDescription`), never localized text, so the flow survives copy/locale changes and an agent can generate it mechanically.
749
+
750
+ | Flow ID | Öncelik / Priority (why it earns a UI test) | Adımlar / Steps | Seçiciler / Selectors | Doğrulamalar / Assertions | Ön koşullar / Preconditions | BR ID |
751
+ |---|---|---|---|---|---|---|
752
+ | UI-<slug>-01 | <high-risk reason> | <ordered user actions> | <accessibilityIdentifier / testTag> | <visible state / navigation / error> | <seeded data / launch args / mocked network> | BR-<slug>-NN |
753
+
754
+ Framework: iOS XCUITest (`waitForExistence`, identifier-driven); Android Compose UI test (`createComposeRule`, `onNodeWithTag`) or Espresso for View-based screens.
652
755
  ```
653
756
 
654
757
  ## 16. Erişilebilirlik / Accessibility
@@ -659,17 +762,29 @@ Per-platform projection. WCAG 2.2 AA + Apple HIG + Material A11y.
659
762
  ## 16. Erişilebilirlik <!-- TR -->
660
763
  ## 16. Accessibility <!-- EN -->
661
764
 
662
- | Madde / Item | Durum / Status | Notlar / Notes |
663
- |---|---|---|
664
- | Unique identifier on every interactive element | required | <per C5 convention> |
665
- | Minimum tap target (iOS 44x44 pt / Android 48x48 dp / Web 44x44 px) | required | |
666
- | Screen reader label (accessibilityLabel / contentDescription / aria-label) | required | |
667
- | Hint text (accessibilityHint / stateDescription / aria-description) | optional | |
668
- | Dynamic Type / font scaling support | required | |
669
- | Contrast ratio >= 4.5:1 | required | |
670
- | State expressed beyond colour | required | |
671
- | Keyboard navigation (web only) | required | tabindex + focus order |
672
- | Voice Over / TalkBack reading order | required | |
765
+ ### 16.1 Kontrol listesi / Checklist (always)
766
+
767
+ | Madde / Item | Durum / Status | Notlar / Notes | WCAG 2.2 |
768
+ |---|---|---|---|
769
+ | Unique identifier on every interactive element | required | <per C5 convention> | 4.1.2 |
770
+ | Minimum tap target (iOS 44x44 pt / Android 48x48 dp / Web 44x44 px) | required | exceeds AA | 2.5.8 |
771
+ | Screen reader label (accessibilityLabel / contentDescription / aria-label) | required | | 1.1.1 / 4.1.2 |
772
+ | Hint text (accessibilityHint / stateDescription / aria-description) | optional | | 4.1.2 |
773
+ | Dynamic Type / font scaling to 200% | required | | 1.4.4 |
774
+ | Contrast ratio >= 4.5:1 (text) / 3:1 (large) | required | recheck in dark | 1.4.3 |
775
+ | State expressed beyond colour | required | | 1.4.1 |
776
+ | Reduce motion honored | required | | 2.3.3 |
777
+ | Keyboard navigation (web only) | required | tabindex + focus order | 2.1.1 / 2.4.3 |
778
+ | Voice Over / TalkBack reading order | required | see 16.2 when full depth | 2.4.3 |
779
+
780
+ ### 16.2 Ekran okuyucu gezinme / Screen-reader walkthrough (OPTIONAL)
781
+
782
+ Rendered ONLY when `state.analysisSpec.options.a11yDepth == "full"` (Phase 0 Step 5a opt-in); otherwise omitted. An ordered reading-order script that is BOTH the implementation spec and the manual VoiceOver / TalkBack test. One row per focus stop, in traversal order.
783
+
784
+ | # | Odak / Focus target | Seslendirilen / Announced (label + role/trait + value + hint) | Hareket / Gesture | Sonuç / Resulting state |
785
+ |---|---|---|---|---|
786
+ | 1 | <element + selector> | "<label>, <role>, <value>. <hint>" | swipe right | focus moves to 2 |
787
+ | 2 | <element> | "<...>" | double-tap | <navigation / state change> |
673
788
  ```
674
789
 
675
790
  ## 17. Güvenlik ve Gizlilik / Security and Privacy
@@ -700,17 +815,15 @@ OWASP MASVS quick check:
700
815
 
701
816
  ## 18. Yayılım Planı / Rollout Plan
702
817
 
818
+ Compact checklist only (default-drop per the omission table when the pipeline's own commit/PR/rollout phase already covers it). Include a row ONLY when it carries feature-specific information beyond the standard pipeline flow.
819
+
703
820
  ```markdown
704
821
  ## 18. Yayılım Planı <!-- TR -->
705
822
  ## 18. Rollout Plan <!-- EN -->
706
823
 
707
- | Aşama / Stage | Aksiyon / Action |
708
- |---|---|
709
- | Development | feature branch <name> |
710
- | Code review | <number of approvals, who> |
711
- | QA test | <devices and versions> |
712
- | Staged rollout | <internal -> beta -> production %> |
713
- | Rollback | <remote flag name> |
824
+ - Staged rollout: <internal -> beta -> production %, or "standard pipeline flow">
825
+ - Feature flag / rollback: <remote flag name, or "none">
826
+ - QA gate: <feature-specific device/version notes, or "standard">
714
827
  ```
715
828
 
716
829
  ## 19. Değerlendirilen Alternatifler / Alternatives Considered
@@ -779,13 +892,11 @@ Footer. Optional in Lite mode. Alphabetical.
779
892
 
780
893
  | Kısaltma / Abbreviation | Açıklama / Description |
781
894
  |---|---|
782
- | APIS | Advance Passenger Information System |
783
895
  | ATS | App Transport Security |
784
896
  | BFF | Backend For Frontend |
785
897
  | CTA | Call To Action |
786
- | FFP | Frequent Flyer Programme |
787
- | KVKK | Kişisel Verilerin Korunması Kanunu |
788
- | PNR | Passenger Name Record |
898
+ | DTO | Data Transfer Object |
899
+ | SSOT | Single Source of Truth |
789
900
  | WCAG | Web Content Accessibility Guidelines |
790
901
  ```
791
902
 
@@ -817,19 +928,19 @@ Conventions extracted: feature-first + clean arch, `<Feature>ViewModel`, sealed
817
928
 
818
929
  ```swift
819
930
  @MainActor
820
- final class PassengerFlightViewModel: ObservableObject {
821
- @Published private(set) var state: PassengerFlightUIState = .loading
822
- private let fetchUseCase: FetchPassengerFlightUseCaseProtocol
931
+ final class UserProfileViewModel: ObservableObject {
932
+ @Published private(set) var state: UserProfileUIState = .loading
933
+ private let fetchUseCase: FetchUserProfileUseCaseProtocol
823
934
 
824
- init(fetchUseCase: FetchPassengerFlightUseCaseProtocol) {
935
+ init(fetchUseCase: FetchUserProfileUseCaseProtocol) {
825
936
  self.fetchUseCase = fetchUseCase
826
937
  }
827
938
 
828
939
  func onAppear() async {
829
940
  do {
830
941
  let result = try await fetchUseCase.execute()
831
- state = .idle(passengers: result.passengers, flights: result.flights)
832
- } catch let error as PreventionError {
942
+ state = .idle(profile: result.profile)
943
+ } catch let error as DomainError {
833
944
  state = .error(error.code)
834
945
  } catch {
835
946
  state = .error(.generic)
@@ -837,17 +948,17 @@ final class PassengerFlightViewModel: ObservableObject {
837
948
  }
838
949
  }
839
950
 
840
- enum PassengerFlightUIState {
951
+ enum UserProfileUIState {
841
952
  case loading
842
- case idle(passengers: [PassengerEntity], flights: [FlightEntity])
843
- case error(PreventionCode)
953
+ case idle(profile: ProfileEntity)
954
+ case error(DomainErrorCode)
844
955
  }
845
956
  ```
846
957
 
847
- Folder: `Domains/Checkin/Sources/Checkin/Screens/PassengerAndFlightSelection/Presentation/ViewModel/`
958
+ Folder: `Domains/Profile/Sources/Profile/Screens/ProfileEditor/Presentation/ViewModel/`
848
959
  Test name: `@Test func fetch_validInput_returnsIdle()`
849
- Identifier: `passengerFlight.continueButton`
850
- Localization key: `PassengerFlight.ContinueButton`
960
+ Identifier: `userProfile.continueButton`
961
+ Localization key: `UserProfile.ContinueButton`
851
962
 
852
963
  ## Android render example
853
964
 
@@ -855,97 +966,96 @@ Conventions extracted: feature module + clean arch, `<Feature>ViewModel`, sealed
855
966
 
856
967
  ```kotlin
857
968
  @HiltViewModel
858
- class PassengerFlightViewModel @Inject constructor(
859
- private val fetchUseCase: FetchPassengerFlightUseCase
969
+ class UserProfileViewModel @Inject constructor(
970
+ private val fetchUseCase: FetchUserProfileUseCase
860
971
  ) : ViewModel() {
861
- private val _state = MutableStateFlow<PassengerFlightUiState>(PassengerFlightUiState.Loading)
862
- val state: StateFlow<PassengerFlightUiState> = _state.asStateFlow()
972
+ private val _state = MutableStateFlow<UserProfileUiState>(UserProfileUiState.Loading)
973
+ val state: StateFlow<UserProfileUiState> = _state.asStateFlow()
863
974
 
864
975
  fun onAppear() {
865
976
  viewModelScope.launch {
866
977
  try {
867
978
  val result = fetchUseCase.execute()
868
- _state.value = PassengerFlightUiState.Idle(result.passengers, result.flights)
869
- } catch (e: PreventionException) {
870
- _state.value = PassengerFlightUiState.Error(e.code)
979
+ _state.value = UserProfileUiState.Idle(result.profile)
980
+ } catch (e: DomainException) {
981
+ _state.value = UserProfileUiState.Error(e.code)
871
982
  }
872
983
  }
873
984
  }
874
985
  }
875
986
 
876
- sealed interface PassengerFlightUiState {
877
- data object Loading : PassengerFlightUiState
878
- data class Idle(val passengers: List<Passenger>, val flights: List<Flight>) : PassengerFlightUiState
879
- data class Error(val code: PreventionCode) : PassengerFlightUiState
987
+ sealed interface UserProfileUiState {
988
+ data object Loading : UserProfileUiState
989
+ data class Idle(val profile: Profile) : UserProfileUiState
990
+ data class Error(val code: DomainErrorCode) : UserProfileUiState
880
991
  }
881
992
  ```
882
993
 
883
- Folder: `feature/checkin/src/main/java/<package>/passengerflight/ui/`
994
+ Folder: `feature/profile/src/main/java/<package>/userprofile/ui/`
884
995
  Test name: `fun fetch_validInput_returnsIdle()`
885
- testTag: `passengerFlightContinueButton`
886
- strings.xml: `passenger_flight_continue_button`
996
+ testTag: `userProfileContinueButton`
997
+ strings.xml: `user_profile_continue_button`
887
998
 
888
999
  ## Backend render example
889
1000
 
890
1001
  Conventions extracted: layered Python FastAPI, router + service + repository, Pydantic DTO, pytest snake_case, FastAPI Depends.
891
1002
 
892
1003
  ```python
893
- class PassengerFlightService:
1004
+ class UserProfileService:
894
1005
  def __init__(
895
1006
  self,
896
- repository: PassengerFlightRepository,
897
- validator: PassengerSelectionValidator,
1007
+ repository: UserProfileRepository,
1008
+ validator: ProfileInputValidator,
898
1009
  ):
899
1010
  self.repository = repository
900
1011
  self.validator = validator
901
1012
 
902
- async def fetch(self, request: PassengerFlightIn) -> PassengerFlightOut:
903
- passengers = await self.repository.search_passengers(request.pnr, request.surname)
904
- flights = await self.repository.get_flight_details(request.pnr)
905
- return PassengerFlightOut(passengers=passengers, flights=flights)
1013
+ async def fetch(self, request: UserProfileIn) -> UserProfileOut:
1014
+ profile = await self.repository.get_profile(request.user_id)
1015
+ return UserProfileOut(profile=profile)
906
1016
 
907
1017
 
908
- @router.post("/start-checkin", response_model=PassengerFlightOut)
909
- async def start_checkin(
910
- request: PassengerFlightIn,
911
- service: PassengerFlightService = Depends(get_passenger_flight_service),
912
- ) -> PassengerFlightOut:
1018
+ @router.post("/update-profile", response_model=UserProfileOut)
1019
+ async def update_profile(
1020
+ request: UserProfileIn,
1021
+ service: UserProfileService = Depends(get_user_profile_service),
1022
+ ) -> UserProfileOut:
913
1023
  return await service.fetch(request)
914
1024
  ```
915
1025
 
916
- Folder: `app/api/checkin/`, `app/services/checkin/`, `app/schemas/checkin/`
917
- Test name: `def test_fetch_valid_input_returns_passenger_list():`
918
- OpenAPI operationId: `startCheckin`
1026
+ Folder: `app/api/profile/`, `app/services/profile/`, `app/schemas/profile/`
1027
+ Test name: `def test_fetch_valid_input_returns_profile():`
1028
+ OpenAPI operationId: `updateProfile`
919
1029
 
920
1030
  ## Frontend render example
921
1031
 
922
1032
  Conventions extracted: Next.js App Router, feature folder, React Query hooks, Zustand stores, Vitest, kebab data-testid, hierarchical dot camelCase i18n.
923
1033
 
924
1034
  ```tsx
925
- type PassengerFlightState =
1035
+ type UserProfileState =
926
1036
  | { kind: "loading" }
927
- | { kind: "idle"; passengers: Passenger[]; flights: Flight[] }
928
- | { kind: "error"; code: PreventionCode };
1037
+ | { kind: "idle"; profile: Profile }
1038
+ | { kind: "error"; code: DomainErrorCode };
929
1039
 
930
- export function usePassengerFlight(pnr: string, surname: string) {
931
- return useQuery<PassengerFlightDto, PreventionError>({
932
- queryKey: ["passenger-flight", pnr, surname],
933
- queryFn: () => passengerFlightApi.fetch({ pnr, surname }),
1040
+ export function useUserProfile(userId: string) {
1041
+ return useQuery<UserProfileDto, DomainError>({
1042
+ queryKey: ["user-profile", userId],
1043
+ queryFn: () => userProfileApi.fetch({ userId }),
934
1044
  });
935
1045
  }
936
1046
 
937
- export function PassengerFlightPage({ pnr, surname }: Props) {
938
- const query = usePassengerFlight(pnr, surname);
1047
+ export function UserProfilePage({ userId }: Props) {
1048
+ const query = useUserProfile(userId);
939
1049
  if (query.isLoading) return <LoadingState />;
940
1050
  if (query.isError) return <ErrorState code={query.error.code} />;
941
- return <PassengerFlightContent data={query.data} />;
1051
+ return <UserProfileContent data={query.data} />;
942
1052
  }
943
1053
  ```
944
1054
 
945
- Folder: `src/features/checkin/passenger-flight/{components,hooks,api,types}/`
1055
+ Folder: `src/features/profile/user-profile/{components,hooks,api,types}/`
946
1056
  Test name: `it('returns idle state for valid input', ...)`
947
- data-testid: `passenger-flight-continue-button`
948
- i18n key: `passengerFlight.continueButton`
1057
+ data-testid: `user-profile-continue-button`
1058
+ i18n key: `userProfile.continueButton`
949
1059
 
950
1060
  ---
951
1061