@tyroneross/build-loop 0.30.3

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 (389) hide show
  1. package/.agents/plugins/marketplace.json +10 -0
  2. package/.claude-plugin/marketplace.json +38 -0
  3. package/.claude-plugin/plugin.json +30 -0
  4. package/.codex-plugin/plugin.json +41 -0
  5. package/.cursor/rules/build-loop-surface.mdc +22 -0
  6. package/AGENTS.md +498 -0
  7. package/LICENSE +202 -0
  8. package/README.md +403 -0
  9. package/agents/alignment-checker.md +124 -0
  10. package/agents/api-assessor.md +136 -0
  11. package/agents/architecture-scout.md +185 -0
  12. package/agents/assessment-orchestrator.md +163 -0
  13. package/agents/build-orchestrator.md +198 -0
  14. package/agents/database-assessor.md +133 -0
  15. package/agents/design-contract-specialist.md +239 -0
  16. package/agents/fact-checker.md +69 -0
  17. package/agents/fix-critique.md +161 -0
  18. package/agents/frontend-assessor.md +136 -0
  19. package/agents/implementer.md +185 -0
  20. package/agents/independent-auditor.md +143 -0
  21. package/agents/mock-scanner.md +78 -0
  22. package/agents/optimize-runner.md +112 -0
  23. package/agents/overfitting-reviewer.md +96 -0
  24. package/agents/performance-assessor.md +159 -0
  25. package/agents/plan-critic.md +85 -0
  26. package/agents/promotion-reviewer.md +121 -0
  27. package/agents/recurring-pattern-detector.md +248 -0
  28. package/agents/retrospective-synthesizer.md +109 -0
  29. package/agents/root-cause-investigator.md +285 -0
  30. package/agents/scope-auditor.md +121 -0
  31. package/agents/security-reviewer.md +151 -0
  32. package/agents/self-improvement-architect.md +125 -0
  33. package/agents/synthesis-critic.md +99 -0
  34. package/agents/transcript-pattern-miner.md +165 -0
  35. package/agents/ui-validator.md +193 -0
  36. package/codex-skills/build-loop/SKILL.md +20 -0
  37. package/codex-skills/debug-loop/SKILL.md +18 -0
  38. package/codex-skills/handoff/SKILL.md +22 -0
  39. package/codex-skills/knowledge/SKILL.md +21 -0
  40. package/codex-skills/optimize/SKILL.md +18 -0
  41. package/codex-skills/research/SKILL.md +18 -0
  42. package/commands/agent-rally-point.md +301 -0
  43. package/commands/assess.md +41 -0
  44. package/commands/debug.md +20 -0
  45. package/commands/debugger-detail.md +34 -0
  46. package/commands/debugger-scan.md +15 -0
  47. package/commands/debugger-status.md +20 -0
  48. package/commands/debugger.md +32 -0
  49. package/commands/handoff.md +113 -0
  50. package/commands/knowledge-review.md +16 -0
  51. package/commands/optimize.md +31 -0
  52. package/commands/plan-verify.md +39 -0
  53. package/commands/promote-experiment.md +129 -0
  54. package/commands/research.md +22 -0
  55. package/commands/run.md +12 -0
  56. package/commands/self-review.md +51 -0
  57. package/commands/start-prd.md +57 -0
  58. package/commands/test.md +34 -0
  59. package/dist/src/assessment-orchestrator.d.ts +78 -0
  60. package/dist/src/assessment-orchestrator.d.ts.map +1 -0
  61. package/dist/src/assessment-orchestrator.js +304 -0
  62. package/dist/src/assessment-orchestrator.js.map +1 -0
  63. package/dist/src/audit-miner.d.ts +21 -0
  64. package/dist/src/audit-miner.d.ts.map +1 -0
  65. package/dist/src/audit-miner.js +421 -0
  66. package/dist/src/audit-miner.js.map +1 -0
  67. package/dist/src/batch-operations.d.ts +30 -0
  68. package/dist/src/batch-operations.d.ts.map +1 -0
  69. package/dist/src/batch-operations.js +339 -0
  70. package/dist/src/batch-operations.js.map +1 -0
  71. package/dist/src/config.d.ts +83 -0
  72. package/dist/src/config.d.ts.map +1 -0
  73. package/dist/src/config.js +176 -0
  74. package/dist/src/config.js.map +1 -0
  75. package/dist/src/context-engine.d.ts +45 -0
  76. package/dist/src/context-engine.d.ts.map +1 -0
  77. package/dist/src/context-engine.js +135 -0
  78. package/dist/src/context-engine.js.map +1 -0
  79. package/dist/src/debug-wrapper.d.ts +58 -0
  80. package/dist/src/debug-wrapper.d.ts.map +1 -0
  81. package/dist/src/debug-wrapper.js +285 -0
  82. package/dist/src/debug-wrapper.js.map +1 -0
  83. package/dist/src/index.d.ts +31 -0
  84. package/dist/src/index.d.ts.map +1 -0
  85. package/dist/src/index.js +149 -0
  86. package/dist/src/index.js.map +1 -0
  87. package/dist/src/interactive-verifier.d.ts +26 -0
  88. package/dist/src/interactive-verifier.d.ts.map +1 -0
  89. package/dist/src/interactive-verifier.js +435 -0
  90. package/dist/src/interactive-verifier.js.map +1 -0
  91. package/dist/src/lessons.d.ts +59 -0
  92. package/dist/src/lessons.d.ts.map +1 -0
  93. package/dist/src/lessons.js +172 -0
  94. package/dist/src/lessons.js.map +1 -0
  95. package/dist/src/log-reader.d.ts +39 -0
  96. package/dist/src/log-reader.d.ts.map +1 -0
  97. package/dist/src/log-reader.js +291 -0
  98. package/dist/src/log-reader.js.map +1 -0
  99. package/dist/src/logger.d.ts +59 -0
  100. package/dist/src/logger.d.ts.map +1 -0
  101. package/dist/src/logger.js +328 -0
  102. package/dist/src/logger.js.map +1 -0
  103. package/dist/src/parallel-retrieval.d.ts +52 -0
  104. package/dist/src/parallel-retrieval.d.ts.map +1 -0
  105. package/dist/src/parallel-retrieval.js +309 -0
  106. package/dist/src/parallel-retrieval.js.map +1 -0
  107. package/dist/src/pattern-extractor.d.ts +30 -0
  108. package/dist/src/pattern-extractor.d.ts.map +1 -0
  109. package/dist/src/pattern-extractor.js +386 -0
  110. package/dist/src/pattern-extractor.js.map +1 -0
  111. package/dist/src/result-aggregator.d.ts +44 -0
  112. package/dist/src/result-aggregator.d.ts.map +1 -0
  113. package/dist/src/result-aggregator.js +315 -0
  114. package/dist/src/result-aggregator.js.map +1 -0
  115. package/dist/src/retrieval.d.ts +139 -0
  116. package/dist/src/retrieval.d.ts.map +1 -0
  117. package/dist/src/retrieval.js +724 -0
  118. package/dist/src/retrieval.js.map +1 -0
  119. package/dist/src/setup/auto-setup.d.ts +15 -0
  120. package/dist/src/setup/auto-setup.d.ts.map +1 -0
  121. package/dist/src/setup/auto-setup.js +157 -0
  122. package/dist/src/setup/auto-setup.js.map +1 -0
  123. package/dist/src/setup/configure-hooks.d.ts +8 -0
  124. package/dist/src/setup/configure-hooks.d.ts.map +1 -0
  125. package/dist/src/setup/configure-hooks.js +16 -0
  126. package/dist/src/setup/configure-hooks.js.map +1 -0
  127. package/dist/src/setup/create-slash-commands.d.ts +8 -0
  128. package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
  129. package/dist/src/setup/create-slash-commands.js +101 -0
  130. package/dist/src/setup/create-slash-commands.js.map +1 -0
  131. package/dist/src/setup/index.d.ts +8 -0
  132. package/dist/src/setup/index.d.ts.map +1 -0
  133. package/dist/src/setup/index.js +17 -0
  134. package/dist/src/setup/index.js.map +1 -0
  135. package/dist/src/setup/inject-claude-md.d.ts +10 -0
  136. package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
  137. package/dist/src/setup/inject-claude-md.js +150 -0
  138. package/dist/src/setup/inject-claude-md.js.map +1 -0
  139. package/dist/src/setup/uninstall.d.ts +22 -0
  140. package/dist/src/setup/uninstall.d.ts.map +1 -0
  141. package/dist/src/setup/uninstall.js +235 -0
  142. package/dist/src/setup/uninstall.js.map +1 -0
  143. package/dist/src/storage.d.ts +192 -0
  144. package/dist/src/storage.d.ts.map +1 -0
  145. package/dist/src/storage.js +1037 -0
  146. package/dist/src/storage.js.map +1 -0
  147. package/dist/src/string-similarity.d.ts +8 -0
  148. package/dist/src/string-similarity.d.ts.map +1 -0
  149. package/dist/src/string-similarity.js +57 -0
  150. package/dist/src/string-similarity.js.map +1 -0
  151. package/dist/src/traces/adapters/browser.d.ts +69 -0
  152. package/dist/src/traces/adapters/browser.d.ts.map +1 -0
  153. package/dist/src/traces/adapters/browser.js +280 -0
  154. package/dist/src/traces/adapters/browser.js.map +1 -0
  155. package/dist/src/traces/adapters/index.d.ts +23 -0
  156. package/dist/src/traces/adapters/index.d.ts.map +1 -0
  157. package/dist/src/traces/adapters/index.js +67 -0
  158. package/dist/src/traces/adapters/index.js.map +1 -0
  159. package/dist/src/traces/adapters/langchain.d.ts +42 -0
  160. package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
  161. package/dist/src/traces/adapters/langchain.js +151 -0
  162. package/dist/src/traces/adapters/langchain.js.map +1 -0
  163. package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
  164. package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
  165. package/dist/src/traces/adapters/opentelemetry.js +164 -0
  166. package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
  167. package/dist/src/traces/adapters/sentry.d.ts +58 -0
  168. package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
  169. package/dist/src/traces/adapters/sentry.js +212 -0
  170. package/dist/src/traces/adapters/sentry.js.map +1 -0
  171. package/dist/src/traces/index.d.ts +10 -0
  172. package/dist/src/traces/index.d.ts.map +1 -0
  173. package/dist/src/traces/index.js +45 -0
  174. package/dist/src/traces/index.js.map +1 -0
  175. package/dist/src/traces/storage.d.ts +78 -0
  176. package/dist/src/traces/storage.d.ts.map +1 -0
  177. package/dist/src/traces/storage.js +399 -0
  178. package/dist/src/traces/storage.js.map +1 -0
  179. package/dist/src/traces/summarizer.d.ts +83 -0
  180. package/dist/src/traces/summarizer.d.ts.map +1 -0
  181. package/dist/src/traces/summarizer.js +247 -0
  182. package/dist/src/traces/summarizer.js.map +1 -0
  183. package/dist/src/traces/types.d.ts +365 -0
  184. package/dist/src/traces/types.d.ts.map +1 -0
  185. package/dist/src/traces/types.js +14 -0
  186. package/dist/src/traces/types.js.map +1 -0
  187. package/dist/src/types.d.ts +495 -0
  188. package/dist/src/types.d.ts.map +1 -0
  189. package/dist/src/types.js +27 -0
  190. package/dist/src/types.js.map +1 -0
  191. package/docs/agent-surface-policy.md +45 -0
  192. package/docs/memory-setup.md +226 -0
  193. package/hooks/_arch_scan_bg.py +88 -0
  194. package/hooks/_plugin_drift_check_bg.py +85 -0
  195. package/hooks/_session_start_lib.sh +42 -0
  196. package/hooks/git/pre-push +141 -0
  197. package/hooks/hooks.json +126 -0
  198. package/hooks/post-commit +30 -0
  199. package/hooks/pre-commit +29 -0
  200. package/hooks/pre-edit-apppulse.sh +6 -0
  201. package/hooks/pre-edit-architecture.sh +106 -0
  202. package/hooks/pre-edit-rally-point.sh +92 -0
  203. package/hooks/session-start-apppulse.sh +6 -0
  204. package/hooks/session-start-architecture.sh +10 -0
  205. package/hooks/session-start-git-hooks.sh +12 -0
  206. package/hooks/session-start-plugin-drift.sh +27 -0
  207. package/hooks/session-start-rally-point.sh +44 -0
  208. package/hooks/session-start-retrieval.sh +24 -0
  209. package/hooks/session-start-worktree-gc.sh +105 -0
  210. package/hooks/test_apppulse_hooks.sh +6 -0
  211. package/hooks/test_rally_point_hooks.sh +388 -0
  212. package/hooks/test_sec008_hooks.sh +66 -0
  213. package/package.json +111 -0
  214. package/scripts/README.md +301 -0
  215. package/scripts/_db_url.py +61 -0
  216. package/scripts/_paths.py +445 -0
  217. package/scripts/blm.py +162 -0
  218. package/scripts/blm_api.py +263 -0
  219. package/scripts/check_cache_sync.py +490 -0
  220. package/scripts/embed_backend.py +475 -0
  221. package/scripts/install_memory.py +556 -0
  222. package/scripts/lessons_index/__init__.py +77 -0
  223. package/scripts/lessons_index/__main__.py +135 -0
  224. package/scripts/lessons_index/ingest.py +288 -0
  225. package/scripts/lessons_index/query.py +285 -0
  226. package/scripts/lessons_index/schema.py +114 -0
  227. package/scripts/memory_context/__init__.py +610 -0
  228. package/scripts/memory_graph/__init__.py +393 -0
  229. package/scripts/project_resolver.py +172 -0
  230. package/scripts/prune_codex_plugin_cache.py +23 -0
  231. package/scripts/prune_plugin_cache.py +359 -0
  232. package/scripts/semantic_index/__init__.py +482 -0
  233. package/scripts/semantic_index/_bench_hybrid.py +175 -0
  234. package/scripts/semantic_index/backfill.py +263 -0
  235. package/scripts/semantic_index/hybrid.py +174 -0
  236. package/scripts/semantic_index/test_backfill.py +292 -0
  237. package/scripts/semantic_index/test_hybrid.py +541 -0
  238. package/scripts/sync_navgator_lessons.py +666 -0
  239. package/scripts/sync_plugin_cache.py +388 -0
  240. package/skills/agent-rally-point/SKILL.md +165 -0
  241. package/skills/agent-rally-watcher/SKILL.md +83 -0
  242. package/skills/api-registry-bridge/SKILL.md +59 -0
  243. package/skills/architecture/dead/SKILL.md +56 -0
  244. package/skills/architecture/impact/SKILL.md +88 -0
  245. package/skills/architecture/review/SKILL.md +227 -0
  246. package/skills/architecture/rules/SKILL.md +72 -0
  247. package/skills/architecture/scan/SKILL.md +79 -0
  248. package/skills/architecture/trace/SKILL.md +65 -0
  249. package/skills/attribution-standard/SKILL.md +122 -0
  250. package/skills/authentication/SKILL.md +81 -0
  251. package/skills/authentication/references/better-auth-magic-link.md +135 -0
  252. package/skills/authentication/references/better-auth-setup.md +224 -0
  253. package/skills/authentication/references/google-calendar-sync.md +278 -0
  254. package/skills/authentication/references/google-cloud-console.md +159 -0
  255. package/skills/authentication/references/google-geocoding-directions.md +126 -0
  256. package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
  257. package/skills/authentication/references/google-maps.md +162 -0
  258. package/skills/authentication/references/google-oauth-setup.md +343 -0
  259. package/skills/authentication/references/google-places.md +173 -0
  260. package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
  261. package/skills/authentication/references/resend-email.md +227 -0
  262. package/skills/authentication/references/resend-otp-magic-link.md +153 -0
  263. package/skills/authentication/references/supabase-auth.md +172 -0
  264. package/skills/auto-decision-capture/SKILL.md +419 -0
  265. package/skills/build-loop/SKILL.md +516 -0
  266. package/skills/build-loop/detect-plugins.mjs +82 -0
  267. package/skills/build-loop/eval-guide.md +65 -0
  268. package/skills/build-loop/fallbacks.md +549 -0
  269. package/skills/build-loop/phases/fact-check.md +42 -0
  270. package/skills/build-loop/phases/ui-validation.md +267 -0
  271. package/skills/build-loop/references/apple-native-planning.md +439 -0
  272. package/skills/build-loop/references/capability-routing.md +181 -0
  273. package/skills/build-loop/references/codex-subagents.md +98 -0
  274. package/skills/build-loop/references/coordination.md +161 -0
  275. package/skills/build-loop/references/correction-aware-capture.md +177 -0
  276. package/skills/build-loop/references/independent-auditor.md +72 -0
  277. package/skills/build-loop/references/intent-capability-pack.md +170 -0
  278. package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
  279. package/skills/build-loop/references/leadership.md +72 -0
  280. package/skills/build-loop/references/memory.md +284 -0
  281. package/skills/build-loop/references/modular-systems-pack.md +96 -0
  282. package/skills/build-loop/references/output-style.md +129 -0
  283. package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
  284. package/skills/build-loop/references/phase-1-assess.md +218 -0
  285. package/skills/build-loop/references/phase-2-plan.md +77 -0
  286. package/skills/build-loop/references/phase-3-execute.md +47 -0
  287. package/skills/build-loop/references/phase-4-review.md +310 -0
  288. package/skills/build-loop/references/phase-5-iterate.md +71 -0
  289. package/skills/build-loop/references/phase-6-learn.md +56 -0
  290. package/skills/build-loop/references/recent-design-structures.md +274 -0
  291. package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
  292. package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
  293. package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
  294. package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
  295. package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
  296. package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
  297. package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
  298. package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
  299. package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
  300. package/skills/build-loop/references/self-recursive-dev.md +77 -0
  301. package/skills/build-loop/references/self-review.md +232 -0
  302. package/skills/build-loop/references/ui-io-contract.md +116 -0
  303. package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
  304. package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
  305. package/skills/build-loop/templates/backlog-item.md +35 -0
  306. package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
  307. package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
  308. package/skills/build-loop/templates/ux-fix-plan.md +40 -0
  309. package/skills/building-with-deepagents/SKILL.md +195 -0
  310. package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
  311. package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
  312. package/skills/capabilities/SKILL.md +91 -0
  313. package/skills/debug-loop/SKILL.md +278 -0
  314. package/skills/debug-loop/references/convergence-rules.md +145 -0
  315. package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
  316. package/skills/debugging/assess/SKILL.md +111 -0
  317. package/skills/debugging/debug-loop/SKILL.md +231 -0
  318. package/skills/debugging/memory/SKILL.md +147 -0
  319. package/skills/debugging/store/SKILL.md +153 -0
  320. package/skills/debugging-memory/SKILL.md +440 -0
  321. package/skills/debugging-memory/examples/incident-example.json +67 -0
  322. package/skills/debugging-memory/references/incident-documentation.md +280 -0
  323. package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
  324. package/skills/debugging-memory/references/pattern-extraction.md +163 -0
  325. package/skills/debugging-memory/references/subagent-integration.md +312 -0
  326. package/skills/defenseclaw-bridge/SKILL.md +156 -0
  327. package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
  328. package/skills/defenseclaw-bridge/references/output-format.md +108 -0
  329. package/skills/handoff/SKILL.md +94 -0
  330. package/skills/ibr-bridge/SKILL.md +68 -0
  331. package/skills/knowledge/SKILL.md +127 -0
  332. package/skills/knowledge/references/recall-integration.md +83 -0
  333. package/skills/knowledge/templates/madr-minimal.md +56 -0
  334. package/skills/knowledge-review/SKILL.md +112 -0
  335. package/skills/logging-tracer/SKILL.md +202 -0
  336. package/skills/logging-tracer/references/log-analysis.md +128 -0
  337. package/skills/logging-tracer/references/stack-templates.md +274 -0
  338. package/skills/logging-tracer-bridge/SKILL.md +70 -0
  339. package/skills/mcp-builder/SKILL.md +278 -0
  340. package/skills/mcp-builder/references/mcp-security.md +106 -0
  341. package/skills/model-tiering/SKILL.md +216 -0
  342. package/skills/native-ax-driver/SKILL.md +220 -0
  343. package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
  344. package/skills/native-ax-driver/scripts/native_driver.py +495 -0
  345. package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
  346. package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
  347. package/skills/optimize/SKILL.md +202 -0
  348. package/skills/optimize/profiles.md +61 -0
  349. package/skills/plan-verify/SKILL.md +115 -0
  350. package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
  351. package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
  352. package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
  353. package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
  354. package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
  355. package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
  356. package/skills/plugin-builder/SKILL.md +329 -0
  357. package/skills/plugin-builder/references/authoritative-sources.md +104 -0
  358. package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
  359. package/skills/plugin-builder/references/components-guide.md +221 -0
  360. package/skills/plugin-builder/references/distribution.md +317 -0
  361. package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
  362. package/skills/plugin-builder/references/hooks-reference.md +210 -0
  363. package/skills/plugin-builder/references/manifest-schema.md +111 -0
  364. package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
  365. package/skills/plugin-tests/SKILL.md +94 -0
  366. package/skills/prd-bridge/SKILL.md +135 -0
  367. package/skills/research/SKILL.md +80 -0
  368. package/skills/security-methodology/SKILL.md +105 -0
  369. package/skills/security-methodology/references/cross-source-matrix.md +77 -0
  370. package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
  371. package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
  372. package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
  373. package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
  374. package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
  375. package/skills/self-improve/SKILL.md +298 -0
  376. package/skills/spec-writing/SKILL.md +548 -0
  377. package/skills/spec-writing/scripts/check_checklist.py +478 -0
  378. package/skills/sync-skills/SKILL.md +132 -0
  379. package/skills/telemetry/SKILL.md +66 -0
  380. package/skills/ui-design/SKILL.md +75 -0
  381. package/skills/ui-design/references/design-patterns-multi.md +165 -0
  382. package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
  383. package/skills/ui-design/references/evidence-capture-policy.md +55 -0
  384. package/skills/ui-design/references/ui-guidance-sources.md +330 -0
  385. package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
  386. package/skills/ui-design/references/universal-design-principles.md +419 -0
  387. package/templates/memory/MEMORY.md.template +55 -0
  388. package/templates/memory/constitution.md.template +64 -0
  389. package/templates/memory/manifest.json +47 -0
@@ -0,0 +1,823 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="slides-format" content="viewport">
7
+ <title>SwiftUI macOS: Clunky UI Debugging Explorer</title>
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
11
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
12
+ <style type="text/tailwindcss">
13
+ @theme {
14
+ --color-bg: #0d1117;
15
+ --color-bg-deep: #010409;
16
+ --color-surface: #161b22;
17
+ --color-text: #e6edf3;
18
+ --color-text-secondary: #8b949e;
19
+ --color-text-muted: #484f58;
20
+ --color-accent-1: #58a6ff;
21
+ --color-accent-2: #3fb950;
22
+ --color-accent-3: #ff7b72;
23
+ --color-accent-4: #d2a8ff;
24
+ --color-glass-bg: rgba(22,27,34,0.85);
25
+ --color-glass-border: rgba(48,54,61,0.8);
26
+ --color-vignette: rgba(0,0,0,0.5);
27
+ --font-display: 'JetBrains Mono', monospace;
28
+ --font-body: 'Inter', sans-serif;
29
+ }
30
+ </style>
31
+ <style>
32
+ :root {
33
+ --color-bg: #0d1117;
34
+ --color-text: #e6edf3;
35
+ --glow-color-rgb: 88,166,255;
36
+ }
37
+ *, *::before, *::after { box-sizing: border-box; }
38
+ html, body { background: var(--color-bg); margin: 0; }
39
+ body { font-family: 'Inter', sans-serif; color: var(--color-text); overflow: hidden; height: 100vh; width: 100vw; }
40
+ .deck { width: 100vw; height: 100vh; position: relative; }
41
+ .slide { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: var(--color-bg); display: flex; flex-direction: column; align-items: center; justify-content: center; opacity: 0; transform: scale(0.95); transition: opacity 0.7s ease, transform 0.7s ease; pointer-events: none; overflow: hidden; }
42
+ .slide.active { opacity: 1; transform: scale(1); pointer-events: all; }
43
+ .slide > .content { position: relative; z-index: 2; width: 100%; max-width: 1100px; padding: clamp(1.5rem, 4vw, 4rem); }
44
+ .nav-controls { position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 16px; z-index: 100; background: rgba(22,27,34,0.9); backdrop-filter: blur(10px); padding: 10px 24px; border-radius: 40px; border: 1px solid rgba(48,54,61,0.8); }
45
+ .nav-btn { width: 38px; height: 38px; border: none; background: rgba(48,54,61,0.5); color: #58a6ff; border-radius: 50%; font-size: 1.2rem; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.3s; }
46
+ .nav-btn:hover { background: rgba(88,166,255,0.15); }
47
+ .slide-dots { display: flex; gap: 7px; }
48
+ .slide-dots .dot { width: 9px; height: 9px; border-radius: 50%; background: rgba(48,54,61,0.8); cursor: pointer; transition: background 0.3s, transform 0.3s; }
49
+ .slide-dots .dot.active { background: #58a6ff; transform: scale(1.3); }
50
+ .slide-counter { font-size: 0.75rem; color: #484f58; min-width: 40px; text-align: center; font-family: 'JetBrains Mono', monospace; }
51
+ .reveal { opacity: 0; transform: translateY(18px); }
52
+ .gradient-mesh { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }
53
+ .blob { position: absolute; border-radius: 50%; filter: blur(80px); animation: float-slow 12s ease-in-out infinite; }
54
+ .blob:nth-child(2) { animation: float-drift 16s ease-in-out infinite; }
55
+ .blob:nth-child(3) { animation: float-slow 20s ease-in-out infinite reverse; }
56
+ @keyframes float-slow { 0%{transform:translate(0,0) scale(1)} 25%{transform:translate(50px,-40px) scale(1.1)} 50%{transform:translate(-30px,35px) scale(0.92)} 75%{transform:translate(40px,15px) scale(1.07)} 100%{transform:translate(0,0) scale(1)} }
57
+ @keyframes float-drift { 0%{transform:translate(0,0) scale(1) rotate(0deg)} 33%{transform:translate(-45px,-55px) scale(1.13) rotate(3deg)} 66%{transform:translate(38px,28px) scale(0.88) rotate(-2deg)} 100%{transform:translate(0,0) scale(1) rotate(0deg)} }
58
+ @keyframes float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-12px)} }
59
+ .slide::before { content:''; position:absolute; inset:0; z-index:1; pointer-events:none; background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.7' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.04'/%3E%3C/svg%3E"); background-repeat:repeat; background-size:256px 256px; opacity:0.025; mix-blend-mode:overlay; }
60
+ .slide::after { content:''; position:absolute; inset:0; z-index:1; pointer-events:none; background:radial-gradient(ellipse at center, transparent 50%, var(--color-vignette) 100%); }
61
+ .text-glow { text-shadow: 0 0 20px rgba(88,166,255,0.5), 0 0 40px rgba(88,166,255,0.2); }
62
+ .stat-glow { text-shadow: 0 0 25px rgba(63,185,80,0.6), 0 0 50px rgba(63,185,80,0.25); }
63
+ @keyframes glow-pulse { 0%,100%{text-shadow:0 0 20px rgba(88,166,255,0.5),0 0 40px rgba(88,166,255,0.2)} 50%{text-shadow:0 0 30px rgba(88,166,255,0.8),0 0 60px rgba(88,166,255,0.35)} }
64
+ .glow-pulse { animation: glow-pulse 3s ease-in-out infinite; }
65
+ @keyframes scanline { 0%{transform:translateY(-100%)} 100%{transform:translateY(100vh)} }
66
+ .code-block { background: #010409; border: 1px solid rgba(48,54,61,0.8); border-radius: 8px; padding: 1rem 1.25rem; font-family: 'JetBrains Mono', monospace; font-size: 0.78rem; line-height: 1.6; color: #e6edf3; overflow-x: auto; }
67
+ .code-block .kw { color: #ff7b72; }
68
+ .code-block .fn { color: #d2a8ff; }
69
+ .code-block .str { color: #a5d6ff; }
70
+ .code-block .cm { color: #484f58; font-style: italic; }
71
+ .code-block .ok { color: #3fb950; }
72
+ .code-block .warn { color: #f2cc60; }
73
+ .badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px; border-radius: 20px; font-size: 0.7rem; font-weight: 600; font-family: 'JetBrains Mono', monospace; letter-spacing: 0.05em; text-transform: uppercase; }
74
+ .badge-blue { background: rgba(88,166,255,0.12); border: 1px solid rgba(88,166,255,0.3); color: #58a6ff; }
75
+ .badge-green { background: rgba(63,185,80,0.12); border: 1px solid rgba(63,185,80,0.3); color: #3fb950; }
76
+ .badge-red { background: rgba(255,123,114,0.12); border: 1px solid rgba(255,123,114,0.3); color: #ff7b72; }
77
+ .badge-purple { background: rgba(210,168,255,0.12); border: 1px solid rgba(210,168,255,0.3); color: #d2a8ff; }
78
+ .badge-yellow { background: rgba(242,204,96,0.12); border: 1px solid rgba(242,204,96,0.3); color: #f2cc60; }
79
+ .diag-card { background: rgba(22,27,34,0.85); border: 1px solid rgba(48,54,61,0.8); border-radius: 10px; padding: 1.1rem 1.25rem; transition: transform 0.2s, box-shadow 0.2s; }
80
+ .diag-card:hover { transform: translateY(-3px); box-shadow: 0 8px 32px rgba(88,166,255,0.1); }
81
+ .diag-card.warn-card { border-left: 3px solid #ff7b72; }
82
+ .diag-card.ok-card { border-left: 3px solid #3fb950; }
83
+ .diag-card.info-card { border-left: 3px solid #58a6ff; }
84
+ .diag-card.purple-card { border-left: 3px solid #d2a8ff; }
85
+ .flow-step { display: flex; align-items: flex-start; gap: 14px; }
86
+ .flow-num { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; font-weight: 700; flex-shrink: 0; font-family: 'JetBrains Mono', monospace; background: rgba(88,166,255,0.15); border: 1px solid rgba(88,166,255,0.3); color: #58a6ff; }
87
+ .component-row { display: flex; align-items: center; gap: 12px; padding: 0.65rem 0.9rem; background: rgba(22,27,34,0.85); border: 1px solid rgba(48,54,61,0.8); border-radius: 8px; }
88
+ .arrow-right { color: #484f58; font-size: 1rem; flex-shrink: 0; }
89
+ .bar-h { height: 10px; border-radius: 5px; background: linear-gradient(90deg, #58a6ff, #3fb950); margin-top: 4px; }
90
+ .terminal-header { display: flex; gap: 6px; align-items: center; padding: 0.5rem 0.75rem; background: #161b22; border-bottom: 1px solid rgba(48,54,61,0.8); border-radius: 8px 8px 0 0; }
91
+ .dot-red { width:10px; height:10px; border-radius:50%; background:#ff5f57; }
92
+ .dot-yellow { width:10px; height:10px; border-radius:50%; background:#febc2e; }
93
+ .dot-green { width:10px; height:10px; border-radius:50%; background:#28c840; }
94
+ </style>
95
+ </head>
96
+ <body>
97
+ <div class="mouse-spotlight" style="position:fixed;inset:0;z-index:99;pointer-events:none;"></div>
98
+ <div class="deck">
99
+
100
+ <!-- SLIDE 1: TITLE -->
101
+ <div class="slide slide-1 active" data-slide="1">
102
+ <div class="gradient-mesh">
103
+ <div class="blob" style="width:500px;height:500px;top:-120px;right:-100px;background:#58a6ff;opacity:0.1;"></div>
104
+ <div class="blob" style="width:350px;height:350px;bottom:-80px;left:-60px;background:#3fb950;opacity:0.08;"></div>
105
+ <div class="blob" style="width:220px;height:220px;top:45%;left:20%;background:#d2a8ff;opacity:0.07;"></div>
106
+ </div>
107
+ <canvas class="particle-canvas" style="position:absolute;inset:0;z-index:1;"></canvas>
108
+ <div class="content text-center">
109
+ <div style="font-size:80px;margin-bottom:1rem;animation:float 3s ease-in-out infinite;">🔬</div>
110
+ <h1 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(2rem,5vw,3.8rem);font-weight:700;background:linear-gradient(135deg,#58a6ff,#3fb950,#d2a8ff);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-0.02em;margin-bottom:0.75rem;">SwiftUI macOS<br>Clunky UI Debugger</div>
111
+ <p class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:0.85rem;color:#484f58;letter-spacing:0.12em;text-transform:uppercase;margin-bottom:1.5rem;">Diagnosis · Root Causes · Fix Patterns</p>
112
+ <div class="reveal" style="display:flex;gap:10px;justify-content:center;flex-wrap:wrap;">
113
+ <span class="badge badge-blue">Panel Jank</span>
114
+ <span class="badge badge-green">Wrong Components</span>
115
+ <span class="badge badge-red">Re-render Storms</span>
116
+ <span class="badge badge-purple">AppKit Bridging</span>
117
+ </div>
118
+ </div>
119
+ </div>
120
+
121
+ <!-- SLIDE 2: DIAGNOSIS FRAMEWORK -->
122
+ <div class="slide slide-2" data-slide="2">
123
+ <div class="gradient-mesh">
124
+ <div class="blob" style="width:350px;height:350px;top:-80px;right:-80px;background:#58a6ff;opacity:0.07;"></div>
125
+ <div class="blob" style="width:250px;height:250px;bottom:-60px;left:-40px;background:#3fb950;opacity:0.06;"></div>
126
+ </div>
127
+ <div class="content">
128
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-blue">// Step 0 — Before touching code</span></div>
129
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.5rem,3.5vw,2.5rem);font-weight:700;margin-bottom:1.5rem;">Clunky UI: Diagnostic Framework</h2>
130
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
131
+ <div class="diag-card warn-card reveal">
132
+ <div style="font-size:1.2rem;margin-bottom:0.4rem;">👁️ <span style="font-family:'JetBrains Mono',monospace;font-size:0.8rem;color:#ff7b72;">SYMPTOM CHECK</span></div>
133
+ <ul style="font-size:0.82rem;color:#8b949e;line-height:1.8;list-style:none;padding:0;">
134
+ <li>→ Panel expands in steps, not fluidly?</li>
135
+ <li>→ Cursor lags behind drag handle?</li>
136
+ <li>→ Buttons feel delayed or unresponsive?</li>
137
+ <li>→ UI freezes 1–2s during interaction?</li>
138
+ <li>→ Resize feels "sticky" near edges?</li>
139
+ </ul>
140
+ </div>
141
+ <div class="diag-card ok-card reveal">
142
+ <div style="font-size:1.2rem;margin-bottom:0.4rem;">🔍 <span style="font-family:'JetBrains Mono',monospace;font-size:0.8rem;color:#3fb950;">FIRST QUESTIONS</span></div>
143
+ <ul style="font-size:0.82rem;color:#8b949e;line-height:1.8;list-style:none;padding:0;">
144
+ <li>→ Using SwiftUI, AppKit, or mixed?</li>
145
+ <li>→ Is the panel in an HSplitView or HStack?</li>
146
+ <li>→ Using onHover or DragGesture for tracking?</li>
147
+ <li>→ Any @State in a ToolbarItem?</li>
148
+ <li>→ How many views re-render on drag?</li>
149
+ </ul>
150
+ </div>
151
+ <div class="diag-card info-card reveal">
152
+ <div style="font-size:1.2rem;margin-bottom:0.4rem;">⚡ <span style="font-family:'JetBrains Mono',monospace;font-size:0.8rem;color:#58a6ff;">FAST TRIAGE</span></div>
153
+ <ul style="font-size:0.82rem;color:#8b949e;line-height:1.8;list-style:none;padding:0;">
154
+ <li>→ Run Instruments → SwiftUI template</li>
155
+ <li>→ Look at View Body avg duration</li>
156
+ <li>→ Check if parent views re-render on drag</li>
157
+ <li>→ Profile on real hardware (not simulator)</li>
158
+ </ul>
159
+ </div>
160
+ <div class="diag-card purple-card reveal">
161
+ <div style="font-size:1.2rem;margin-bottom:0.4rem;">🧠 <span style="font-family:'JetBrains Mono',monospace;font-size:0.8rem;color:#d2a8ff;">ROOT CAUSE BUCKETS</span></div>
162
+ <ul style="font-size:0.82rem;color:#8b949e;line-height:1.8;list-style:none;padding:0;">
163
+ <li>→ Wrong event source (onHover vs continuous)</li>
164
+ <li>→ Wrong layout primitive (HStack vs HSplitView)</li>
165
+ <li>→ Re-render storm during drag</li>
166
+ <li>→ Wrong SwiftUI component (should be AppKit)</li>
167
+ </ul>
168
+ </div>
169
+ </div>
170
+ </div>
171
+ </div>
172
+
173
+ <!-- SLIDE 3: EVENT SOURCE PROBLEM -->
174
+ <div class="slide slide-3" data-slide="3">
175
+ <div class="gradient-mesh">
176
+ <div class="blob" style="width:300px;height:300px;top:-60px;right:-60px;background:#ff7b72;opacity:0.07;"></div>
177
+ <div class="blob" style="width:200px;height:200px;bottom:-50px;left:-30px;background:#58a6ff;opacity:0.06;"></div>
178
+ </div>
179
+ <div class="content">
180
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-red">// Root Cause #1</span></div>
181
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.25rem;">Wrong Event Source: Cursor Tracking</h2>
182
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:14px;">
183
+ <div>
184
+ <div style="color:#ff7b72;font-size:0.75rem;font-family:'JetBrains Mono',monospace;margin-bottom:6px;">❌ ENTER/EXIT ONLY — causes jumps</div>
185
+ <div class="code-block reveal">
186
+ <span class="cm">// Fires ONCE on enter, ONCE on exit</span>
187
+ <span class="cm">// Cursor position: UNKNOWN between events</span>
188
+ .<span class="fn">onHover</span> { isHovering <span class="kw">in</span>
189
+ <span class="kw">if</span> isHovering {
190
+ NSCursor.resizeLeftRight.push()
191
+ } <span class="kw">else</span> {
192
+ NSCursor.pop()
193
+ }
194
+ }
195
+ <span class="cm">// ⚠ Panel has NO idea where cursor is</span>
196
+ <span class="cm">// ⚠ Resize only triggers at boundary</span>
197
+ </div>
198
+ </div>
199
+ <div>
200
+ <div style="color:#3fb950;font-size:0.75rem;font-family:'JetBrains Mono',monospace;margin-bottom:6px;">✅ CONTINUOUS — smooth tracking (macOS 13+)</div>
201
+ <div class="code-block reveal">
202
+ .<span class="fn">onContinuousHover</span> { phase <span class="kw">in</span>
203
+ <span class="kw">switch</span> phase {
204
+ <span class="kw">case</span> .active(<span class="kw">let</span> location):
205
+ <span class="cm">// Called on EVERY pointer move</span>
206
+ cursorX = location.x
207
+ isHovering = <span class="kw">true</span>
208
+ NSCursor.resizeLeftRight.push()
209
+ <span class="kw">case</span> .ended:
210
+ isHovering = <span class="kw">false</span>
211
+ NSCursor.pop()
212
+ }
213
+ }
214
+ <span class="cm">// ✅ Full position delta available</span>
215
+ </div>
216
+ </div>
217
+ </div>
218
+ <div class="diag-card info-card reveal" style="padding:0.75rem 1rem;">
219
+ <span style="font-size:0.8rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">⚡ AppKit alternative for even lower latency: </span>
220
+ <span style="font-size:0.8rem;color:#58a6ff;font-family:'JetBrains Mono',monospace;">NSTrackingArea + mouseDragged(_:) + mouseMoved(_:)</span>
221
+ <span style="font-size:0.8rem;color:#484f58;"> — zero SwiftUI overhead during drag</span>
222
+ </div>
223
+ </div>
224
+ </div>
225
+
226
+ <!-- SLIDE 4: DRAG BINDING PROBLEM -->
227
+ <div class="slide slide-4" data-slide="4">
228
+ <div class="gradient-mesh">
229
+ <div class="blob" style="width:280px;height:280px;top:-60px;right:-60px;background:#d2a8ff;opacity:0.08;"></div>
230
+ <div class="blob" style="width:200px;height:200px;bottom:-40px;left:-30px;background:#58a6ff;opacity:0.06;"></div>
231
+ </div>
232
+ <div class="content">
233
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-purple">// Root Cause #2</span></div>
234
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.25rem;">DragGesture: Binding Size to Cursor Delta</h2>
235
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
236
+ <div>
237
+ <div style="color:#ff7b72;font-size:0.75rem;font-family:'JetBrains Mono',monospace;margin-bottom:6px;">❌ Wrong: size snaps on gesture end only</div>
238
+ <div class="code-block reveal" style="font-size:0.74rem;">
239
+ <span class="cm">// Width only updates AFTER drag ends</span>
240
+ <span class="cm">// ⚠ Panel doesn't track cursor in real-time</span>
241
+ .<span class="fn">gesture</span>(
242
+ DragGesture()
243
+ .<span class="fn">onEnded</span> { val <span class="kw">in</span>
244
+ panelWidth -= val.translation.width
245
+ }
246
+ )
247
+ .frame(width: panelWidth)
248
+ </div>
249
+ </div>
250
+ <div>
251
+ <div style="color:#3fb950;font-size:0.75rem;font-family:'JetBrains Mono',monospace;margin-bottom:6px;">✅ Correct: width updates on EVERY delta</div>
252
+ <div class="code-block reveal" style="font-size:0.74rem;">
253
+ @<span class="fn">State</span> <span class="kw">private var</span> panelWidth: CGFloat = 280
254
+ @<span class="fn">State</span> <span class="kw">private var</span> dragOffset: CGFloat = 0
255
+
256
+ .<span class="fn">gesture</span>(
257
+ DragGesture(<span class="fn">minimumDistance</span>: 0)
258
+ .<span class="fn">onChanged</span> { val <span class="kw">in</span>
259
+ <span class="cm">// Live update: subtract translation</span>
260
+ dragOffset = val.translation.width
261
+ }
262
+ .<span class="fn">onEnded</span> { val <span class="kw">in</span>
263
+ panelWidth -= val.translation.width
264
+ dragOffset = 0
265
+ }
266
+ )
267
+ <span class="cm">// Frame uses BOTH for smooth tracking</span>
268
+ .frame(width: panelWidth - dragOffset)
269
+ </div>
270
+ </div>
271
+ </div>
272
+ <div class="diag-card warn-card reveal" style="padding:0.6rem 1rem;margin-top:12px;">
273
+ <span style="font-size:0.78rem;color:#f2cc60;font-family:'JetBrains Mono',monospace;">⚠ Known SwiftUI bug: </span>
274
+ <span style="font-size:0.78rem;color:#8b949e;">Any @State in a ToolbarItem causes drag degradation — even if unrelated. Move toolbar @State to a separate observable or remove it.</span>
275
+ </div>
276
+ </div>
277
+ </div>
278
+
279
+ <!-- SLIDE 5: LAYOUT PRIMITIVE MISMATCH -->
280
+ <div class="slide slide-5" data-slide="5">
281
+ <div class="gradient-mesh">
282
+ <div class="blob" style="width:300px;height:300px;top:-60px;right:-60px;background:#3fb950;opacity:0.07;"></div>
283
+ <div class="blob" style="width:200px;height:200px;bottom:-40px;left:-30px;background:#d2a8ff;opacity:0.06;"></div>
284
+ </div>
285
+ <div class="content">
286
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-green">// Root Cause #3</span></div>
287
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.25rem;">Layout Primitive Mismatch</h2>
288
+ <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:14px;">
289
+ <div class="diag-card warn-card reveal">
290
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.78rem;color:#ff7b72;margin-bottom:6px;">HStack + frame()</div>
291
+ <div style="font-size:0.78rem;color:#8b949e;line-height:1.7;">Custom drag on Rectangle divider. Works but you reinvent resize logic, cursor tracking, min/max clamping, and animation. Prone to jank.</div>
292
+ <div style="margin-top:8px;"><span class="badge badge-red">Manual work</span></div>
293
+ </div>
294
+ <div class="diag-card ok-card reveal">
295
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.78rem;color:#3fb950;margin-bottom:6px;">HSplitView</div>
296
+ <div style="font-size:0.78rem;color:#8b949e;line-height:1.7;">AppKit-backed native divider. System handles resize cursor, drag tracking, and frame updates. Fluid by default. Use for persistent split layouts.</div>
297
+ <div style="margin-top:8px;"><span class="badge badge-green">Native feel</span></div>
298
+ </div>
299
+ <div class="diag-card info-card reveal">
300
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.78rem;color:#58a6ff;margin-bottom:6px;">NavigationSplitView</div>
301
+ <div style="font-size:0.78rem;color:#8b949e;line-height:1.7;">Best for sidebar + detail pattern. Gets hide/show sidebar button, proper selection states, and macOS window chrome for free. Preferred since macOS 13.</div>
302
+ <div style="margin-top:8px;"><span class="badge badge-blue">Most native</span></div>
303
+ </div>
304
+ </div>
305
+ <div class="code-block reveal" style="font-size:0.78rem;">
306
+ <span class="cm">// If you need custom widths — use HSplitView + constraints, not HStack + DragGesture</span>
307
+ HSplitView {
308
+ SidebarView()
309
+ .frame(minWidth: 200, idealWidth: 280, maxWidth: 400)
310
+ ContentView()
311
+ .frame(maxWidth: .infinity)
312
+ }
313
+ <span class="cm">// NavigationSplitView for full Mac sidebar UX (macOS 13+)</span>
314
+ NavigationSplitView { SidebarView() } detail: { DetailView() }
315
+ </div>
316
+ </div>
317
+ </div>
318
+
319
+ <!-- SLIDE 6: RE-RENDER STORMS -->
320
+ <div class="slide slide-6" data-slide="6">
321
+ <div class="gradient-mesh">
322
+ <div class="blob" style="width:300px;height:300px;top:-60px;right:-60px;background:#ff7b72;opacity:0.08;"></div>
323
+ <div class="blob" style="width:220px;height:220px;bottom:-50px;left:-30px;background:#58a6ff;opacity:0.06;"></div>
324
+ </div>
325
+ <div class="content">
326
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-red">// Root Cause #4</span></div>
327
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.2rem;">Re-render Storms During Drag</h2>
328
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
329
+ <div>
330
+ <div style="color:#ff7b72;font-size:0.75rem;font-family:'JetBrains Mono',monospace;margin-bottom:6px;">❌ State too high — every drag re-renders all children</div>
331
+ <div class="code-block reveal" style="font-size:0.76rem;">
332
+ <span class="cm">// ⚠ panelWidth at ContentView level</span>
333
+ <span class="cm">// ⚠ All children re-render on EVERY drag delta</span>
334
+ <span class="kw">struct</span> ContentView: View {
335
+ @<span class="fn">State</span> <span class="kw">var</span> panelWidth: CGFloat = 280
336
+ <span class="kw">var</span> body: some View {
337
+ HStack {
338
+ ExpensiveListView() <span class="cm">// re-renders!</span>
339
+ ExpensiveDetailView() <span class="cm">// re-renders!</span>
340
+ ResizeHandle($panelWidth)
341
+ }
342
+ }
343
+ }
344
+ </div>
345
+ </div>
346
+ <div>
347
+ <div style="color:#3fb950;font-size:0.75rem;font-family:'JetBrains Mono',monospace;margin-bottom:6px;">✅ Push state down — only resize handle re-renders</div>
348
+ <div class="code-block reveal" style="font-size:0.76rem;">
349
+ <span class="cm">// panelWidth owned by ResizablePanel</span>
350
+ <span class="kw">struct</span> ResizablePanel: View {
351
+ @<span class="fn">State</span> <span class="kw">private var</span> width: CGFloat = 280
352
+ <span class="kw">var</span> body: some View {
353
+ ZStack(alignment: .trailing) {
354
+ PanelContent() <span class="cm">// stable identity</span>
355
+ .frame(width: width)
356
+ ResizeHandle($width) <span class="cm">// only this re-renders</span>
357
+ }
358
+ }
359
+ }
360
+ <span class="cm">// PanelContent: conforming to Equatable</span>
361
+ <span class="cm">// skips re-render when width changes</span>
362
+ </div>
363
+ </div>
364
+ </div>
365
+ <div class="diag-card info-card reveal" style="padding:0.65rem 1rem;margin-top:12px;">
366
+ <span style="font-size:0.78rem;color:#58a6ff;font-family:'JetBrains Mono',monospace;">Equatable short-circuit: </span>
367
+ <span style="font-size:0.78rem;color:#8b949e;">struct PanelContent: View, Equatable — SwiftUI skips body recomputation entirely when props are equal. Critical for complex content inside resizable panels.</span>
368
+ </div>
369
+ </div>
370
+ </div>
371
+
372
+ <!-- SLIDE 7: ANIMATION MISSING -->
373
+ <div class="slide slide-7" data-slide="7">
374
+ <div class="gradient-mesh">
375
+ <div class="blob" style="width:280px;height:280px;top:-60px;right:-60px;background:#d2a8ff;opacity:0.08;"></div>
376
+ <div class="blob" style="width:200px;height:200px;bottom:-40px;left:-30px;background:#3fb950;opacity:0.05;"></div>
377
+ </div>
378
+ <div class="content">
379
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-purple">// Root Cause #5</span></div>
380
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.2rem;">Missing / Misplaced Animation</h2>
381
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:12px;">
382
+ <div class="diag-card warn-card reveal">
383
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.76rem;color:#ff7b72;margin-bottom:6px;">During drag: NO animation</div>
384
+ <div style="font-size:0.8rem;color:#8b949e;line-height:1.7;">While the user is actively dragging, <strong style="color:#e6edf3;">remove</strong> any animation from frame changes. Animation during live drag fights the cursor and makes it feel laggy.</div>
385
+ </div>
386
+ <div class="diag-card ok-card reveal">
387
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.76rem;color:#3fb950;margin-bottom:6px;">On snap/settle: YES animation</div>
388
+ <div style="font-size:0.8rem;color:#8b949e;line-height:1.7;">When drag ends, animate the panel snapping to its final clamped size. <code style="color:#d2a8ff;">.animation(.spring(response:0.3), value: panelWidth)</code> on the settled state feels native.</div>
389
+ </div>
390
+ </div>
391
+ <div class="code-block reveal" style="font-size:0.76rem;">
392
+ <span class="cm">// Pattern: animate ONLY on drag end, not during drag</span>
393
+ .<span class="fn">gesture</span>(
394
+ DragGesture(<span class="fn">minimumDistance</span>: 0)
395
+ .<span class="fn">onChanged</span> { val <span class="kw">in</span>
396
+ isDragging = <span class="ok">true</span>
397
+ dragOffset = val.translation.width <span class="cm">// no animation — direct</span>
398
+ }
399
+ .<span class="fn">onEnded</span> { val <span class="kw">in</span>
400
+ isDragging = <span class="ok">false</span>
401
+ <span class="cm">// withAnimation only fires here</span>
402
+ withAnimation(.<span class="fn">spring</span>(response: 0.3, dampingFraction: 0.7)) {
403
+ panelWidth = max(minW, min(maxW, panelWidth - val.translation.width))
404
+ dragOffset = 0
405
+ }
406
+ }
407
+ )
408
+ <span class="cm">// Frame during drag: no animation modifier</span>
409
+ .frame(width: isDragging ? panelWidth - dragOffset : panelWidth)
410
+ </div>
411
+ </div>
412
+ </div>
413
+
414
+ <!-- SLIDE 8: WRONG COMPONENTS -->
415
+ <div class="slide slide-8" data-slide="8">
416
+ <div class="gradient-mesh">
417
+ <div class="blob" style="width:300px;height:300px;top:-60px;right:-60px;background:#58a6ff;opacity:0.07;"></div>
418
+ <div class="blob" style="width:220px;height:220px;bottom:-50px;left:-30px;background:#ff7b72;opacity:0.06;"></div>
419
+ </div>
420
+ <div class="content">
421
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-blue">// Root Cause #6 — Component Selection</span></div>
422
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.3rem,2.8vw,2rem);font-weight:700;margin-bottom:1.1rem;">SwiftUI vs AppKit: Component Decision Map</h2>
423
+ <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;">
424
+ <div class="component-row reveal" style="flex-direction:column;align-items:flex-start;">
425
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.7rem;color:#f2cc60;margin-bottom:6px;">Picker with 100+ items</div>
426
+ <div style="font-size:0.76rem;color:#ff7b72;margin-bottom:4px;">SwiftUI Picker → 4-5s delay, beachball</div>
427
+ <div class="arrow-right">↓ fix</div>
428
+ <div style="font-size:0.76rem;color:#3fb950;">NSViewRepresentable(NSPopUpButton)</div>
429
+ </div>
430
+ <div class="component-row reveal" style="flex-direction:column;align-items:flex-start;">
431
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.7rem;color:#f2cc60;margin-bottom:6px;">Scrollable list (1000+ rows)</div>
432
+ <div style="font-size:0.76rem;color:#ff7b72;margin-bottom:4px;">VStack in ScrollView → memory + jank</div>
433
+ <div class="arrow-right">↓ fix</div>
434
+ <div style="font-size:0.76rem;color:#3fb950;">List{} or NSViewRepresentable(NSTableView)</div>
435
+ </div>
436
+ <div class="component-row reveal" style="flex-direction:column;align-items:flex-start;">
437
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.7rem;color:#f2cc60;margin-bottom:6px;">Text input with autocomplete</div>
438
+ <div style="font-size:0.76rem;color:#ff7b72;margin-bottom:4px;">SwiftUI TextField → limited delegate control</div>
439
+ <div class="arrow-right">↓ fix</div>
440
+ <div style="font-size:0.76rem;color:#3fb950;">NSViewRepresentable(NSTextField)</div>
441
+ </div>
442
+ <div class="component-row reveal" style="flex-direction:column;align-items:flex-start;">
443
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.7rem;color:#f2cc60;margin-bottom:6px;">Outline / tree view</div>
444
+ <div style="font-size:0.76rem;color:#ff7b72;margin-bottom:4px;">SwiftUI List → broken disclosure arrows</div>
445
+ <div class="arrow-right">↓ fix</div>
446
+ <div style="font-size:0.76rem;color:#3fb950;">NSViewRepresentable(NSOutlineView)</div>
447
+ </div>
448
+ <div class="component-row reveal" style="flex-direction:column;align-items:flex-start;">
449
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.7rem;color:#f2cc60;margin-bottom:6px;">Inspector panel resize</div>
450
+ <div style="font-size:0.76rem;color:#ff7b72;margin-bottom:4px;">.inspector modifier → crashes on resize (macOS 26 bug)</div>
451
+ <div class="arrow-right">↓ fix</div>
452
+ <div style="font-size:0.76rem;color:#3fb950;">HSplitView or manual panel until fixed</div>
453
+ </div>
454
+ <div class="component-row reveal" style="flex-direction:column;align-items:flex-start;">
455
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.7rem;color:#f2cc60;margin-bottom:6px;">Toolbar with @State</div>
456
+ <div style="font-size:0.76rem;color:#ff7b72;margin-bottom:4px;">ToolbarItem + @State → drag degradation bug</div>
457
+ <div class="arrow-right">↓ fix</div>
458
+ <div style="font-size:0.76rem;color:#3fb950;">Move state to @Observable or NSToolbar</div>
459
+ </div>
460
+ </div>
461
+ </div>
462
+ </div>
463
+
464
+ <!-- SLIDE 9: INSTRUMENTS WORKFLOW -->
465
+ <div class="slide slide-9" data-slide="9">
466
+ <div class="gradient-mesh">
467
+ <div class="blob" style="width:300px;height:300px;top:-60px;right:-60px;background:#3fb950;opacity:0.07;"></div>
468
+ <div class="blob" style="width:220px;height:220px;bottom:-50px;left:-30px;background:#d2a8ff;opacity:0.06;"></div>
469
+ </div>
470
+ <canvas class="particle-canvas-ambient" style="position:absolute;inset:0;z-index:1;"></canvas>
471
+ <div class="content">
472
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-green">// Instruments — Profiling Protocol</span></div>
473
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.25rem;">Profiling the Jank: Instruments Workflow</h2>
474
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;">
475
+ <div>
476
+ <div style="display:flex;flex-direction:column;gap:10px;">
477
+ <div class="flow-step reveal">
478
+ <div class="flow-num">1</div>
479
+ <div><div style="font-family:'JetBrains Mono',monospace;font-size:0.78rem;color:#58a6ff;">⌘+I → SwiftUI + Time Profiler</div><div style="font-size:0.76rem;color:#8b949e;margin-top:2px;">Select both templates. Profile on real hardware only. Simulator hides real bottlenecks.</div></div>
480
+ </div>
481
+ <div class="flow-step reveal">
482
+ <div class="flow-num">2</div>
483
+ <div><div style="font-family:'JetBrains Mono',monospace;font-size:0.78rem;color:#58a6ff;">Reproduce the drag jank</div><div style="font-size:0.76rem;color:#8b949e;margin-top:2px;">Record while dragging the panel handle 5-10 times. Include fast and slow drags.</div></div>
484
+ </div>
485
+ <div class="flow-step reveal">
486
+ <div class="flow-num">3</div>
487
+ <div><div style="font-family:'JetBrains Mono',monospace;font-size:0.78rem;color:#58a6ff;">View Body → Average Duration</div><div style="font-size:0.76rem;color:#8b949e;margin-top:2px;">Target: &lt;8ms per body call. Anything over 16ms = visible dropped frame. Find the outlier views.</div></div>
488
+ </div>
489
+ <div class="flow-step reveal">
490
+ <div class="flow-num">4</div>
491
+ <div><div style="font-family:'JetBrains Mono',monospace;font-size:0.78rem;color:#58a6ff;">Check parent re-render cascade</div><div style="font-size:0.76rem;color:#8b949e;margin-top:2px;">If ContentView body fires on every drag delta → state is too high. Push down.</div></div>
492
+ </div>
493
+ </div>
494
+ </div>
495
+ <div>
496
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.7rem;color:#484f58;margin-bottom:6px;">// Red flags in SwiftUI instrument</div>
497
+ <div style="display:flex;flex-direction:column;gap:8px;">
498
+ <div class="diag-card warn-card reveal" style="padding:0.6rem 0.9rem;">
499
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.74rem;color:#ff7b72;">Large subtrees invalidating on drag</div>
500
+ <div style="font-size:0.74rem;color:#8b949e;margin-top:3px;">Root: @State too high or environment misuse</div>
501
+ </div>
502
+ <div class="diag-card warn-card reveal" style="padding:0.6rem 0.9rem;">
503
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.74rem;color:#ff7b72;">View identity resets repeatedly</div>
504
+ <div style="font-size:0.74rem;color:#8b949e;margin-top:3px;">Root: unstable IDs in ForEach, or .id() with UUID()</div>
505
+ </div>
506
+ <div class="diag-card warn-card reveal" style="padding:0.6rem 0.9rem;">
507
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.74rem;color:#ff7b72;">GeometryReader called repeatedly</div>
508
+ <div style="font-size:0.74rem;color:#8b949e;margin-top:3px;">Root: GeometryReader inside list rows or panel body</div>
509
+ </div>
510
+ <div class="diag-card ok-card reveal" style="padding:0.6rem 0.9rem;">
511
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.74rem;color:#3fb950;">Only ResizeHandle body fires during drag</div>
512
+ <div style="font-size:0.74rem;color:#8b949e;margin-top:3px;">✅ State is properly localized — expected behavior</div>
513
+ </div>
514
+ </div>
515
+ </div>
516
+ </div>
517
+ </div>
518
+ </div>
519
+
520
+ <!-- SLIDE 10: APPKIT BRIDGE PATTERN -->
521
+ <div class="slide slide-10" data-slide="10">
522
+ <div class="gradient-mesh">
523
+ <div class="blob" style="width:300px;height:300px;top:-60px;right:-60px;background:#d2a8ff;opacity:0.08;"></div>
524
+ <div class="blob" style="width:200px;height:200px;bottom:-50px;left:-30px;background:#3fb950;opacity:0.05;"></div>
525
+ </div>
526
+ <div class="content">
527
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-purple">// AppKit Bridge Pattern</span></div>
528
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.1rem;">NSViewRepresentable: Minimal Correct Pattern</h2>
529
+ <div class="code-block reveal" style="font-size:0.77rem;margin-bottom:12px;">
530
+ <span class="cm">// Template: drop-in AppKit component with SwiftUI binding</span>
531
+ <span class="kw">struct</span> NativeTextField: <span class="fn">NSViewRepresentable</span> {
532
+ @<span class="fn">Binding</span> <span class="kw">var</span> text: String
533
+
534
+ <span class="kw">func</span> <span class="fn">makeNSView</span>(context: Context) -> NSTextField {
535
+ <span class="kw">let</span> tf = NSTextField(string: text)
536
+ tf.delegate = context.coordinator <span class="cm">// required for delegate callbacks</span>
537
+ <span class="kw">return</span> tf
538
+ }
539
+ <span class="kw">func</span> <span class="fn">updateNSView</span>(_ nsView: NSTextField, context: Context) {
540
+ <span class="cm">// Only update if changed — prevent feedback loops</span>
541
+ <span class="kw">if</span> nsView.stringValue != text { nsView.stringValue = text }
542
+ }
543
+ <span class="kw">func</span> <span class="fn">makeCoordinator</span>() -> Coordinator { Coordinator(<span class="fn">parent</span>: <span class="kw">self</span>) }
544
+
545
+ <span class="kw">class</span> Coordinator: NSObject, <span class="fn">NSTextFieldDelegate</span> {
546
+ <span class="kw">var</span> parent: NativeTextField
547
+ <span class="kw">init</span>(parent: NativeTextField) { <span class="kw">self</span>.parent = parent }
548
+ <span class="kw">func</span> controlTextDidChange(_ obj: Notification) {
549
+ <span class="kw">guard let</span> tf = obj.object <span class="kw">as</span>? NSTextField <span class="kw">else</span> { <span class="kw">return</span> }
550
+ parent.text = tf.stringValue <span class="cm">// write back to SwiftUI</span>
551
+ }
552
+ }
553
+ }
554
+ </div>
555
+ <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px;">
556
+ <div class="diag-card warn-card reveal" style="padding:0.6rem 0.9rem;">
557
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.72rem;color:#ff7b72;">NSView action button</div>
558
+ <div style="font-size:0.73rem;color:#8b949e;margin-top:3px;">Use NSViewControllerRepresentable, not NSViewRepresentable — action targets need a VC</div>
559
+ </div>
560
+ <div class="diag-card ok-card reveal" style="padding:0.6rem 0.9rem;">
561
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.72rem;color:#3fb950;">updateNSView guard</div>
562
+ <div style="font-size:0.73rem;color:#8b949e;margin-top:3px;">Always guard-check before setting values — prevents binding feedback loops on every render</div>
563
+ </div>
564
+ <div class="diag-card info-card reveal" style="padding:0.6rem 0.9rem;">
565
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.72rem;color:#58a6ff;">Coordinator lifetime</div>
566
+ <div style="font-size:0.73rem;color:#8b949e;margin-top:3px;">SwiftUI owns the Coordinator — do not create it manually or store outside representable</div>
567
+ </div>
568
+ </div>
569
+ </div>
570
+ </div>
571
+
572
+ <!-- SLIDE 11: VIEW IDENTITY & EQUATABLE -->
573
+ <div class="slide slide-11" data-slide="11">
574
+ <div class="gradient-mesh">
575
+ <div class="blob" style="width:300px;height:300px;top:-60px;right:-60px;background:#58a6ff;opacity:0.07;"></div>
576
+ <div class="blob" style="width:200px;height:200px;bottom:-50px;left:-30px;background:#d2a8ff;opacity:0.06;"></div>
577
+ </div>
578
+ <div class="content">
579
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-blue">// Root Cause #7 — Identity</span></div>
580
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.1rem;">View Identity: The #1 Diffing Killer</h2>
581
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:12px;">
582
+ <div>
583
+ <div style="color:#ff7b72;font-size:0.75rem;font-family:'JetBrains Mono',monospace;margin-bottom:6px;">❌ Identity anti-patterns</div>
584
+ <div class="code-block reveal" style="font-size:0.76rem;">
585
+ <span class="cm">// UUID() → new identity every render</span>
586
+ Text(title).<span class="fn">id</span>(UUID())
587
+
588
+ <span class="cm">// Unstable ForEach IDs</span>
589
+ ForEach(items) { item <span class="kw">in</span> <span class="cm">// no id: \.id</span>
590
+ RowView(item: item)
591
+ }
592
+
593
+ <span class="cm">// ViewModel recreated on each render</span>
594
+ MyView(viewModel: ViewModel()) <span class="cm">// ❌</span>
595
+
596
+ <span class="cm">// Conditional structure changes identity</span>
597
+ <span class="kw">if</span> loading { ProgressView() }
598
+ <span class="kw">else</span> { ContentView() } <span class="cm">// ❌ different trees</span>
599
+ </div>
600
+ </div>
601
+ <div>
602
+ <div style="color:#3fb950;font-size:0.75rem;font-family:'JetBrains Mono',monospace;margin-bottom:6px;">✅ Stable identity patterns</div>
603
+ <div class="code-block reveal" style="font-size:0.76rem;">
604
+ <span class="cm">// Stable, meaningful IDs</span>
605
+ ForEach(items, id: \.id) { item <span class="kw">in</span>
606
+ RowView(item: item)
607
+ }
608
+
609
+ <span class="cm">// ViewModel owned by SwiftUI lifecycle</span>
610
+ @StateObject <span class="kw">var</span> vm = ViewModel() <span class="cm">// ✅</span>
611
+
612
+ <span class="cm">// Preserve identity with ZStack</span>
613
+ ZStack {
614
+ ContentView() <span class="cm">// always in tree</span>
615
+ <span class="kw">if</span> loading { ProgressView() }
616
+ }
617
+
618
+ <span class="cm">// Equatable to skip diffing</span>
619
+ <span class="kw">struct</span> PanelContent: View, <span class="fn">Equatable</span> { ... }
620
+ </div>
621
+ </div>
622
+ </div>
623
+ <div class="diag-card info-card reveal" style="padding:0.65rem 1rem;">
624
+ <span style="font-size:0.78rem;color:#58a6ff;font-family:'JetBrains Mono',monospace;">Rule: </span>
625
+ <span style="font-size:0.78rem;color:#8b949e;">Stable identity = stable performance. Every identity reset causes state loss, animation restart, and full layout recalculation. Profile identity in Instruments under "View Properties."</span>
626
+ </div>
627
+ </div>
628
+ </div>
629
+
630
+ <!-- SLIDE 12: MAIN THREAD & OS CAVEATS -->
631
+ <div class="slide slide-12" data-slide="12">
632
+ <div class="gradient-mesh">
633
+ <div class="blob" style="width:300px;height:300px;top:-60px;right:-60px;background:#f2cc60;opacity:0.06;"></div>
634
+ <div class="blob" style="width:220px;height:220px;bottom:-50px;left:-30px;background:#3fb950;opacity:0.05;"></div>
635
+ </div>
636
+ <div class="content">
637
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-yellow">// Platform Caveats</span></div>
638
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.1rem;">Main Thread, Layout, and OS Caveats</h2>
639
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
640
+ <div class="diag-card warn-card reveal">
641
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.76rem;color:#ff7b72;margin-bottom:5px;">Main thread blocking</div>
642
+ <div style="font-size:0.78rem;color:#8b949e;line-height:1.7;">Any I/O, decoding, or heavy computation in a body or drag handler blocks the main thread. Drag events queue up → burst on release = jumpy motion. Fix: <code style="color:#d2a8ff;">Task { await ... }</code> for all async work.</div>
643
+ </div>
644
+ <div class="diag-card warn-card reveal">
645
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.76rem;color:#ff7b72;margin-bottom:5px;">GeometryReader abuse</div>
646
+ <div style="font-size:0.78rem;color:#8b949e;line-height:1.7;">GeometryReader triggers layout measurement on every size change. Inside a resizable panel = layout thrash on every drag event. Prefer <code style="color:#d2a8ff;">.containerRelativeFrame</code> or intrinsic sizing.</div>
647
+ </div>
648
+ <div class="diag-card warn-card reveal">
649
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.76rem;color:#ff7b72;margin-bottom:5px;">SwiftUI Inspector API bug (macOS 26)</div>
650
+ <div style="font-size:0.78rem;color:#8b949e;line-height:1.7;">The <code style="color:#d2a8ff;">.inspector</code> modifier causes crashes and freezes when the user resizes with inspector open. Active bug as of macOS 26. Workaround: use HSplitView instead.</div>
651
+ </div>
652
+ <div class="diag-card warn-card reveal">
653
+ <div style="font-family:'JetBrains Mono',monospace;font-size:0.76rem;color:#ff7b72;margin-bottom:5px;">Environment global invalidation</div>
654
+ <div style="font-size:0.78rem;color:#8b949e;line-height:1.7;">Putting fast-changing values (cursor position, drag offset) into @EnvironmentObject invalidates the entire subtree on every change. Treat environment as configuration, not live state.</div>
655
+ </div>
656
+ </div>
657
+ <div class="code-block reveal" style="font-size:0.76rem;margin-top:12px;">
658
+ <span class="cm">// ✅ Correct async pattern — never block drag events</span>
659
+ .<span class="fn">onChanged</span> { val <span class="kw">in</span>
660
+ dragOffset = val.translation.width <span class="cm">// sync, cheap — fine</span>
661
+ <span class="kw">if</span> shouldLoadPreview {
662
+ Task { <span class="kw">await</span> loadPreviewAsync() } <span class="cm">// off main thread</span>
663
+ }
664
+ }
665
+ </div>
666
+ </div>
667
+ </div>
668
+
669
+ <!-- SLIDE 13: DECISION TREE / FIX CHECKLIST -->
670
+ <div class="slide slide-13" data-slide="13">
671
+ <div class="gradient-mesh">
672
+ <div class="blob" style="width:350px;height:350px;top:-80px;right:-80px;background:#3fb950;opacity:0.08;"></div>
673
+ <div class="blob" style="width:250px;height:250px;bottom:-60px;left:-40px;background:#58a6ff;opacity:0.06;"></div>
674
+ <div class="blob" style="width:180px;height:180px;top:40%;left:10%;background:#d2a8ff;opacity:0.05;"></div>
675
+ </div>
676
+ <canvas class="particle-canvas" style="position:absolute;inset:0;z-index:1;"></canvas>
677
+ <div class="content">
678
+ <div class="reveal" style="margin-bottom:0.5rem;"><span class="badge badge-green">// Fix Checklist</span></div>
679
+ <h2 class="reveal" style="font-family:'JetBrains Mono',monospace;font-size:clamp(1.4rem,3vw,2.2rem);font-weight:700;margin-bottom:1.2rem;">Panel Jank Fix Checklist</h2>
680
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
681
+ <div style="display:flex;flex-direction:column;gap:8px;">
682
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
683
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
684
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">Use onContinuousHover, not onHover</span>
685
+ </div>
686
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
687
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
688
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">Bind frame to live dragOffset, not onEnded</span>
689
+ </div>
690
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
691
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
692
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">Use HSplitView or NavigationSplitView</span>
693
+ </div>
694
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
695
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
696
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">Push @State down to ResizeHandle only</span>
697
+ </div>
698
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
699
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
700
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">No animation during drag — only on snap</span>
701
+ </div>
702
+ </div>
703
+ <div style="display:flex;flex-direction:column;gap:8px;">
704
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
705
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
706
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">Conform heavy panel content to Equatable</span>
707
+ </div>
708
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
709
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
710
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">Use NSViewRepresentable for Picker 100+ items</span>
711
+ </div>
712
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
713
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
714
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">Stable IDs in ForEach (id: \.id, not UUID())</span>
715
+ </div>
716
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
717
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
718
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">Remove GeometryReader from resizable panels</span>
719
+ </div>
720
+ <div class="diag-card ok-card reveal" style="padding:0.55rem 0.9rem;display:flex;align-items:center;gap:10px;">
721
+ <span style="color:#3fb950;font-size:1rem;">✓</span>
722
+ <span style="font-size:0.78rem;color:#8b949e;font-family:'JetBrains Mono',monospace;">All async work off main thread (Task { await })</span>
723
+ </div>
724
+ </div>
725
+ </div>
726
+ </div>
727
+ </div>
728
+
729
+ </div>
730
+
731
+ <!-- NAVIGATION -->
732
+ <div class="nav-controls">
733
+ <button class="nav-btn" onclick="changeSlide(-1)">&#8249;</button>
734
+ <div class="slide-dots" id="dots"></div>
735
+ <button class="nav-btn" onclick="changeSlide(1)">&#8250;</button>
736
+ <span class="slide-counter" id="counter">1 / 13</span>
737
+ </div>
738
+
739
+ <script>
740
+ let current = 1;
741
+ const total = document.querySelectorAll('.slide').length;
742
+ const dotsContainer = document.getElementById('dots');
743
+ const counter = document.getElementById('counter');
744
+ for (let i = 1; i <= total; i++) {
745
+ const dot = document.createElement('div');
746
+ dot.className = 'dot' + (i === 1 ? ' active' : '');
747
+ dot.onclick = () => goToSlide(i);
748
+ dotsContainer.appendChild(dot);
749
+ }
750
+ function goToSlide(n) {
751
+ const prev = document.querySelector('.slide.active');
752
+ const next = document.querySelector('.slide[data-slide="' + n + '"]');
753
+ if (prev) prev.classList.remove('active');
754
+ if (next) { next.classList.add('active'); animateSlide(next); }
755
+ current = n; updateNav();
756
+ }
757
+ function changeSlide(dir) { let n = current + dir; if (n < 1) n = total; if (n > total) n = 1; goToSlide(n); }
758
+ function updateNav() {
759
+ document.querySelectorAll('.dot').forEach((d, i) => d.classList.toggle('active', i + 1 === current));
760
+ counter.textContent = current + ' / ' + total;
761
+ }
762
+ document.addEventListener('keydown', (e) => {
763
+ if (e.key === 'ArrowRight' || e.key === ' ') { e.preventDefault(); changeSlide(1); }
764
+ if (e.key === 'ArrowLeft') { e.preventDefault(); changeSlide(-1); }
765
+ });
766
+ let touchStartX = 0;
767
+ document.addEventListener('touchstart', (e) => { touchStartX = e.touches[0].clientX; });
768
+ document.addEventListener('touchend', (e) => { if (Math.abs(touchStartX - e.changedTouches[0].clientX) > 50) changeSlide(touchStartX > e.changedTouches[0].clientX ? 1 : -1); });
769
+
770
+ function animateSlide(slide) {
771
+ slide.querySelectorAll('.reveal').forEach((el, i) => {
772
+ el.style.transition = 'none';
773
+ el.style.opacity = '0';
774
+ el.style.transform = 'translateY(18px)';
775
+ el.offsetHeight;
776
+ const delay = i * 0.07;
777
+ el.style.transition = 'opacity 0.35s ease ' + delay + 's, transform 0.35s ease ' + delay + 's';
778
+ el.style.opacity = '1';
779
+ el.style.transform = 'translateY(0px)';
780
+ });
781
+ }
782
+
783
+ document.addEventListener('mousemove', (e) => {
784
+ const s = document.querySelector('.mouse-spotlight');
785
+ if (s) s.style.background = 'radial-gradient(600px circle at ' + e.clientX + 'px ' + e.clientY + 'px, rgba(88,166,255,0.05), transparent 40%)';
786
+ });
787
+
788
+ window.initParticles = function(canvas, opts) {
789
+ if (!canvas) return;
790
+ const ctx = canvas.getContext('2d');
791
+ canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight;
792
+ const interactive = opts.interactive !== false;
793
+ const count = opts.count || (interactive ? 40 : 15);
794
+ let mx = -1000, my = -1000;
795
+ const particles = Array.from({length: count}, () => ({
796
+ x: Math.random() * canvas.width, y: Math.random() * canvas.height,
797
+ vx: (Math.random() - 0.5) * 0.25, vy: (Math.random() - 0.5) * 0.25,
798
+ size: Math.random() * 2 + 0.6, alpha: Math.random() * 0.3 + 0.08
799
+ }));
800
+ if (interactive) {
801
+ canvas.addEventListener('mousemove', (e) => { const r = canvas.getBoundingClientRect(); mx = e.clientX - r.left; my = e.clientY - r.top; });
802
+ canvas.addEventListener('mouseleave', () => { mx = -1000; my = -1000; });
803
+ }
804
+ (function animate() {
805
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
806
+ particles.forEach(p => {
807
+ if (interactive) { const dx = p.x - mx, dy = p.y - my, d = Math.sqrt(dx*dx+dy*dy); if (d < 100) { const f = (100-d)/100*1.5; p.vx += (dx/d)*f*0.08; p.vy += (dy/d)*f*0.08; } }
808
+ p.vx *= 0.98; p.vy *= 0.98; p.x += p.vx; p.y += p.vy;
809
+ if (p.x < 0) p.x = canvas.width; if (p.x > canvas.width) p.x = 0;
810
+ if (p.y < 0) p.y = canvas.height; if (p.y > canvas.height) p.y = 0;
811
+ ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
812
+ ctx.fillStyle = 'rgba(88,166,255,' + p.alpha + ')'; ctx.fill();
813
+ });
814
+ requestAnimationFrame(animate);
815
+ })();
816
+ };
817
+ document.querySelectorAll('.particle-canvas').forEach(c => window.initParticles(c, {interactive: true, count: 40}));
818
+ document.querySelectorAll('.particle-canvas-ambient').forEach(c => window.initParticles(c, {interactive: false, count: 15}));
819
+
820
+ animateSlide(document.querySelector('.slide.active'));
821
+ </script>
822
+ </body>
823
+ </html>