claudecode-omc 5.6.6 → 5.6.8

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 (179) hide show
  1. package/.local/skills/THIRD_PARTY_LICENSES/AvdLee-SwiftUI-Agent-Skill.LICENSE +21 -0
  2. package/.local/skills/THIRD_PARTY_LICENSES/Dimillian-Skills.LICENSE +21 -0
  3. package/.local/skills/THIRD_PARTY_LICENSES/README.md +36 -0
  4. package/.local/skills/THIRD_PARTY_LICENSES/twostraws-swiftui-agent-skill.LICENSE +21 -0
  5. package/.local/skills/h5-to-swiftui/SKILL.md +201 -0
  6. package/.local/skills/h5-to-swiftui/assets/calibration/README.md +176 -0
  7. package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/index.html +52 -0
  8. package/.local/skills/h5-to-swiftui/assets/calibration/h5-twin/style.css +133 -0
  9. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Package.swift +26 -0
  10. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin/Sources/CalibrationScreen/CalibrationScreen.swift +142 -0
  11. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Package.swift +32 -0
  12. package/.local/skills/h5-to-swiftui/assets/calibration/swiftui-twin-divergent/Sources/CalibrationScreenDivergent/CalibrationScreenDivergent.swift +122 -0
  13. package/.local/skills/h5-to-swiftui/assets/calibration/tokens.json +42 -0
  14. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/index.html +14 -0
  15. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/package.json +20 -0
  16. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/001.json +96 -0
  17. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/public/api/articles/index.json +89 -0
  18. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.jsx +22 -0
  19. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/App.module.css +11 -0
  20. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.jsx +53 -0
  21. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/ArticleCard.module.css +139 -0
  22. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.jsx +37 -0
  23. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/NavBar.module.css +72 -0
  24. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.jsx +30 -0
  25. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TagCloud.module.css +50 -0
  26. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.jsx +159 -0
  27. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/components/TrendChart.module.css +21 -0
  28. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/main.jsx +12 -0
  29. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.jsx +182 -0
  30. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/ArticleScreen.module.css +294 -0
  31. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.jsx +147 -0
  32. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/screens/FeedScreen.module.css +161 -0
  33. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/global.css +50 -0
  34. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/src/styles/tokens.css +103 -0
  35. package/.local/skills/h5-to-swiftui/assets/sample-h5-react/vite.config.js +6 -0
  36. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/data/tasks.js +67 -0
  37. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/index.html +26 -0
  38. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/router.js +73 -0
  39. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/detail.js +164 -0
  40. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/home.js +53 -0
  41. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/screens/list.js +87 -0
  42. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/app.css +342 -0
  43. package/.local/skills/h5-to-swiftui/assets/sample-h5-vanilla/styles/tokens.css +68 -0
  44. package/.local/skills/h5-to-swiftui/references/css-to-swiftui-map.md +205 -0
  45. package/.local/skills/h5-to-swiftui/references/design-token-extraction.md +209 -0
  46. package/.local/skills/h5-to-swiftui/references/high-risk-triage.md +209 -0
  47. package/.local/skills/h5-to-swiftui/references/render-equivalence-calibration.md +193 -0
  48. package/.local/skills/h5-to-swiftui/references/stack-detection.md +160 -0
  49. package/.local/skills/h5-to-swiftui/references/visual-diff-loop-protocol.md +365 -0
  50. package/.local/skills/h5-to-swiftui/scripts/_calib-consts.mjs +150 -0
  51. package/.local/skills/h5-to-swiftui/scripts/_imglib.mjs +547 -0
  52. package/.local/skills/h5-to-swiftui/scripts/_provenance.mjs +123 -0
  53. package/.local/skills/h5-to-swiftui/scripts/calibrate-render.mjs +625 -0
  54. package/.local/skills/h5-to-swiftui/scripts/capture-reference.mjs +386 -0
  55. package/.local/skills/h5-to-swiftui/scripts/detect-stack.mjs +305 -0
  56. package/.local/skills/h5-to-swiftui/scripts/evaluate-convergence.mjs +1093 -0
  57. package/.local/skills/h5-to-swiftui/scripts/extract-tokens.mjs +600 -0
  58. package/.local/skills/h5-to-swiftui/scripts/mark-overlay.mjs +379 -0
  59. package/.local/skills/h5-to-swiftui/scripts/pixel-diff.mjs +530 -0
  60. package/.local/skills/h5-to-swiftui/scripts/sim-screenshot.sh +544 -0
  61. package/.local/skills/ios-debugger-agent/SKILL.md +51 -0
  62. package/.local/skills/ios-debugger-agent/agents/openai.yaml +4 -0
  63. package/.local/skills/swift-concurrency-expert/SKILL.md +105 -0
  64. package/.local/skills/swift-concurrency-expert/agents/openai.yaml +4 -0
  65. package/.local/skills/swift-concurrency-expert/references/approachable-concurrency.md +63 -0
  66. package/.local/skills/swift-concurrency-expert/references/swift-6-2-concurrency.md +272 -0
  67. package/.local/skills/swift-concurrency-expert/references/swiftui-concurrency-tour-wwdc.md +33 -0
  68. package/.local/skills/swiftui-expert-skill/SKILL.md +162 -0
  69. package/.local/skills/swiftui-expert-skill/references/accessibility-patterns.md +215 -0
  70. package/.local/skills/swiftui-expert-skill/references/animation-advanced.md +403 -0
  71. package/.local/skills/swiftui-expert-skill/references/animation-basics.md +284 -0
  72. package/.local/skills/swiftui-expert-skill/references/animation-transitions.md +326 -0
  73. package/.local/skills/swiftui-expert-skill/references/charts-accessibility.md +135 -0
  74. package/.local/skills/swiftui-expert-skill/references/charts.md +602 -0
  75. package/.local/skills/swiftui-expert-skill/references/focus-patterns.md +299 -0
  76. package/.local/skills/swiftui-expert-skill/references/image-optimization.md +203 -0
  77. package/.local/skills/swiftui-expert-skill/references/latest-apis.md +488 -0
  78. package/.local/skills/swiftui-expert-skill/references/layout-best-practices.md +266 -0
  79. package/.local/skills/swiftui-expert-skill/references/liquid-glass.md +423 -0
  80. package/.local/skills/swiftui-expert-skill/references/list-patterns.md +446 -0
  81. package/.local/skills/swiftui-expert-skill/references/macos-scenes.md +318 -0
  82. package/.local/skills/swiftui-expert-skill/references/macos-views.md +357 -0
  83. package/.local/skills/swiftui-expert-skill/references/macos-window-styling.md +303 -0
  84. package/.local/skills/swiftui-expert-skill/references/performance-patterns.md +403 -0
  85. package/.local/skills/swiftui-expert-skill/references/scroll-patterns.md +293 -0
  86. package/.local/skills/swiftui-expert-skill/references/sheet-navigation-patterns.md +363 -0
  87. package/.local/skills/swiftui-expert-skill/references/state-management.md +388 -0
  88. package/.local/skills/swiftui-expert-skill/references/text-patterns.md +32 -0
  89. package/.local/skills/swiftui-expert-skill/references/trace-analysis.md +295 -0
  90. package/.local/skills/swiftui-expert-skill/references/trace-recording.md +134 -0
  91. package/.local/skills/swiftui-expert-skill/references/view-structure.md +780 -0
  92. package/.local/skills/swiftui-expert-skill/scripts/__pycache__/analyze_trace.cpython-313.pyc +0 -0
  93. package/.local/skills/swiftui-expert-skill/scripts/__pycache__/record_trace.cpython-313.pyc +0 -0
  94. package/.local/skills/swiftui-expert-skill/scripts/analyze_trace.py +301 -0
  95. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__init__.py +1 -0
  96. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/__init__.cpython-313.pyc +0 -0
  97. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/causes.cpython-313.pyc +0 -0
  98. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/correlate.cpython-313.pyc +0 -0
  99. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/events.cpython-313.pyc +0 -0
  100. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/hangs.cpython-313.pyc +0 -0
  101. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/hitches.cpython-313.pyc +0 -0
  102. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/summary.cpython-313.pyc +0 -0
  103. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/swiftui.cpython-313.pyc +0 -0
  104. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/time_profiler.cpython-313.pyc +0 -0
  105. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/xctrace.cpython-313.pyc +0 -0
  106. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/__pycache__/xml_utils.cpython-313.pyc +0 -0
  107. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/causes.py +187 -0
  108. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/correlate.py +179 -0
  109. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/events.py +291 -0
  110. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/hangs.py +108 -0
  111. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/hitches.py +145 -0
  112. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/summary.py +243 -0
  113. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/swiftui.py +195 -0
  114. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/time_profiler.py +135 -0
  115. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/xctrace.py +117 -0
  116. package/.local/skills/swiftui-expert-skill/scripts/instruments_parser/xml_utils.py +224 -0
  117. package/.local/skills/swiftui-expert-skill/scripts/record_trace.py +252 -0
  118. package/.local/skills/swiftui-liquid-glass/SKILL.md +90 -0
  119. package/.local/skills/swiftui-liquid-glass/agents/openai.yaml +4 -0
  120. package/.local/skills/swiftui-liquid-glass/references/liquid-glass.md +280 -0
  121. package/.local/skills/swiftui-performance-audit/SKILL.md +106 -0
  122. package/.local/skills/swiftui-performance-audit/agents/openai.yaml +4 -0
  123. package/.local/skills/swiftui-performance-audit/references/code-smells.md +150 -0
  124. package/.local/skills/swiftui-performance-audit/references/demystify-swiftui-performance-wwdc23.md +46 -0
  125. package/.local/skills/swiftui-performance-audit/references/optimizing-swiftui-performance-instruments.md +29 -0
  126. package/.local/skills/swiftui-performance-audit/references/profiling-intake.md +44 -0
  127. package/.local/skills/swiftui-performance-audit/references/report-template.md +47 -0
  128. package/.local/skills/swiftui-performance-audit/references/understanding-hangs-in-your-app.md +33 -0
  129. package/.local/skills/swiftui-performance-audit/references/understanding-improving-swiftui-performance.md +52 -0
  130. package/.local/skills/swiftui-pro/SKILL.md +108 -0
  131. package/.local/skills/swiftui-pro/agents/openai.yaml +10 -0
  132. package/.local/skills/swiftui-pro/assets/swiftui-pro-icon.png +0 -0
  133. package/.local/skills/swiftui-pro/assets/swiftui-pro-icon.svg +29 -0
  134. package/.local/skills/swiftui-pro/references/accessibility.md +13 -0
  135. package/.local/skills/swiftui-pro/references/api.md +39 -0
  136. package/.local/skills/swiftui-pro/references/data.md +43 -0
  137. package/.local/skills/swiftui-pro/references/design.md +32 -0
  138. package/.local/skills/swiftui-pro/references/hygiene.md +9 -0
  139. package/.local/skills/swiftui-pro/references/navigation.md +14 -0
  140. package/.local/skills/swiftui-pro/references/performance.md +46 -0
  141. package/.local/skills/swiftui-pro/references/swift.md +56 -0
  142. package/.local/skills/swiftui-pro/references/views.md +36 -0
  143. package/.local/skills/swiftui-ui-patterns/SKILL.md +95 -0
  144. package/.local/skills/swiftui-ui-patterns/agents/openai.yaml +4 -0
  145. package/.local/skills/swiftui-ui-patterns/references/app-wiring.md +201 -0
  146. package/.local/skills/swiftui-ui-patterns/references/async-state.md +96 -0
  147. package/.local/skills/swiftui-ui-patterns/references/components-index.md +50 -0
  148. package/.local/skills/swiftui-ui-patterns/references/controls.md +57 -0
  149. package/.local/skills/swiftui-ui-patterns/references/deeplinks.md +66 -0
  150. package/.local/skills/swiftui-ui-patterns/references/focus.md +90 -0
  151. package/.local/skills/swiftui-ui-patterns/references/form.md +97 -0
  152. package/.local/skills/swiftui-ui-patterns/references/grids.md +71 -0
  153. package/.local/skills/swiftui-ui-patterns/references/haptics.md +71 -0
  154. package/.local/skills/swiftui-ui-patterns/references/input-toolbar.md +51 -0
  155. package/.local/skills/swiftui-ui-patterns/references/lightweight-clients.md +93 -0
  156. package/.local/skills/swiftui-ui-patterns/references/list.md +86 -0
  157. package/.local/skills/swiftui-ui-patterns/references/loading-placeholders.md +38 -0
  158. package/.local/skills/swiftui-ui-patterns/references/macos-settings.md +71 -0
  159. package/.local/skills/swiftui-ui-patterns/references/matched-transitions.md +59 -0
  160. package/.local/skills/swiftui-ui-patterns/references/media.md +73 -0
  161. package/.local/skills/swiftui-ui-patterns/references/menu-bar.md +101 -0
  162. package/.local/skills/swiftui-ui-patterns/references/navigationstack.md +159 -0
  163. package/.local/skills/swiftui-ui-patterns/references/overlay.md +45 -0
  164. package/.local/skills/swiftui-ui-patterns/references/performance.md +62 -0
  165. package/.local/skills/swiftui-ui-patterns/references/previews.md +48 -0
  166. package/.local/skills/swiftui-ui-patterns/references/scroll-reveal.md +133 -0
  167. package/.local/skills/swiftui-ui-patterns/references/scrollview.md +87 -0
  168. package/.local/skills/swiftui-ui-patterns/references/searchable.md +71 -0
  169. package/.local/skills/swiftui-ui-patterns/references/sheets.md +155 -0
  170. package/.local/skills/swiftui-ui-patterns/references/split-views.md +72 -0
  171. package/.local/skills/swiftui-ui-patterns/references/tabview.md +114 -0
  172. package/.local/skills/swiftui-ui-patterns/references/theming.md +71 -0
  173. package/.local/skills/swiftui-ui-patterns/references/title-menus.md +93 -0
  174. package/.local/skills/swiftui-ui-patterns/references/top-bar.md +49 -0
  175. package/.local/skills/swiftui-view-refactor/SKILL.md +202 -0
  176. package/.local/skills/swiftui-view-refactor/agents/openai.yaml +4 -0
  177. package/.local/skills/swiftui-view-refactor/references/mv-patterns.md +161 -0
  178. package/bundled/manifest.json +1 -1
  179. package/package.json +1 -1
@@ -0,0 +1,193 @@
1
+ # Stage 2.5 — Render-Equivalence Calibration
2
+
3
+ **Why this exists.** A simulator screenshot and a Playwright screenshot of
4
+ the "same" screen are *not* comparable rasters. Diffing them directly makes
5
+ every metric meaningless and the loop oscillates on non-defects. Calibration
6
+ defines the exact normalization that makes them comparable, and **measures
7
+ the best achievable cross-renderer agreement** so Stage 5 gates against a
8
+ real number instead of an asserted constant.
9
+
10
+ ## The non-comparability sources (all must be neutralized)
11
+
12
+ | Source | H5 (Playwright/WebKit) | iOS (simctl/SwiftUI) | Fix |
13
+ |---|---|---|---|
14
+ | Chrome | no status bar / home indicator | full framebuffer incl. status bar, notch, home indicator | crop simulator content rect via known safe-area insets for `--device` |
15
+ | Scale | logical px × `deviceScaleFactor` | physical px (e.g. @3x) | resample **both** to a common logical raster (device logical pt × fixed scale) with an **identical filter** (Lanczos3 both sides) |
16
+ | Color | sRGB-tagged PNG | Display-P3 framebuffer | convert P3 → sRGB (relative colorimetric) before any ΔE |
17
+ | Coords | DOM bbox in CSS px @ logical viewport | view frame in screen pt | apply `transform` (scale + safe-area offset) DOM-bbox → screen-rect |
18
+ | Text raster | Skia glyphs | CoreText glyphs | irreducible — handled in Stage 5 by judging text regions on layout-box IoU + resolved token-color ΔE, NOT glyph-raster SSIM |
19
+
20
+ ## Calibration procedure
21
+
22
+ Inputs: `assets/calibration/` ships a **hand-built known-correct SwiftUI
23
+ screen** + its **H5 twin** (must include both a text block and a non-text
24
+ color/shape block).
25
+
26
+ 1. Render H5 twin via `capture-reference.mjs` settings; render the SwiftUI
27
+ twin via `sim-screenshot.sh` for `--device`.
28
+ 2. Apply the normalization pipeline above → two co-registered logical
29
+ rasters + the `transform`.
30
+ 3. Run `pixel-diff.mjs` on the *known-correct* pair. The result is the
31
+ **best achievable** cross-renderer agreement for this exact toolchain:
32
+ - `floor.ssim_nontext` — SSIM over non-text regions (expect ≥ ~0.98)
33
+ - `floor.deltaE_p95` — 95th-pct CIEDE2000 over color regions
34
+ - `floor.text_iou` — layout-box IoU achievable for text regions
35
+ - `floor.ssim_global` — whole-screen SSIM of a *correct* screen (this is
36
+ the realistic ceiling, typically **< 0.995**, often ~0.97–0.99 with text)
37
+ 4. Sanity bound: if the known-correct pair cannot beat conservative bounds
38
+ (`ssim_nontext ≥ 0.95`, `text_iou ≥ 0.9`), the toolchain is **not
39
+ measurable** → write `blocked.json` and STOP. Never grade real screens
40
+ against an unproven metric.
41
+ 5. **Flat-content guard**: SSIM is insensitive to a uniform mean shift on
42
+ near-zero-variance (effectively flat) images — a clearly-divergent solid
43
+ pair can still score ~0.95. If BOTH normalized images are effectively flat
44
+ (luma variance below the flat threshold on each side), the floor is
45
+ unreliable → `blocked.json` + STOP, never a falsely-high floor. This is
46
+ why the bundled calibration content is **textured** (text + a multi-color
47
+ stripe region), not flat solid blocks — see
48
+ `../assets/calibration/README.md`.
49
+
50
+ ## `calibration.json` schema (consumed by Stage 5)
51
+
52
+ ```json
53
+ {
54
+ "schema": "h5-to-swiftui/calibration@1",
55
+ "pinned": {
56
+ "sim_runtime": "iOS 17.5 (21F79)",
57
+ "device": "iPhone 15 Pro",
58
+ "logical_size": [393, 852],
59
+ "render_scale": 3,
60
+ "browser": "chromium-1180",
61
+ "model_id": "claude-sonnet-4-6",
62
+ "temperature": 0
63
+ },
64
+ "transform": {
65
+ "dom_to_screen_scale": 1.0,
66
+ "safe_area_offset_pt": [0, 59],
67
+ "content_rect_pt": [0, 59, 393, 759],
68
+ "resample_filter": "lanczos3",
69
+ "color": "p3->srgb-relative"
70
+ },
71
+ "twin_hashes": {
72
+ "ref_png": "/abs/path/twin-ref.png",
73
+ "ref_sha256": "…64-hex…",
74
+ "gen_png": "/abs/path/twin-gen.png",
75
+ "gen_sha256": "…64-hex…"
76
+ },
77
+ "calibration_source": {
78
+ "h5_twin_dir": "assets/calibration/h5-twin",
79
+ "swiftui_twin_dir": "assets/calibration/swiftui-twin",
80
+ "h5_twin_source_sha256": "…64-hex…",
81
+ "swiftui_twin_source_sha256": "…64-hex…",
82
+ "source_tree_hash_algo": "sha256/sorted-relpath+filebytes/v1"
83
+ },
84
+ "floor": {
85
+ "ssim_global": 0.982,
86
+ "ssim_nontext": 0.991,
87
+ "deltaE_p95": 1.6,
88
+ "text_iou": null,
89
+ "luma_variance": { "ref": 612.4, "gen": 588.1, "flat_threshold": 9 }
90
+ },
91
+ "gate": {
92
+ "converged": {
93
+ "ssim_nontext_min": 0.986,
94
+ "deltaE_p95_max": 2.0,
95
+ "text_iou_min": null,
96
+ "require_judge_yes": true
97
+ },
98
+ "close": {
99
+ "ssim_nontext_min": 0.981,
100
+ "deltaE_p95_max": 2.4,
101
+ "text_iou_min": null,
102
+ "require_judge_equiv": true
103
+ },
104
+ "gate_explain": "human-readable only — code reads gate.converged / gate.close numerically"
105
+ },
106
+ "measured_at": "ISO8601"
107
+ }
108
+ ```
109
+
110
+ `gate.converged` / `gate.close` are **structured numeric objects**, not a
111
+ string DSL — `scripts/evaluate-convergence.mjs` evaluates them directly
112
+ without a parser (a legacy string gate is rejected as un-enforceable). They
113
+ are derived from the measured floor: `ssim_nontext_min = floor − 0.005`,
114
+ `deltaE_p95_max = floor + 0.4`, `text_iou_min = floor − 0.03` (null ⇒ the
115
+ text-IoU sub-gate is skipped for that calibration); `close` is the 2× band.
116
+ `gate_explain` is a human-only convenience string and is **never** evaluated
117
+ by code. The numbers in `floor` are **measured per run**, not shipped
118
+ constants — different Xcode/sim versions yield different floors, which is
119
+ exactly why this stage exists. `floor.luma_variance` records each side's
120
+ structure (a both-flat pair is blocked, never floored).
121
+
122
+ ### Provenance binding (enforced, not advisory)
123
+
124
+ `scripts/evaluate-convergence.mjs` **mechanically binds** the floor it grades
125
+ against — these are code-enforced fail-closed checks, not prose:
126
+
127
+ 1. **schema** — `calibration.json.schema` MUST be
128
+ `h5-to-swiftui/calibration@1`, else the grader exits 1 (it refuses to
129
+ grade against an unrecognized contract).
130
+ 2. **gate ⇐ floor** — the grader **recomputes** the expected structured gate
131
+ from `floor` with the published tolerances (converged
132
+ `ssim_nontext_min = floor − 0.005`, `deltaE_p95_max = floor + 0.4`,
133
+ `text_iou_min = floor − 0.03` when non-null; `close` = 2× band) and
134
+ rejects any `gate` that deviates beyond a 1e-4 rounding epsilon
135
+ (`gate-floor-mismatch`, exit 1). This binds the **gate to the floor**: a
136
+ hand-loosened `gate` (tight-looking `floor` + loose `gate`) is rejected
137
+ unless the `floor` itself is loosened. It does **not** bind the floor's
138
+ *value* — that is checks 3 and 4.
139
+ 3. **twin source identity ⇐ shipped twins** — `calibration_source` carries a
140
+ **deterministic SHA-256 source-tree hash** of the bundled
141
+ `assets/calibration/h5-twin` and `assets/calibration/swiftui-twin`
142
+ **source files (excluding build output/dotfiles)**. The grader
143
+ **recomputes those hashes from the actual shipped assets** (resolved
144
+ relative to the skill root, not the cwd) and fails closed
145
+ (`calibration-twin-mismatch`, exit 1) on any mismatch. This binds the
146
+ **identity of the bundled twin source files**. It does **not** re-measure
147
+ or bind the `floor` *value*: an attacker can keep the real, public,
148
+ unmodified bundled-twin source hashes here and still write a loose
149
+ `floor`. (The *rendered* PNGs are runtime-produced and not byte-stable
150
+ across machines, so the SOURCE tree — fixed and shippable — is what is
151
+ bound. `twin_hashes` is non-security provenance metadata of the exact PNGs
152
+ the floor was measured on; the *enforced* binding is the source-tree hash
153
+ of the source files.)
154
+ 4. **floor value ⇐ calibrate-render's own sanity envelope** — the grader
155
+ asserts `floor` satisfies the SAME sanity bound `calibrate-render.mjs`
156
+ enforces before it is willing to emit `calibration.json` at all
157
+ (`ssim_nontext ≥ 0.95`, non-null `text_iou ≥ 0.9`, metric-valid
158
+ `deltaE_p95`), via the shared `scripts/_calib-consts.mjs` (single source
159
+ of truth, imported by both producer and consumer). A `floor` that fails
160
+ this envelope could not have been produced by an honest
161
+ `calibrate-render.mjs` run (it writes `blocked.json`, not
162
+ `calibration.json`) ⇒ `floor-implausible`, exit 1. This **kills the
163
+ absurd-floor attack** (e.g. `ssim_nontext:0.05, deltaE_p95:200` with the
164
+ real public twin hashes copied in). It does **not** re-measure the floor:
165
+ a `floor` *within* this envelope yet looser than the TRUE measured floor
166
+ is still trusted — the grader cannot re-render the bundled twins to
167
+ re-derive the real number.
168
+
169
+ **Named irreducible residuals (honest, per §1.1) — BOTH stated, neither
170
+ hidden:**
171
+
172
+ 1. **The grader cannot re-execute the simulator renders.** It trusts the
173
+ per-iteration `pixel-diff.mjs` JSONs were produced by running the real
174
+ `pixel-diff.mjs` on real `sim-screenshot.sh` renders (bounded by that
175
+ script's no-fake build/env spine — no simulator / no build ⇒
176
+ `blocked`/`needs-human`, never converged).
177
+ 2. **The grader cannot re-measure the calibration floor.** Check 4 asserts
178
+ the supplied `floor` is within calibrate-render's own sanity envelope and
179
+ the gate is recomputed from it, but a `floor` *within* that envelope yet
180
+ looser than the true measured floor is trusted (the grader cannot
181
+ re-render the bundled twins to re-derive the real number). Mitigated by:
182
+ the orchestrator's contractual obligation to run the real,
183
+ sanity/flat-image-spined `calibrate-render.mjs`, and the human-readable
184
+ `calibration_provenance` recorded in the convergence artifact.
185
+
186
+ Both residuals are irreducible without the grader itself re-rendering; they
187
+ are stated, not hidden.
188
+
189
+ ## Reproducibility rule
190
+
191
+ Re-running calibration on the same pinned toolchain must produce `floor`
192
+ values within ±0.005 SSIM / ±0.3 ΔE. Larger drift ⇒ environment is unstable;
193
+ record it and degrade the run's claims (Stage 5 verdicts become advisory).
@@ -0,0 +1,160 @@
1
+ # Stack Detection — Stage 0
2
+
3
+ Used by `scripts/detect-stack.mjs`. Runs **before any other stage**. Determines
4
+ whether the project is in v1 scope; out-of-scope projects stop here with an
5
+ explicit written report — they are never guessed past.
6
+
7
+ ---
8
+
9
+ ## Why native rewrite beats WebView shell and mechanical transpilation
10
+
11
+ Condensed from findings.md RQ1 (sources: kean.blog; Apple "Composing custom
12
+ layouts" WWDC22-10056; MDN Specificity; LogRocket / Yoga 3.0; dbushell; bjango;
13
+ fatbobman; MDN animations; tonsky.me; Android→iOS pilot 2507.16037).
14
+
15
+ | Route | Fidelity ceiling | Core failure mechanism |
16
+ |---|---|---|
17
+ | WebView shell (Capacitor / Cordova / Ionic) | ~70–80% | Renders in WebKit, not Core Animation/Metal; ProMotion 120 Hz unavailable to WebKit-rendered content; Dynamic Type needs full reload; rubber-band scroll constant diverges; haptics absent; 2 extra OS processes per instance |
18
+ | React Native / NativeScript | ~85–92% | Yoga bridge drops 5 CSS flex properties (`flex-basis`, `flex` shorthand, `wrap-reverse`, `order`, column/row-gap individually); bridge latency; not SwiftUI semantics |
19
+ | Flutter (Impeller) | ~80–85% | Own GPU renderer independent of UIKit; iOS conventions require manual Cupertino override throughout |
20
+ | Mechanical transpilation (DOM→view tree) | **<60%** | Six structural incompatibilities listed below — errors compound per nesting level |
21
+ | **Native rewrite** (this skill) | **100% platform ceiling** | Requires a render-measure-feedback loop; see `visual-diff-loop-protocol.md` |
22
+
23
+ ### Six reasons mechanical transpilation cannot reach pixel-level
24
+
25
+ 1. **Inverted layout protocol.** CSS: parent establishes a containing block;
26
+ child size = content + padding + border + margin. SwiftUI: parent _proposes_
27
+ a size; child _chooses_; parent must honor it. The information-flow direction
28
+ is reversed. Reconstructing it from CSS is underdetermined for nested
29
+ layouts. (kean.blog; Apple "Composing custom layouts".)
30
+
31
+ 2. **No cascade.** CSS resolves styles by global selector specificity +
32
+ inheritance + `!important`. SwiftUI styling is local modifier-chain order +
33
+ downward `environment`. A transpiler needs a full cascade engine; any cascade
34
+ error is a visible defect. (MDN Specificity.)
35
+
36
+ 3. **No 1:1 flex mapping.** Yoga (the most mature CSS-flex bridge) drops
37
+ `flex-basis`, the `flex` shorthand, `wrap-reverse`, `order`, and individual
38
+ `row-gap`/`column-gap`. Errors compound per nesting level. (LogRocket /
39
+ Yoga 3.0.)
40
+
41
+ 4. **Font metric divergence.** WebKit implements `line-height: normal ≈ 1.2`
42
+ against the CSS font spec; SwiftUI uses CoreText metrics directly. Identical
43
+ `16px` declarations yield different line height, advance width, and
44
+ baseline — different wrap column — all subsequent layout shifts. (dbushell;
45
+ bjango.)
46
+
47
+ 5. **Animation model mismatch.** CSS = timeline keyframes on absolute time;
48
+ SwiftUI = state-diff interpolation on Core Animation. Mechanical
49
+ `@keyframes` → `.animation()` diverges in timing, easing, and
50
+ interruptibility. (fatbobman; MDN animations.)
51
+
52
+ 6. **Non-deterministic adaptive spacing.** SwiftUI inserts type-dependent
53
+ spacing and context-sensitive control styling with no CSS source to
54
+ translate from. (tonsky.me.)
55
+
56
+ **Takeaway:** native rewrite is the only route to the 100% ceiling; transpilation
57
+ is at best a draft generator; a render-diff feedback loop is mandatory because
58
+ these divergences are invisible to static code diffing.
59
+
60
+ ---
61
+
62
+ ## Detection heuristics table
63
+
64
+ `detect-stack.mjs` reads `package.json` + a shallow source scan (`.js/.ts/.jsx/
65
+ .tsx/.vue/.svelte` in `src/` or root, max 500 files). No network calls.
66
+
67
+ ### Framework detection
68
+
69
+ | Framework | `package.json` deps (any of) | Source signatures |
70
+ |---|---|---|
71
+ | React | `react`, `react-dom`, `@types/react` | `import React`, `JSX.Element`, `.jsx`/`.tsx` extensions, `ReactDOM.render`, `createRoot` |
72
+ | Vue | `vue`, `@vue/core`, `nuxt` | `.vue` files, `<template>` + `<script setup>`, `createApp` |
73
+ | Svelte | `svelte`, `@sveltejs/kit` | `.svelte` files, `<script>` + `<style>` co-located, `$:` reactivity |
74
+ | Angular | `@angular/core`, `@angular/common` | `@Component`, `@NgModule`, `.component.ts` suffix pattern |
75
+ | Solid | `solid-js`, `@solidjs/router` | `createSignal`, `createEffect`, `.jsx` with no `react` dep |
76
+ | Vanilla | none of the above in deps | Bare `addEventListener`, no framework imports, `.js`/`.ts` only |
77
+
78
+ Confidence scoring: `high` = dep present + source signature found; `medium` =
79
+ dep only (source scan inconclusive); `low` = source signature only (dep absent,
80
+ e.g. CDN-loaded). At `low` confidence, `detect-stack.mjs` logs a warning and
81
+ continues; `in_v1_scope` reflects the most-likely classification.
82
+
83
+ ### Build tool detection
84
+
85
+ | Build tool | Primary signal | Secondary signal |
86
+ |---|---|---|
87
+ | Vite | `vite` in deps or devDeps; `vite.config.{js,ts}` present | `"dev": "vite"` in scripts |
88
+ | webpack | `webpack` in deps; `webpack.config.{js,cjs,mjs}` present | `"build": "webpack"` in scripts |
89
+ | Next.js | `next` in deps; `next.config.{js,mjs,ts}` present | `pages/` or `app/` dir with `page.{tsx,jsx}` |
90
+ | Nuxt | `nuxt` in deps; `nuxt.config.{ts,js}` present | `pages/` dir with `.vue` files |
91
+ | CRA | `react-scripts` in deps | `"start": "react-scripts start"` in scripts |
92
+ | None | none of the above | Single `index.html` + inline `<script>` or `<script type=module>` |
93
+
94
+ ### Styling detection
95
+
96
+ | Styling system | Detection signal |
97
+ |---|---|
98
+ | Tailwind v3 | `tailwindcss@^3` in deps; `tailwind.config.{js,ts}` present; class names like `text-sm`, `flex`, `bg-gray-100` in source |
99
+ | Tailwind v4 | `tailwindcss@^4` in deps; `@import "tailwindcss"` or `@theme` block in a `.css` file; no `tailwind.config.*` |
100
+ | CSS Modules | `.module.css` / `.module.scss` files present; `import styles from '…module.css'` in source |
101
+ | Sass / SCSS | `.scss` or `.sass` files; `sass` or `node-sass` in deps |
102
+ | CSS-in-JS | `styled-components`, `@emotion/react`, `@emotion/styled`, `@stitches/react`, or `linaria` in deps |
103
+ | Plain CSS | None of the above; `.css` files imported directly |
104
+
105
+ ### Router detection
106
+
107
+ | Router | Detection signal |
108
+ |---|---|
109
+ | React Router | `react-router-dom` or `react-router` in deps |
110
+ | TanStack Router | `@tanstack/react-router` in deps |
111
+ | Next.js router | Next.js detected (built-in) |
112
+ | Vue Router | `vue-router` in deps |
113
+ | Wouter | `wouter` in deps |
114
+ | None | Single-page with no router dep |
115
+
116
+ ---
117
+
118
+ ## v1 scope gate
119
+
120
+ **v1 supports: framework ∈ {vanilla, React}.**
121
+
122
+ All other detected frameworks (Vue, Svelte, Angular, Solid) are **out of v1 scope**.
123
+ `detect-stack.mjs` writes `stack-report.json` and **exits with code 2** (not 0,
124
+ not 1 — pipeline scripts detect this as a scope-stop, not an error).
125
+
126
+ The pipeline does **not** attempt to convert an out-of-scope project. It does not
127
+ guess. It does not warn and continue. It stops cleanly so the caller knows exactly
128
+ what happened.
129
+
130
+ ### `stack-report.json` schema
131
+
132
+ ```json
133
+ {
134
+ "schema": "h5-to-swiftui/stack-report@1",
135
+ "framework": "vue",
136
+ "buildTool": "vite",
137
+ "styling": ["css-modules", "tailwind-v3"],
138
+ "router": "vue-router",
139
+ "confidence": "high",
140
+ "in_v1_scope": false,
141
+ "stop_reason": "detected=vue, out of v1 scope; v1 supports vanilla|React only",
142
+ "detected_at": "2026-05-19T12:00:00Z"
143
+ }
144
+ ```
145
+
146
+ | Field | Type | Notes |
147
+ |---|---|---|
148
+ | `framework` | string | `vanilla` \| `react` \| `vue` \| `svelte` \| `angular` \| `solid` \| `unknown` |
149
+ | `buildTool` | string | `vite` \| `webpack` \| `next` \| `nuxt` \| `cra` \| `none` \| `unknown` |
150
+ | `styling` | string[] | Array; a project may use multiple (e.g. `["tailwind-v3","css-modules"]`) |
151
+ | `router` | string | Primary detected router or `none` |
152
+ | `confidence` | string | `high` \| `medium` \| `low` |
153
+ | `in_v1_scope` | boolean | `true` only when `framework` is `vanilla` or `react` |
154
+ | `stop_reason` | string | Present only when `in_v1_scope: false` |
155
+ | `detected_at` | string | ISO-8601 timestamp |
156
+
157
+ When `in_v1_scope: true`, the pipeline proceeds to Stage 1. The `stack-report.json`
158
+ is retained as a Stage 0 artifact and its fields inform Stage 1 (token extraction
159
+ strategy differs for plain CSS vs Tailwind v4 vs CSS-in-JS) and Stage 4 (component
160
+ pattern differs for class components vs function+hooks vs vanilla DOM).