@warpgogol/werkstatt-shared 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (437) hide show
  1. package/README.md +31 -13
  2. package/README.uk.md +25 -15
  3. package/package.json +519 -762
  4. package/src/{share/agent → agent}/ard-catalog.ts +6 -1
  5. package/src/{share/agent → agent}/dns-aid.test.ts +2 -2
  6. package/src/{share/agent → agent}/manifest.ts +15 -1
  7. package/src/{share/agent → agent}/search.ts +6 -1
  8. package/src/{share → agent}/tests/api-catalog.test.ts +2 -2
  9. package/src/{share → agent}/tests/ard-catalog.test.ts +2 -2
  10. package/src/{share → agent}/tests/capability.test.ts +1 -1
  11. package/src/{share → agent}/tests/fleet-catalog.test.ts +1 -1
  12. package/src/{share → agent}/tests/knowledge.test.ts +2 -2
  13. package/src/{share → agent}/tests/manifest.test.ts +1 -1
  14. package/src/{share → agent}/tests/mcp-card.test.ts +2 -2
  15. package/src/agent/tests/omit-empty.test.ts +33 -0
  16. package/src/{share → agent}/tests/openapi.test.ts +2 -2
  17. package/src/{share → agent}/tests/proof.test.ts +1 -1
  18. package/src/{share → attribution}/material-credits.ts +4 -4
  19. package/src/{share → checks}/check-hints.ts +6 -1
  20. package/src/checks/index.ts +4 -1
  21. package/src/checks/lib/astro-site-url.ts +6 -1
  22. package/src/checks/lib/i18n.ts +14 -0
  23. package/src/checks/result-helpers.ts +22 -5
  24. package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
  25. package/src/{share → client-scripts}/counter-utils.ts +6 -2
  26. package/src/{share/scripts → client-scripts}/external-link-qr.ts +11 -16
  27. package/src/{share/scripts → client-scripts}/external-links.ts +12 -1
  28. package/src/{share/scripts → client-scripts}/gsap-counter.ts +8 -3
  29. package/src/client-scripts/index.ts +35 -0
  30. package/src/{share/scripts → client-scripts}/inline-number-animation.ts +1 -1
  31. package/src/{share/scripts → client-scripts}/lenis.ts +6 -1
  32. package/src/{share/scripts → client-scripts}/lordicon.ts +6 -1
  33. package/src/{share/scripts → client-scripts}/orchestrator.ts +10 -1
  34. package/src/{share/scripts → client-scripts}/scheduler.ts +6 -1
  35. package/src/client-scripts/scroll-spy.ts +106 -0
  36. package/src/client-scripts/tests/external-link-qr.test.ts +8 -0
  37. package/src/client-scripts/tests/external-links.test.ts +25 -0
  38. package/src/client-scripts/tests/gsap-counter.test.ts +8 -0
  39. package/src/client-scripts/tests/gsap-mountain-journey.test.ts +8 -0
  40. package/src/client-scripts/tests/gsap-parallax.test.ts +8 -0
  41. package/src/client-scripts/tests/gsap-reveal.test.ts +8 -0
  42. package/src/client-scripts/tests/gsap-stagger.test.ts +8 -0
  43. package/src/client-scripts/tests/inline-number-animation.test.ts +8 -0
  44. package/src/client-scripts/tests/lenis.test.ts +8 -0
  45. package/src/client-scripts/tests/live-photos.test.ts +8 -0
  46. package/src/client-scripts/tests/lordicon.test.ts +8 -0
  47. package/src/client-scripts/tests/orchestrator.test.ts +47 -0
  48. package/src/client-scripts/tests/scheduler.test.ts +23 -0
  49. package/src/client-scripts/tests/scroll-spy.test.ts +204 -0
  50. package/src/{share/scripts → client-scripts/tests}/video-player.test.ts +51 -31
  51. package/src/component/contracts.ts +206 -0
  52. package/src/component/index.ts +11 -0
  53. package/src/{share → content}/content-reference.ts +3 -3
  54. package/src/{share/content → content}/entity-id.ts +6 -3
  55. package/src/content/index.ts +3 -0
  56. package/src/{share/content → content}/merge.ts +6 -1
  57. package/src/content/system-manifest.test.ts +107 -0
  58. package/src/content/system-manifest.ts +12 -94
  59. package/src/{share → content}/tests/resolve-field-path.test.ts +1 -1
  60. package/src/{share → content}/tests/substitute-deep.test.ts +1 -1
  61. package/src/content-discipline/index.ts +19 -0
  62. package/src/{share/content-discipline → content-discipline}/parsers.ts +2 -2
  63. package/src/{share/env.d.ts → env.d.ts} +6 -3
  64. package/src/fingerprint/canonical-json.ts +731 -0
  65. package/src/fingerprint/index.ts +12 -0
  66. package/src/fingerprint/primitives.ts +72 -0
  67. package/src/{share/i18n → i18n}/localization.ts +6 -2
  68. package/src/integration/adapters.ts +10 -71
  69. package/src/integration/dispatch.ts +1 -1
  70. package/src/integration/funnel.ts +6 -1
  71. package/src/integration/index.ts +101 -4
  72. package/src/integration/lagebild-destination-adapter.ts +108 -0
  73. package/src/integration/lagebild-ingress.ts +7 -12
  74. package/src/integration/orchestration.ts +11 -10
  75. package/src/integration/tests/deliver-event.test.ts +1 -1
  76. package/src/integration/tests/dispatch.test.ts +4 -4
  77. package/src/integration/tests/hub-runtime.test.ts +22 -14
  78. package/src/integration/vitest.config.ts +8 -0
  79. package/src/kernel/desired-state.ts +227 -0
  80. package/src/kernel/diagnostic.ts +289 -0
  81. package/src/kernel/fs-atomic.ts +131 -0
  82. package/src/kernel/index.ts +15 -0
  83. package/src/kernel/tests/fs-atomic.test.ts +150 -0
  84. package/src/kernel/tests/workspace-io.test.ts +187 -0
  85. package/src/kernel/types.ts +459 -0
  86. package/src/kernel/workspace-io.ts +248 -0
  87. package/src/{share → knowledge}/tests/claim.test.ts +1 -1
  88. package/src/{share → knowledge}/tests/derivation.test.ts +1 -1
  89. package/src/{share → knowledge}/tests/freshness.test.ts +1 -1
  90. package/src/{share → knowledge}/tests/ledger.test.ts +1 -1
  91. package/src/{share → knowledge}/tests/plan.test.ts +1 -1
  92. package/src/{share → knowledge}/tests/source.test.ts +1 -1
  93. package/src/legal/index.ts +15 -0
  94. package/src/{share/middleware → middleware}/access-protection.ts +8 -8
  95. package/src/{share → node}/dev-props-validator.ts +6 -1
  96. package/src/{share → node}/import-scan.ts +23 -3
  97. package/src/{share → node}/semantic/derived-prices-loader.ts +4 -2
  98. package/src/{share → node}/semantic/markdown-twin-provenance.ts +1 -1
  99. package/src/node/tests/derived-prices-loader.test.ts +59 -0
  100. package/src/node/tests/markdown-twin-provenance.test.ts +152 -0
  101. package/src/observability/env-resolver.ts +6 -1
  102. package/src/observability/index.ts +6 -1
  103. package/src/observability/otlp-converter.ts +6 -1
  104. package/src/observability/otlp-transport.ts +6 -1
  105. package/src/observability/pusher.ts +6 -1
  106. package/src/observability/redact.ts +6 -1
  107. package/src/observability/typed-refs.ts +6 -2
  108. package/src/observability/vitest.config.ts +1 -0
  109. package/src/{share → offers}/formula-eval.ts +3 -3
  110. package/src/{share → offers}/tests/formula-eval.test.ts +2 -2
  111. package/src/onboarding/index.ts +1 -0
  112. package/src/{share → onboarding}/tests/onboarding-yaml.test.ts +1 -1
  113. package/src/ontology/archetype-registry.ts +6 -1
  114. package/src/ontology/archetypes/index.json +33 -1
  115. package/src/ontology/archetypes/index.yaml +27 -1
  116. package/src/ontology/archetypes/sections/entity-grid.yaml +46 -0
  117. package/src/ontology/archetypes/sections/exit-diagram.yaml +43 -0
  118. package/src/ontology/biome-token-projection.ts +6 -1
  119. package/src/ontology/cosmic/index.ts +6 -1
  120. package/src/ontology/cosmic/moon-catalog.ts +6 -1
  121. package/src/ontology/cosmic/planet-catalog.ts +6 -1
  122. package/src/ontology/cosmic/star-catalog.ts +6 -1
  123. package/src/ontology/enums.ts +6 -1
  124. package/src/ontology/external-surfaces/index.ts +1 -1
  125. package/src/ontology/index.ts +6 -2
  126. package/src/ontology/manifest.ts +26 -4
  127. package/src/ontology/operations/artifact-store.ts +45 -0
  128. package/src/ontology/operations/dht.ts +98 -0
  129. package/src/ontology/operations/handoff.ts +92 -0
  130. package/src/ontology/operations/index.ts +187 -1
  131. package/src/ontology/operations/leitstand.ts +99 -0
  132. package/src/ontology/operations/materialization.ts +90 -0
  133. package/src/ontology/operations/mission.ts +111 -0
  134. package/src/ontology/operations/naming-policy.ts +132 -0
  135. package/src/ontology/operations/notausgang.ts +64 -0
  136. package/src/ontology/operations/release.ts +121 -0
  137. package/src/ontology/operations/sternsystem.ts +192 -0
  138. package/src/ontology/operations/werkstatt.ts +48 -0
  139. package/src/ontology/schemas/biome.ts +7 -2
  140. package/src/ontology/schemas/capability.ts +1 -1
  141. package/src/ontology/schemas/constellation.ts +6 -1
  142. package/src/ontology/schemas/index.ts +6 -2
  143. package/src/ontology/schemas/manifest-resolver.ts +6 -1
  144. package/src/ontology/schemas/page-entry.ts +9 -5
  145. package/src/ontology/schemas/system/growth.ts +6 -1
  146. package/src/ontology/schemas/system/manifest.ts +40 -1
  147. package/src/ontology/schemas/system/page-output.ts +7 -6
  148. package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
  149. package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
  150. package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
  151. package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
  152. package/src/ontology/schemas/system/tests/release.test.ts +22 -0
  153. package/src/ontology/schemas/system/tests/text.test.ts +18 -0
  154. package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
  155. package/src/ontology/schemas/system/text.ts +15 -0
  156. package/src/ontology/schemas/system.ts +6 -2
  157. package/src/ontology/schemas/tests/biome.test.ts +19 -0
  158. package/src/ontology/schemas/tests/capability.test.ts +22 -0
  159. package/src/ontology/schemas/tests/constellation.test.ts +22 -0
  160. package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
  161. package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
  162. package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
  163. package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
  164. package/src/ontology/schemas/tests/site-family.test.ts +18 -0
  165. package/src/ontology/schemas/tests/system.test.ts +20 -0
  166. package/src/ontology/shared-section-props/body-fragments.ts +2 -0
  167. package/src/ontology/tests/sternsystem-owner.test.ts +1 -1
  168. package/src/ontology/vitest.config.ts +1 -0
  169. package/src/{share → page}/page.ts +13 -9
  170. package/src/{share → page}/tests/dev-props-validator.test.ts +1 -1
  171. package/src/{share → page}/tests/hero-section-numbering-parity.test.ts +1 -1
  172. package/src/passport/index.ts +6 -1
  173. package/src/passport/key-rotate.ts +10 -2
  174. package/src/passport/schema.ts +6 -1
  175. package/src/passport/sign.ts +8 -3
  176. package/src/passport/vitest.config.ts +1 -0
  177. package/src/pbt/index.test.ts +95 -0
  178. package/src/pbt/index.ts +251 -0
  179. package/src/plugin/plugin-contract.ts +1 -1
  180. package/src/{share → policy}/entitlement.ts +24 -4
  181. package/src/{share → policy}/feature-policy.ts +8 -3
  182. package/src/{share → policy}/visibility.ts +7 -2
  183. package/src/redirects/redirects.ts +117 -0
  184. package/src/{share → redirects/tests}/redirects.test.ts +1 -1
  185. package/src/remediation/remediation-catalog.ts +316 -0
  186. package/src/{share/routes → routes}/template-filter.ts +1 -1
  187. package/src/{share → runtime}/runtime-context.ts +7 -2
  188. package/src/{share → runtime}/shared-context.ts +1 -1
  189. package/src/{share/schemas → schemas}/claims.ts +2 -2
  190. package/src/{share/schemas → schemas}/effects.ts +6 -1
  191. package/src/{share/schemas → schemas}/features.ts +1 -1
  192. package/src/{share/schemas → schemas}/index.ts +3 -3
  193. package/src/{share/schemas → schemas}/navigation.ts +6 -1
  194. package/src/{share/schemas → schemas}/page-base.ts +6 -2
  195. package/src/{share/schemas → schemas}/person.ts +1 -1
  196. package/src/{share/schemas → schemas}/standard-list-item.ts +1 -1
  197. package/src/schemas/tests/claim-records.test.ts +52 -0
  198. package/src/schemas/tests/claims.test.ts +23 -0
  199. package/src/schemas/tests/effects.test.ts +116 -0
  200. package/src/schemas/tests/features.test.ts +61 -0
  201. package/src/schemas/tests/horizontal-align.test.ts +16 -0
  202. package/src/schemas/tests/icon-color.test.ts +16 -0
  203. package/src/schemas/tests/live-photo.test.ts +40 -0
  204. package/src/schemas/tests/material-credit.test.ts +51 -0
  205. package/src/schemas/tests/media.test.ts +26 -0
  206. package/src/schemas/tests/navigation.test.ts +58 -0
  207. package/src/schemas/tests/page-base.test.ts +19 -0
  208. package/src/schemas/tests/participant.test.ts +26 -0
  209. package/src/schemas/tests/person.test.ts +12 -0
  210. package/src/schemas/tests/print.test.ts +30 -0
  211. package/src/schemas/tests/section-background.test.ts +31 -0
  212. package/src/schemas/tests/section-body.test.ts +16 -0
  213. package/src/schemas/tests/section-cards.test.ts +17 -0
  214. package/src/schemas/tests/section-cta.test.ts +41 -0
  215. package/src/schemas/tests/section-header.test.ts +31 -0
  216. package/src/schemas/tests/section-image.test.ts +24 -0
  217. package/src/schemas/tests/section-motion.test.ts +29 -0
  218. package/src/schemas/tests/section-shell.test.ts +42 -0
  219. package/src/schemas/tests/section-stats.test.ts +17 -0
  220. package/src/schemas/tests/site-background.test.ts +33 -0
  221. package/src/schemas/tests/standard-list-item.test.ts +16 -0
  222. package/src/{share → sections}/rfc0042-utils.ts +1 -1
  223. package/src/{share/semantic → semantic}/block-extractors/index.ts +20 -1
  224. package/src/{share/semantic → semantic}/business-projection.ts +95 -1
  225. package/src/semantic/canonical-uri.ts +54 -0
  226. package/src/{share/semantic → semantic}/extract.ts +100 -1
  227. package/src/semantic/fact-extraction.test.ts +47 -0
  228. package/src/semantic/fact-extraction.ts +46 -0
  229. package/src/semantic/freshness.ts +67 -0
  230. package/src/{share/semantic → semantic}/ids.ts +6 -1
  231. package/src/{share/semantic → semantic}/index.ts +9 -2
  232. package/src/{share/semantic → semantic}/jsonld/breadcrumb.ts +6 -2
  233. package/src/{share/semantic → semantic}/jsonld/context.ts +6 -2
  234. package/src/{share/semantic → semantic}/jsonld/faq.ts +6 -2
  235. package/src/{share/semantic → semantic}/jsonld/initiative.ts +6 -1
  236. package/src/{share/semantic → semantic}/jsonld/organization.ts +6 -1
  237. package/src/{share/semantic → semantic}/jsonld/person.ts +6 -2
  238. package/src/{share/semantic → semantic}/jsonld/service.ts +6 -2
  239. package/src/semantic/jsonld/shared.ts +36 -0
  240. package/src/semantic/jsonld/tests/article.test.ts +89 -0
  241. package/src/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
  242. package/src/semantic/jsonld/tests/collection-list.test.ts +39 -0
  243. package/src/semantic/jsonld/tests/faq.test.ts +60 -0
  244. package/src/semantic/jsonld/tests/initiative.test.ts +56 -0
  245. package/src/semantic/jsonld/tests/organization.test.ts +123 -0
  246. package/src/semantic/jsonld/tests/person.test.ts +89 -0
  247. package/src/semantic/jsonld/tests/service.test.ts +65 -0
  248. package/src/semantic/jsonld/tests/shared.test.ts +41 -0
  249. package/src/semantic/jsonld/tests/video.test.ts +84 -0
  250. package/src/semantic/jsonld/tests/webpage.test.ts +93 -0
  251. package/src/semantic/jsonld/tests/website.test.ts +27 -0
  252. package/src/semantic/jsonld/types.ts +24 -0
  253. package/src/{share/semantic → semantic}/jsonld/webpage.ts +6 -1
  254. package/src/semantic/jsonld/website.ts +32 -0
  255. package/src/{share/semantic → semantic}/jsonld.ts +6 -1
  256. package/src/{share/semantic → semantic}/llms.ts +41 -3
  257. package/src/{share/semantic → semantic}/models.ts +27 -38
  258. package/src/{share/semantic → semantic}/organization-profile.ts +3 -0
  259. package/src/{share/semantic → semantic}/page-builders/markdown-page.ts +6 -3
  260. package/src/{share/semantic → semantic}/page-utils.ts +6 -3
  261. package/src/semantic/tests/ai.test.ts +55 -0
  262. package/src/semantic/tests/block-extraction.test.ts +34 -0
  263. package/src/{share/tests/breadcrumbs.test.ts → semantic/tests/breadcrumbs-0229.test.ts} +1 -1
  264. package/src/semantic/tests/breadcrumbs.test.ts +127 -0
  265. package/src/{share → semantic}/tests/build-page-price-markers.test.ts +2 -2
  266. package/src/semantic/tests/build-page.test.ts +122 -0
  267. package/src/semantic/tests/business-projection.test.ts +367 -0
  268. package/src/semantic/tests/canonical-uri.test.ts +56 -0
  269. package/src/semantic/tests/extract.test.ts +209 -0
  270. package/src/semantic/tests/feed.test.ts +113 -0
  271. package/src/semantic/tests/freshness.pbt.test.ts +108 -0
  272. package/src/semantic/tests/helpers.ts +32 -0
  273. package/src/semantic/tests/ids.test.ts +118 -0
  274. package/src/{share/tests/image-sitemap.test.ts → semantic/tests/image-sitemap-0150.test.ts} +1 -1
  275. package/src/semantic/tests/image-sitemap.test.ts +109 -0
  276. package/src/{share → semantic}/tests/jsonld-service.test.ts +2 -2
  277. package/src/{share → semantic}/tests/jsonld-video.test.ts +3 -3
  278. package/src/{share → semantic}/tests/jsonld-webpage.test.ts +3 -3
  279. package/src/semantic/tests/jsonld.test.ts +61 -0
  280. package/src/{share → semantic}/tests/llms-0789.test.ts +2 -2
  281. package/src/semantic/tests/llms-policy.test.ts +60 -0
  282. package/src/semantic/tests/llms.test.ts +164 -0
  283. package/src/semantic/tests/markdown-hygiene.test.ts +72 -0
  284. package/src/semantic/tests/markdown-page.test.ts +97 -0
  285. package/src/{share → semantic}/tests/organization-jsonld.test.ts +3 -3
  286. package/src/semantic/tests/organization-profile.test.ts +107 -0
  287. package/src/{share/tests/output-projection.test.ts → semantic/tests/output-projection-0143.test.ts} +1 -1
  288. package/src/semantic/tests/output-projection.test.ts +77 -0
  289. package/src/semantic/tests/page-markdown.test.ts +98 -0
  290. package/src/semantic/tests/page-utils.test.ts +88 -0
  291. package/src/{share/tests/price-marker-resolver.test.ts → semantic/tests/price-marker-resolver-0767.test.ts} +2 -2
  292. package/src/semantic/tests/price-marker-resolver.test.ts +90 -0
  293. package/src/semantic/tests/project-claims.pbt.test.ts +60 -0
  294. package/src/semantic/tests/robots.test.ts +70 -0
  295. package/src/{share/semantic → semantic}/tests/split-sentences.test.ts +1 -1
  296. package/src/{share → semantic}/tests/twin-path.test.ts +1 -1
  297. package/src/semantic/tests/update-stamp.test.ts +102 -0
  298. package/src/signing/index.ts +13 -0
  299. package/src/signing/key.ts +139 -0
  300. package/src/signing/sign.ts +66 -0
  301. package/src/signing/types.ts +29 -0
  302. package/src/{share/slug → slug}/slug-url.ts +4 -1
  303. package/src/stack/run-tool.ts +108 -0
  304. package/src/stack/stack-checks.ts +124 -0
  305. package/src/stack/tests/run-tool.test.ts +138 -0
  306. package/src/stack/tests/stack-checks.test.ts +141 -0
  307. package/src/stack/tests/walk-files.test.ts +131 -0
  308. package/src/stack/walk-files.ts +114 -0
  309. package/src/surface/blueprint.ts +342 -66
  310. package/src/surface/decision-composer.ts +6 -1
  311. package/src/surface/geo.ts +6 -1
  312. package/src/surface/index.ts +11 -7
  313. package/src/surface/types.ts +2 -2
  314. package/src/surface/vitest.config.ts +1 -0
  315. package/src/tests/signing-core.test.ts +129 -0
  316. package/src/{share → text}/css-value-normalize.ts +6 -1
  317. package/src/text/string-utils.ts +24 -0
  318. package/src/{share → text}/tests/text-normalize.test.ts +5 -0
  319. package/src/{share → text}/text-normalize.ts +4 -3
  320. package/src/{share → text}/wrap-inline-numbers.ts +1 -1
  321. package/src/types/axiom-study.d.ts +36 -0
  322. package/src/types/hls-js-light.d.ts +21 -0
  323. package/src/types/qrcode-browser.d.ts +23 -0
  324. package/src/typography/fix.pbt.test.ts +208 -0
  325. package/src/typography/fix.ts +148 -0
  326. package/src/typography/index.ts +54 -0
  327. package/src/typography/locale-defaults.ts +97 -0
  328. package/src/typography/rules-tier1.test.ts +459 -0
  329. package/src/typography/rules-tier1.ts +925 -0
  330. package/src/typography/rules-tier2-locale.pbt.test.ts +190 -0
  331. package/src/typography/rules-tier2-locale.test.ts +286 -0
  332. package/src/typography/rules-tier2-locale.ts +399 -0
  333. package/src/typography/rules-tier2-structure.test.ts +330 -0
  334. package/src/typography/rules-tier2-structure.ts +378 -0
  335. package/src/typography/rules-tier3-advisory.test.ts +290 -0
  336. package/src/typography/rules-tier3-advisory.ts +239 -0
  337. package/src/typography/text-surface.test.ts +244 -0
  338. package/src/typography/text-surface.ts +448 -0
  339. package/src/index.ts +0 -14
  340. package/src/integration/port-barrel.ts +0 -105
  341. package/src/share/content/index.ts +0 -16
  342. package/src/share/content-discipline/index.ts +0 -14
  343. package/src/share/index.ts +0 -86
  344. package/src/share/legal/index.ts +0 -1
  345. package/src/share/redirects.ts +0 -54
  346. package/src/share/scripts/index.ts +0 -26
  347. package/src/share/semantic/jsonld/shared.ts +0 -31
  348. package/src/share/semantic/jsonld/types.ts +0 -18
  349. package/src/share/semantic/jsonld/website.ts +0 -27
  350. package/src/share/string-utils.ts +0 -19
  351. package/src/share/types/axiom-study.d.ts +0 -22
  352. package/src/share/types/hls-js-light.d.ts +0 -16
  353. package/src/share/types/qrcode-browser.d.ts +0 -18
  354. package/src/share/utility-registry.yaml +0 -36
  355. package/src/share/vitest.config.ts +0 -8
  356. package/src/surface/blueprint-schema.ts +0 -314
  357. package/src/surface/blueprint-types.ts +0 -256
  358. /package/src/{share/agent → agent}/api-catalog.ts +0 -0
  359. /package/src/{share/agent → agent}/capability.ts +0 -0
  360. /package/src/{share/agent → agent}/dns-aid.ts +0 -0
  361. /package/src/{share/agent → agent}/fleet-catalog.ts +0 -0
  362. /package/src/{share/agent → agent}/index.ts +0 -0
  363. /package/src/{share/agent → agent}/knowledge.ts +0 -0
  364. /package/src/{share/agent → agent}/mcp-card.ts +0 -0
  365. /package/src/{share/agent → agent}/omit-empty.ts +0 -0
  366. /package/src/{share/agent → agent}/openapi.ts +0 -0
  367. /package/src/{share/agent → agent}/proof.ts +0 -0
  368. /package/src/{share/agent → agent}/search.test.ts +0 -0
  369. /package/src/{share → attribution}/attribution-display.ts +0 -0
  370. /package/src/{share/scripts → client-scripts}/gsap-mountain-journey.ts +0 -0
  371. /package/src/{share/scripts → client-scripts}/gsap-parallax.ts +0 -0
  372. /package/src/{share/scripts → client-scripts}/gsap-reveal.ts +0 -0
  373. /package/src/{share/scripts → client-scripts}/gsap-stagger.ts +0 -0
  374. /package/src/{share/scripts → client-scripts}/live-photos.ts +0 -0
  375. /package/src/{share/scripts → client-scripts}/video-player.ts +0 -0
  376. /package/src/{share/content → content}/resolve-field-path.ts +0 -0
  377. /package/src/{share/content → content}/substitute-deep.ts +0 -0
  378. /package/src/{share → content}/tests/content-ref-index.test.ts +0 -0
  379. /package/src/{share/content-discipline → content-discipline}/types.ts +0 -0
  380. /package/src/{share → image-provider}/image-provider.ts +0 -0
  381. /package/src/{share/knowledge → knowledge}/claim.ts +0 -0
  382. /package/src/{share/knowledge → knowledge}/derivation.ts +0 -0
  383. /package/src/{share/knowledge → knowledge}/freshness.ts +0 -0
  384. /package/src/{share/knowledge → knowledge}/index.ts +0 -0
  385. /package/src/{share/knowledge → knowledge}/ledger.ts +0 -0
  386. /package/src/{share/knowledge → knowledge}/plan.ts +0 -0
  387. /package/src/{share/knowledge → knowledge}/source.ts +0 -0
  388. /package/src/{share/legal → legal}/translation-policy.ts +0 -0
  389. /package/src/{share/middleware → middleware}/language-redirect.ts +0 -0
  390. /package/src/{share/middleware → middleware}/tests/access-protection.test.ts +0 -0
  391. /package/src/{share → node}/fs/index.ts +0 -0
  392. /package/src/{share/tests/markdown-twin-provenance.test.ts → node/tests/markdown-twin-provenance-0613.test.ts} +0 -0
  393. /package/src/{share → offers}/offer-capacity.ts +0 -0
  394. /package/src/{share/onboarding-yaml/index.ts → onboarding/yaml.ts} +0 -0
  395. /package/src/{share → ontology}/tests/ontology-role-by-cosmic-name.test.ts +0 -0
  396. /package/src/{share/routes → routes}/tests/template-filter.test.ts +0 -0
  397. /package/src/{share/schemas → schemas}/claim-records.ts +0 -0
  398. /package/src/{share/schemas → schemas}/horizontal-align.ts +0 -0
  399. /package/src/{share/schemas → schemas}/icon-color.ts +0 -0
  400. /package/src/{share/schemas → schemas}/live-photo.ts +0 -0
  401. /package/src/{share/schemas → schemas}/material-credit.ts +0 -0
  402. /package/src/{share/schemas → schemas}/media.ts +0 -0
  403. /package/src/{share/schemas → schemas}/participant.ts +0 -0
  404. /package/src/{share/schemas → schemas}/print.ts +0 -0
  405. /package/src/{share/schemas → schemas}/section-background.ts +0 -0
  406. /package/src/{share/schemas → schemas}/section-body.ts +0 -0
  407. /package/src/{share/schemas → schemas}/section-cards.ts +0 -0
  408. /package/src/{share/schemas → schemas}/section-cta.ts +0 -0
  409. /package/src/{share/schemas → schemas}/section-header.ts +0 -0
  410. /package/src/{share/schemas → schemas}/section-image.ts +0 -0
  411. /package/src/{share/schemas → schemas}/section-motion.ts +0 -0
  412. /package/src/{share/schemas → schemas}/section-shell.ts +0 -0
  413. /package/src/{share/schemas → schemas}/section-stats.ts +0 -0
  414. /package/src/{share/schemas → schemas}/site-background.ts +0 -0
  415. /package/src/{share/semantic → semantic}/ai.ts +0 -0
  416. /package/src/{share/semantic → semantic}/block-extraction.ts +0 -0
  417. /package/src/{share/semantic → semantic}/breadcrumbs.ts +0 -0
  418. /package/src/{share/semantic → semantic}/build-page.ts +0 -0
  419. /package/src/{share/semantic → semantic}/feed.ts +0 -0
  420. /package/src/{share/semantic → semantic}/image-sitemap.ts +0 -0
  421. /package/src/{share/semantic → semantic}/jsonld/article.ts +0 -0
  422. /package/src/{share/semantic → semantic}/jsonld/collection-list.ts +0 -0
  423. /package/src/{share/semantic → semantic}/jsonld/video.ts +0 -0
  424. /package/src/{share/semantic → semantic}/llms-policy.ts +0 -0
  425. /package/src/{share/semantic → semantic}/markdown-hygiene.ts +0 -0
  426. /package/src/{share/semantic → semantic}/output-projection.ts +0 -0
  427. /package/src/{share/semantic → semantic}/page-markdown.ts +0 -0
  428. /package/src/{share/semantic → semantic}/price-marker-resolver.ts +0 -0
  429. /package/src/{share/semantic → semantic}/robots.ts +0 -0
  430. /package/src/{share/semantic → semantic}/update-stamp.ts +0 -0
  431. /package/src/{share/slug → slug}/heading-slugger.ts +0 -0
  432. /package/src/{share/slug → slug}/index.ts +0 -0
  433. /package/src/{share/slug → slug}/slug-id.ts +0 -0
  434. /package/src/{share/slug → slug}/strategies.ts +0 -0
  435. /package/src/{share/slug → slug}/tests/slug.test.ts +0 -0
  436. /package/src/{share → text}/tests/text-normalize.pbt.test.ts +0 -0
  437. /package/src/{share → text}/text-position.ts +0 -0
@@ -6,7 +6,12 @@
6
6
  </non-goals>
7
7
  </MODULE_CONTRACT>
8
8
  <CHANGE_SUMMARY>
9
- <item>Migrated Lenis initialization to @warpgogol/werkstatt-shared/share/scripts for platform-wide reuse.</item>
9
+ <item>RFC-1097: step 6 — compass.migrate codemod run
10
+
11
+ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.</item>
12
+ <item>RFC-1097: sweep — werkstatt-engine clean
13
+
14
+ Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
10
15
  </CHANGE_SUMMARY>
11
16
  */
12
17
 
@@ -11,7 +11,12 @@
11
11
  </MODULE_CONTRACT>
12
12
 
13
13
  <CHANGE_SUMMARY>
14
- <item>Tidied by compass.changesummary.tidy; see git history for prior entries.</item>
14
+ <item>RFC-1097: step 6 — compass.migrate codemod run
15
+
16
+ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.</item>
17
+ <item>RFC-1097: sweep — werkstatt-engine clean
18
+
19
+ Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
15
20
  </CHANGE_SUMMARY> ***************/
16
21
 
17
22
  async function initLordIcon(): Promise<void> {
@@ -6,7 +6,12 @@
6
6
  </non-goals>
7
7
  </MODULE_CONTRACT>
8
8
  <CHANGE_SUMMARY>
9
- <item>Created standard layout orchestrator in @warpgogol/werkstatt-shared/share/scripts to reduce code duplication in apps.</item>
9
+ <item>RFC-1097: step 6 — compass.migrate codemod run
10
+
11
+ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.</item>
12
+ <item>RFC-1097: sweep — werkstatt-engine clean
13
+
14
+ Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
10
15
  </CHANGE_SUMMARY>
11
16
  */
12
17
 
@@ -15,6 +20,7 @@
15
20
  import { applyExternalLinkBehavior } from "./external-links.ts";
16
21
  import { initLordIconOnDemand } from "./lordicon.ts";
17
22
  import { initLenis } from "./lenis.ts";
23
+ import { initScrollSpy } from "./scroll-spy.ts";
18
24
 
19
25
  export interface OrchestrationOptions {
20
26
  headerOffset?: number;
@@ -152,4 +158,7 @@ export async function runStandardLayoutOrchestration(
152
158
  const { initExternalLinkQr } = await import("./external-link-qr.ts");
153
159
  initExternalLinkQr();
154
160
  }
161
+
162
+ // 12. RFC-1061: scroll-spy URL hash updates (always-on, zero-dependency).
163
+ initScrollSpy();
155
164
  }
@@ -8,7 +8,12 @@
8
8
  </non-goals>
9
9
  </MODULE_CONTRACT>
10
10
  <CHANGE_SUMMARY>
11
- <item>Tidied by compass.changesummary.tidy; see git history for prior entries.</item>
11
+ <item>RFC-1097: step 6 — compass.migrate codemod run
12
+
13
+ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.</item>
14
+ <item>RFC-1097: sweep — werkstatt-engine clean
15
+
16
+ Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
12
17
  </CHANGE_SUMMARY>
13
18
  ***************************************************************/
14
19
 
@@ -0,0 +1,106 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>scroll-spy — watches section elements via IntersectionObserver and updates the URL hash via history.replaceState as the user scrolls (RFC-1061).</purpose>
4
+ <singleton-state>
5
+ <item>Module-level `activeObserver` and `activeCleanup` enforce single-instance idempotency (AC-9). Calling `initScrollSpy` twice disconnects the previous observer before creating a new one.</item>
6
+ </singleton-state>
7
+ <non-goals>
8
+ <item>Do not add navigation highlight UI — that is a separate concern.</item>
9
+ <item>Do not use pushState — replaceState only to avoid history pollution.</item>
10
+ <item>Do not modify section rendering or section id resolution.</item>
11
+ </non-goals>
12
+ </MODULE_CONTRACT>
13
+ <CHANGE_SUMMARY>
14
+ <item>RFC-1061: Created scroll-spy script with IntersectionObserver-based section tracking and URL hash updates.</item>
15
+ <item>RFC-1097: sweep — tail packages clean
16
+
17
+ Sweep batch 3: rewrote ~95 purposes across werkstatt-knowledge, werkstatt-shared, godot-game, phaser-game, lifecycle-core, projektarchiv-*, portal-*, billing-*, typescript (CONTRACT-02/PURPOSE-02). Real KEY_DECISIONS on 5 godot utils, non-goals on 5 CONTRACT-03 files, headers on 4 headerless files, CS-07 history literal fix on 2 files. Policy: vitest.config.ts + test-fixtures testPatterns, worker-configuration.d.ts excludedPath. All non-site/engine packages now 0 diagnostics.</item>
18
+ </CHANGE_SUMMARY>
19
+ */
20
+
21
+ export interface ScrollSpyOptions {
22
+ /** CSS selector for elements to observe. Default: "section[id]" */
23
+ selector?: string;
24
+ /** IntersectionObserver rootMargin. Default: "-20% 0px -80% 0px" */
25
+ rootMargin?: string;
26
+ /** IntersectionObserver threshold. Default: 0 */
27
+ threshold?: number;
28
+ /** Whether to clear the hash when scrolled above the first section. Default: true */
29
+ clearAtTop?: boolean;
30
+ }
31
+
32
+ let activeCleanup: (() => void) | null = null;
33
+
34
+ export function initScrollSpy(options?: ScrollSpyOptions): () => void {
35
+ const {
36
+ selector = "section[id]",
37
+ rootMargin = "-20% 0px -80% 0px",
38
+ threshold = 0,
39
+ clearAtTop = true,
40
+ } = options ?? {};
41
+
42
+ if (activeCleanup) {
43
+ activeCleanup();
44
+ activeCleanup = null;
45
+ }
46
+
47
+ if (!("IntersectionObserver" in window)) {
48
+ return () => {};
49
+ }
50
+
51
+ const sections = Array.from(document.querySelectorAll<HTMLElement>(selector)).filter(
52
+ (el) => !el.closest(".wl-modal"),
53
+ );
54
+
55
+ if (sections.length === 0) {
56
+ return () => {};
57
+ }
58
+
59
+ let currentId: string | null = null;
60
+
61
+ const updateHash = (id: string | null) => {
62
+ if (id === currentId) return;
63
+ currentId = id;
64
+ if (id) {
65
+ history.replaceState(null, "", `#${id}`);
66
+ } else if (clearAtTop) {
67
+ history.replaceState(null, "", location.pathname + location.search);
68
+ }
69
+ };
70
+
71
+ const observer = new IntersectionObserver(
72
+ (entries) => {
73
+ let topmostVisible: HTMLElement | null = null;
74
+ let topmostTop = Infinity;
75
+
76
+ for (const entry of entries) {
77
+ if (!entry.isIntersecting) continue;
78
+ const el = entry.target as HTMLElement;
79
+ const rect = el.getBoundingClientRect();
80
+ if (rect.top < topmostTop) {
81
+ topmostTop = rect.top;
82
+ topmostVisible = el;
83
+ }
84
+ }
85
+
86
+ if (topmostVisible) {
87
+ updateHash(topmostVisible.id);
88
+ } else if (clearAtTop) {
89
+ updateHash(null);
90
+ }
91
+ },
92
+ { rootMargin, threshold },
93
+ );
94
+
95
+ for (const section of sections) {
96
+ observer.observe(section);
97
+ }
98
+
99
+ const cleanup = () => {
100
+ observer.disconnect();
101
+ activeCleanup = null;
102
+ };
103
+
104
+ activeCleanup = cleanup;
105
+ return cleanup;
106
+ }
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initExternalLinkQr } from "../external-link-qr.ts";
3
+
4
+ describe("initExternalLinkQr", () => {
5
+ it("is a function", () => {
6
+ expect(typeof initExternalLinkQr).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { applyExternalLinkBehavior } from "../external-links.ts";
3
+
4
+ describe("applyExternalLinkBehavior", () => {
5
+ beforeEach(() => {
6
+ vi.stubGlobal("document", {
7
+ querySelectorAll: vi.fn(() => []),
8
+ });
9
+ vi.stubGlobal("window", {
10
+ location: { href: "https://example.com/", origin: "https://example.com" },
11
+ });
12
+ });
13
+
14
+ afterEach(() => {
15
+ vi.unstubAllGlobals();
16
+ });
17
+
18
+ it("is a function", () => {
19
+ expect(typeof applyExternalLinkBehavior).toBe("function");
20
+ });
21
+
22
+ it("does not throw when no anchors exist", () => {
23
+ expect(() => applyExternalLinkBehavior()).not.toThrow();
24
+ });
25
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initGsapCounter } from "../gsap-counter.ts";
3
+
4
+ describe("initGsapCounter", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof initGsapCounter).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initMountainJourneyAnimation } from "../gsap-mountain-journey.ts";
3
+
4
+ describe("initMountainJourneyAnimation", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof initMountainJourneyAnimation).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initGsapParallax } from "../gsap-parallax.ts";
3
+
4
+ describe("initGsapParallax", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof initGsapParallax).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initGsapReveal } from "../gsap-reveal.ts";
3
+
4
+ describe("initGsapReveal", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof initGsapReveal).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initGsapStagger } from "../gsap-stagger.ts";
3
+
4
+ describe("initGsapStagger", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof initGsapStagger).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initInlineNumberAnimation } from "../inline-number-animation.ts";
3
+
4
+ describe("initInlineNumberAnimation", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof initInlineNumberAnimation).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initLenis } from "../lenis.ts";
3
+
4
+ describe("initLenis", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof initLenis).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initLivePhotos } from "../live-photos.ts";
3
+
4
+ describe("initLivePhotos", () => {
5
+ it("is an async function", () => {
6
+ expect(typeof initLivePhotos).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,8 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { initLordIconOnDemand } from "../lordicon.ts";
3
+
4
+ describe("initLordIconOnDemand", () => {
5
+ it("is a function", () => {
6
+ expect(typeof initLordIconOnDemand).toBe("function");
7
+ });
8
+ });
@@ -0,0 +1,47 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+
3
+ vi.mock("../scroll-spy.ts", () => ({
4
+ initScrollSpy: vi.fn(() => () => {}),
5
+ }));
6
+
7
+ import { runStandardLayoutOrchestration } from "../orchestrator.ts";
8
+ import { initScrollSpy } from "../scroll-spy.ts";
9
+
10
+ describe("runStandardLayoutOrchestration", () => {
11
+ beforeEach(() => {
12
+ class MockElement {}
13
+ vi.stubGlobal("Element", MockElement);
14
+ vi.stubGlobal("window", {
15
+ matchMedia: vi.fn(() => ({ matches: false })),
16
+ addEventListener: vi.fn(),
17
+ });
18
+ vi.stubGlobal("document", {
19
+ readyState: "complete",
20
+ querySelector: vi.fn(() => null),
21
+ addEventListener: vi.fn(),
22
+ });
23
+ });
24
+
25
+ afterEach(() => {
26
+ vi.unstubAllGlobals();
27
+ vi.clearAllMocks();
28
+ });
29
+
30
+ it("is an async function", () => {
31
+ expect(typeof runStandardLayoutOrchestration).toBe("function");
32
+ });
33
+
34
+ it("AC-6: calls initScrollSpy unconditionally as a new step", async () => {
35
+ await runStandardLayoutOrchestration();
36
+ expect(initScrollSpy).toHaveBeenCalled();
37
+ });
38
+
39
+ it("AC-6: calls initScrollSpy even when all opt-in flags are false", async () => {
40
+ await runStandardLayoutOrchestration({
41
+ counters: false,
42
+ reveal: false,
43
+ smoothScroll: false,
44
+ });
45
+ expect(initScrollSpy).toHaveBeenCalled();
46
+ });
47
+ });
@@ -0,0 +1,23 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { scheduleTask } from "../scheduler.ts";
3
+
4
+ describe("scheduleTask", () => {
5
+ beforeEach(() => {
6
+ vi.stubGlobal("window", {
7
+ requestIdleCallback: vi.fn(),
8
+ setTimeout: vi.fn(),
9
+ });
10
+ });
11
+
12
+ afterEach(() => {
13
+ vi.unstubAllGlobals();
14
+ });
15
+
16
+ it("is a function", () => {
17
+ expect(typeof scheduleTask).toBe("function");
18
+ });
19
+
20
+ it("does not throw when scheduling a task", () => {
21
+ expect(() => scheduleTask(() => undefined)).not.toThrow();
22
+ });
23
+ });
@@ -0,0 +1,204 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import { initScrollSpy } from "../scroll-spy.ts";
3
+
4
+ interface MockRect {
5
+ top: number;
6
+ }
7
+ interface MockElement {
8
+ id: string;
9
+ closest: (sel: string) => object | null;
10
+ getBoundingClientRect: () => MockRect;
11
+ }
12
+ interface MockEntry {
13
+ target: MockElement;
14
+ isIntersecting: boolean;
15
+ boundingClientRect: MockRect;
16
+ }
17
+ interface MockObserverInstance {
18
+ observe: ReturnType<typeof vi.fn>;
19
+ unobserve: ReturnType<typeof vi.fn>;
20
+ disconnect: ReturnType<typeof vi.fn>;
21
+ takeRecords: ReturnType<typeof vi.fn>;
22
+ }
23
+ interface MockDocument {
24
+ body: { innerHTML: string };
25
+ readyState: string;
26
+ querySelector: ReturnType<typeof vi.fn>;
27
+ querySelectorAll: ReturnType<typeof vi.fn>;
28
+ getElementById: ReturnType<typeof vi.fn>;
29
+ addEventListener: ReturnType<typeof vi.fn>;
30
+ }
31
+
32
+ let observerCallbacks: Array<(entries: MockEntry[]) => void>;
33
+ let observerInstances: MockObserverInstance[];
34
+ let mockHistory: { replaceState: ReturnType<typeof vi.fn> };
35
+ let mockDocument: MockDocument;
36
+ let sectionCache: Map<string, MockElement>;
37
+
38
+ function parseSections(html: string, selector: string): MockElement[] {
39
+ if (selector !== "section[id]") return [];
40
+ const sections: MockElement[] = [];
41
+ const sectionRegex = /<section\s+id="([^"]+)"[^>]*>([\s\S]*?)<\/section>/gi;
42
+ let match;
43
+ while ((match = sectionRegex.exec(html)) !== null) {
44
+ const modalBefore = html.substring(0, match.index);
45
+ const lastModalOpen = modalBefore.lastIndexOf('class="wl-modal"');
46
+ const lastModalClose = modalBefore.lastIndexOf("</div>");
47
+ const inModal = lastModalOpen > lastModalClose;
48
+ const el = {
49
+ id: match[1],
50
+ closest: (sel: string) => (sel === ".wl-modal" && inModal ? {} : null),
51
+ getBoundingClientRect: () => ({ top: 0 }),
52
+ };
53
+ sectionCache.set(match[1], el);
54
+ sections.push(el);
55
+ }
56
+ return sections;
57
+ }
58
+
59
+ describe("initScrollSpy", () => {
60
+ beforeEach(() => {
61
+ observerCallbacks = [];
62
+ observerInstances = [];
63
+ sectionCache = new Map();
64
+
65
+ class MockIntersectionObserver {
66
+ constructor(cb: (entries: MockEntry[]) => void) {
67
+ observerCallbacks.push(cb);
68
+ const instance = {
69
+ observe: vi.fn(),
70
+ unobserve: vi.fn(),
71
+ disconnect: vi.fn(),
72
+ takeRecords: vi.fn(() => []),
73
+ };
74
+ observerInstances.push(instance);
75
+ return instance;
76
+ }
77
+ }
78
+
79
+ mockHistory = { replaceState: vi.fn() };
80
+ mockDocument = {
81
+ body: { innerHTML: "" },
82
+ readyState: "complete",
83
+ querySelector: vi.fn(() => null),
84
+ querySelectorAll: vi.fn((selector: string) => {
85
+ return parseSections(mockDocument.body.innerHTML, selector);
86
+ }),
87
+ getElementById: vi.fn((id: string) => sectionCache.get(id) ?? null),
88
+ addEventListener: vi.fn(),
89
+ };
90
+
91
+ vi.stubGlobal("window", {
92
+ IntersectionObserver: MockIntersectionObserver,
93
+ addEventListener: vi.fn(),
94
+ });
95
+ vi.stubGlobal("IntersectionObserver", MockIntersectionObserver);
96
+ vi.stubGlobal("history", mockHistory);
97
+ vi.stubGlobal("location", {
98
+ pathname: "/page",
99
+ search: "",
100
+ hash: "",
101
+ });
102
+ vi.stubGlobal("document", mockDocument);
103
+ });
104
+
105
+ afterEach(() => {
106
+ vi.unstubAllGlobals();
107
+ });
108
+
109
+ it("AC-1: updates URL hash via history.replaceState when a section is in view", () => {
110
+ mockDocument.body.innerHTML = '<section id="approach">Content</section>';
111
+ const cleanup = initScrollSpy();
112
+ const section = sectionCache.get("approach")!;
113
+
114
+ observerCallbacks[0]([
115
+ {
116
+ target: section,
117
+ isIntersecting: true,
118
+ boundingClientRect: { top: 100 },
119
+ },
120
+ ]);
121
+
122
+ expect(mockHistory.replaceState).toHaveBeenCalledWith(null, "", "#approach");
123
+ cleanup();
124
+ });
125
+
126
+ it("AC-2: clears URL hash when scrolled above the first section", () => {
127
+ mockDocument.body.innerHTML = '<section id="hero">Hero</section>';
128
+ const cleanup = initScrollSpy();
129
+ const section = sectionCache.get("hero")!;
130
+
131
+ observerCallbacks[0]([
132
+ {
133
+ target: section,
134
+ isIntersecting: true,
135
+ boundingClientRect: { top: 100 },
136
+ },
137
+ ]);
138
+ expect(mockHistory.replaceState).toHaveBeenCalledWith(null, "", "#hero");
139
+
140
+ observerCallbacks[0]([
141
+ {
142
+ target: section,
143
+ isIntersecting: false,
144
+ boundingClientRect: { top: 500 },
145
+ },
146
+ ]);
147
+ expect(mockHistory.replaceState).toHaveBeenCalledWith(null, "", "/page");
148
+
149
+ cleanup();
150
+ });
151
+
152
+ it("AC-3: returns a cleanup callback that disconnects the observer", () => {
153
+ mockDocument.body.innerHTML = '<section id="test">Test</section>';
154
+ const cleanup = initScrollSpy();
155
+
156
+ expect(typeof cleanup).toBe("function");
157
+ cleanup();
158
+
159
+ expect(observerInstances[0].disconnect).toHaveBeenCalled();
160
+ });
161
+
162
+ it("AC-4: returns immediately without creating an observer when no section[id] elements exist", () => {
163
+ mockDocument.body.innerHTML = "<div>No sections here</div>";
164
+ const cleanup = initScrollSpy();
165
+
166
+ expect(observerInstances.length).toBe(0);
167
+ expect(typeof cleanup).toBe("function");
168
+ cleanup();
169
+ });
170
+
171
+ it("AC-5: returns silently without throwing when IntersectionObserver is not supported", () => {
172
+ vi.unstubAllGlobals();
173
+ vi.stubGlobal("window", { addEventListener: vi.fn() });
174
+ vi.stubGlobal("history", mockHistory);
175
+ vi.stubGlobal("location", { pathname: "/page", search: "", hash: "" });
176
+ vi.stubGlobal("document", mockDocument);
177
+ mockDocument.body.innerHTML = '<section id="test">Test</section>';
178
+ sectionCache.clear();
179
+
180
+ expect(() => initScrollSpy()).not.toThrow();
181
+ });
182
+
183
+ it("AC-9: is idempotent — calling twice disconnects the first observer before creating a new one", () => {
184
+ mockDocument.body.innerHTML = '<section id="a">A</section><section id="b">B</section>';
185
+ initScrollSpy();
186
+ expect(observerInstances.length).toBe(1);
187
+
188
+ initScrollSpy();
189
+ expect(observerInstances.length).toBe(2);
190
+ expect(observerInstances[0].disconnect).toHaveBeenCalled();
191
+ });
192
+
193
+ it("excludes sections inside .wl-modal elements", () => {
194
+ mockDocument.body.innerHTML =
195
+ '<div class="wl-modal"><section id="modal-section">Modal</section></div>' +
196
+ '<section id="page-section">Page</section>';
197
+
198
+ initScrollSpy();
199
+
200
+ expect(observerInstances.length).toBe(1);
201
+ expect(observerInstances[0].observe).toHaveBeenCalledTimes(1);
202
+ expect(observerInstances[0].observe).toHaveBeenCalledWith(sectionCache.get("page-section"));
203
+ });
204
+ });