@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.
- package/README.md +31 -13
- package/README.uk.md +25 -15
- package/package.json +519 -762
- package/src/{share/agent → agent}/ard-catalog.ts +6 -1
- package/src/{share/agent → agent}/dns-aid.test.ts +2 -2
- package/src/{share/agent → agent}/manifest.ts +15 -1
- package/src/{share/agent → agent}/search.ts +6 -1
- package/src/{share → agent}/tests/api-catalog.test.ts +2 -2
- package/src/{share → agent}/tests/ard-catalog.test.ts +2 -2
- package/src/{share → agent}/tests/capability.test.ts +1 -1
- package/src/{share → agent}/tests/fleet-catalog.test.ts +1 -1
- package/src/{share → agent}/tests/knowledge.test.ts +2 -2
- package/src/{share → agent}/tests/manifest.test.ts +1 -1
- package/src/{share → agent}/tests/mcp-card.test.ts +2 -2
- package/src/agent/tests/omit-empty.test.ts +33 -0
- package/src/{share → agent}/tests/openapi.test.ts +2 -2
- package/src/{share → agent}/tests/proof.test.ts +1 -1
- package/src/{share → attribution}/material-credits.ts +4 -4
- package/src/{share → checks}/check-hints.ts +6 -1
- package/src/checks/index.ts +4 -1
- package/src/checks/lib/astro-site-url.ts +6 -1
- package/src/checks/lib/i18n.ts +14 -0
- package/src/checks/result-helpers.ts +22 -5
- package/src/checks/tests/remediation-auto-populate.test.ts +83 -0
- package/src/{share → client-scripts}/counter-utils.ts +6 -2
- package/src/{share/scripts → client-scripts}/external-link-qr.ts +11 -16
- package/src/{share/scripts → client-scripts}/external-links.ts +12 -1
- package/src/{share/scripts → client-scripts}/gsap-counter.ts +8 -3
- package/src/client-scripts/index.ts +35 -0
- package/src/{share/scripts → client-scripts}/inline-number-animation.ts +1 -1
- package/src/{share/scripts → client-scripts}/lenis.ts +6 -1
- package/src/{share/scripts → client-scripts}/lordicon.ts +6 -1
- package/src/{share/scripts → client-scripts}/orchestrator.ts +10 -1
- package/src/{share/scripts → client-scripts}/scheduler.ts +6 -1
- package/src/client-scripts/scroll-spy.ts +106 -0
- package/src/client-scripts/tests/external-link-qr.test.ts +8 -0
- package/src/client-scripts/tests/external-links.test.ts +25 -0
- package/src/client-scripts/tests/gsap-counter.test.ts +8 -0
- package/src/client-scripts/tests/gsap-mountain-journey.test.ts +8 -0
- package/src/client-scripts/tests/gsap-parallax.test.ts +8 -0
- package/src/client-scripts/tests/gsap-reveal.test.ts +8 -0
- package/src/client-scripts/tests/gsap-stagger.test.ts +8 -0
- package/src/client-scripts/tests/inline-number-animation.test.ts +8 -0
- package/src/client-scripts/tests/lenis.test.ts +8 -0
- package/src/client-scripts/tests/live-photos.test.ts +8 -0
- package/src/client-scripts/tests/lordicon.test.ts +8 -0
- package/src/client-scripts/tests/orchestrator.test.ts +47 -0
- package/src/client-scripts/tests/scheduler.test.ts +23 -0
- package/src/client-scripts/tests/scroll-spy.test.ts +204 -0
- package/src/{share/scripts → client-scripts/tests}/video-player.test.ts +51 -31
- package/src/component/contracts.ts +206 -0
- package/src/component/index.ts +11 -0
- package/src/{share → content}/content-reference.ts +3 -3
- package/src/{share/content → content}/entity-id.ts +6 -3
- package/src/content/index.ts +3 -0
- package/src/{share/content → content}/merge.ts +6 -1
- package/src/content/system-manifest.test.ts +107 -0
- package/src/content/system-manifest.ts +12 -94
- package/src/{share → content}/tests/resolve-field-path.test.ts +1 -1
- package/src/{share → content}/tests/substitute-deep.test.ts +1 -1
- package/src/content-discipline/index.ts +19 -0
- package/src/{share/content-discipline → content-discipline}/parsers.ts +2 -2
- package/src/{share/env.d.ts → env.d.ts} +6 -3
- package/src/fingerprint/canonical-json.ts +731 -0
- package/src/fingerprint/index.ts +12 -0
- package/src/fingerprint/primitives.ts +72 -0
- package/src/{share/i18n → i18n}/localization.ts +6 -2
- package/src/integration/adapters.ts +10 -71
- package/src/integration/dispatch.ts +1 -1
- package/src/integration/funnel.ts +6 -1
- package/src/integration/index.ts +101 -4
- package/src/integration/lagebild-destination-adapter.ts +108 -0
- package/src/integration/lagebild-ingress.ts +7 -12
- package/src/integration/orchestration.ts +11 -10
- package/src/integration/tests/deliver-event.test.ts +1 -1
- package/src/integration/tests/dispatch.test.ts +4 -4
- package/src/integration/tests/hub-runtime.test.ts +22 -14
- package/src/integration/vitest.config.ts +8 -0
- package/src/kernel/desired-state.ts +227 -0
- package/src/kernel/diagnostic.ts +289 -0
- package/src/kernel/fs-atomic.ts +131 -0
- package/src/kernel/index.ts +15 -0
- package/src/kernel/tests/fs-atomic.test.ts +150 -0
- package/src/kernel/tests/workspace-io.test.ts +187 -0
- package/src/kernel/types.ts +459 -0
- package/src/kernel/workspace-io.ts +248 -0
- package/src/{share → knowledge}/tests/claim.test.ts +1 -1
- package/src/{share → knowledge}/tests/derivation.test.ts +1 -1
- package/src/{share → knowledge}/tests/freshness.test.ts +1 -1
- package/src/{share → knowledge}/tests/ledger.test.ts +1 -1
- package/src/{share → knowledge}/tests/plan.test.ts +1 -1
- package/src/{share → knowledge}/tests/source.test.ts +1 -1
- package/src/legal/index.ts +15 -0
- package/src/{share/middleware → middleware}/access-protection.ts +8 -8
- package/src/{share → node}/dev-props-validator.ts +6 -1
- package/src/{share → node}/import-scan.ts +23 -3
- package/src/{share → node}/semantic/derived-prices-loader.ts +4 -2
- package/src/{share → node}/semantic/markdown-twin-provenance.ts +1 -1
- package/src/node/tests/derived-prices-loader.test.ts +59 -0
- package/src/node/tests/markdown-twin-provenance.test.ts +152 -0
- package/src/observability/env-resolver.ts +6 -1
- package/src/observability/index.ts +6 -1
- package/src/observability/otlp-converter.ts +6 -1
- package/src/observability/otlp-transport.ts +6 -1
- package/src/observability/pusher.ts +6 -1
- package/src/observability/redact.ts +6 -1
- package/src/observability/typed-refs.ts +6 -2
- package/src/observability/vitest.config.ts +1 -0
- package/src/{share → offers}/formula-eval.ts +3 -3
- package/src/{share → offers}/tests/formula-eval.test.ts +2 -2
- package/src/onboarding/index.ts +1 -0
- package/src/{share → onboarding}/tests/onboarding-yaml.test.ts +1 -1
- package/src/ontology/archetype-registry.ts +6 -1
- package/src/ontology/archetypes/index.json +33 -1
- package/src/ontology/archetypes/index.yaml +27 -1
- package/src/ontology/archetypes/sections/entity-grid.yaml +46 -0
- package/src/ontology/archetypes/sections/exit-diagram.yaml +43 -0
- package/src/ontology/biome-token-projection.ts +6 -1
- package/src/ontology/cosmic/index.ts +6 -1
- package/src/ontology/cosmic/moon-catalog.ts +6 -1
- package/src/ontology/cosmic/planet-catalog.ts +6 -1
- package/src/ontology/cosmic/star-catalog.ts +6 -1
- package/src/ontology/enums.ts +6 -1
- package/src/ontology/external-surfaces/index.ts +1 -1
- package/src/ontology/index.ts +6 -2
- package/src/ontology/manifest.ts +26 -4
- package/src/ontology/operations/artifact-store.ts +45 -0
- package/src/ontology/operations/dht.ts +98 -0
- package/src/ontology/operations/handoff.ts +92 -0
- package/src/ontology/operations/index.ts +187 -1
- package/src/ontology/operations/leitstand.ts +99 -0
- package/src/ontology/operations/materialization.ts +90 -0
- package/src/ontology/operations/mission.ts +111 -0
- package/src/ontology/operations/naming-policy.ts +132 -0
- package/src/ontology/operations/notausgang.ts +64 -0
- package/src/ontology/operations/release.ts +121 -0
- package/src/ontology/operations/sternsystem.ts +192 -0
- package/src/ontology/operations/werkstatt.ts +48 -0
- package/src/ontology/schemas/biome.ts +7 -2
- package/src/ontology/schemas/capability.ts +1 -1
- package/src/ontology/schemas/constellation.ts +6 -1
- package/src/ontology/schemas/index.ts +6 -2
- package/src/ontology/schemas/manifest-resolver.ts +6 -1
- package/src/ontology/schemas/page-entry.ts +9 -5
- package/src/ontology/schemas/system/growth.ts +6 -1
- package/src/ontology/schemas/system/manifest.ts +40 -1
- package/src/ontology/schemas/system/page-output.ts +7 -6
- package/src/ontology/schemas/system/tests/growth.test.ts +22 -0
- package/src/ontology/schemas/system/tests/integrations.test.ts +12 -0
- package/src/ontology/schemas/system/tests/manifest.test.ts +22 -0
- package/src/ontology/schemas/system/tests/page-output.test.ts +13 -0
- package/src/ontology/schemas/system/tests/release.test.ts +22 -0
- package/src/ontology/schemas/system/tests/text.test.ts +18 -0
- package/src/ontology/schemas/system/tests/verification.test.ts +12 -0
- package/src/ontology/schemas/system/text.ts +15 -0
- package/src/ontology/schemas/system.ts +6 -2
- package/src/ontology/schemas/tests/biome.test.ts +19 -0
- package/src/ontology/schemas/tests/capability.test.ts +22 -0
- package/src/ontology/schemas/tests/constellation.test.ts +22 -0
- package/src/ontology/schemas/tests/dns-records.test.ts +13 -0
- package/src/ontology/schemas/tests/manifest-resolver.test.ts +13 -0
- package/src/ontology/schemas/tests/page-entry.test.ts +22 -0
- package/src/ontology/schemas/tests/section-archetype.test.ts +12 -0
- package/src/ontology/schemas/tests/site-family.test.ts +18 -0
- package/src/ontology/schemas/tests/system.test.ts +20 -0
- package/src/ontology/shared-section-props/body-fragments.ts +2 -0
- package/src/ontology/tests/sternsystem-owner.test.ts +1 -1
- package/src/ontology/vitest.config.ts +1 -0
- package/src/{share → page}/page.ts +13 -9
- package/src/{share → page}/tests/dev-props-validator.test.ts +1 -1
- package/src/{share → page}/tests/hero-section-numbering-parity.test.ts +1 -1
- package/src/passport/index.ts +6 -1
- package/src/passport/key-rotate.ts +10 -2
- package/src/passport/schema.ts +6 -1
- package/src/passport/sign.ts +8 -3
- package/src/passport/vitest.config.ts +1 -0
- package/src/pbt/index.test.ts +95 -0
- package/src/pbt/index.ts +251 -0
- package/src/plugin/plugin-contract.ts +1 -1
- package/src/{share → policy}/entitlement.ts +24 -4
- package/src/{share → policy}/feature-policy.ts +8 -3
- package/src/{share → policy}/visibility.ts +7 -2
- package/src/redirects/redirects.ts +117 -0
- package/src/{share → redirects/tests}/redirects.test.ts +1 -1
- package/src/remediation/remediation-catalog.ts +316 -0
- package/src/{share/routes → routes}/template-filter.ts +1 -1
- package/src/{share → runtime}/runtime-context.ts +7 -2
- package/src/{share → runtime}/shared-context.ts +1 -1
- package/src/{share/schemas → schemas}/claims.ts +2 -2
- package/src/{share/schemas → schemas}/effects.ts +6 -1
- package/src/{share/schemas → schemas}/features.ts +1 -1
- package/src/{share/schemas → schemas}/index.ts +3 -3
- package/src/{share/schemas → schemas}/navigation.ts +6 -1
- package/src/{share/schemas → schemas}/page-base.ts +6 -2
- package/src/{share/schemas → schemas}/person.ts +1 -1
- package/src/{share/schemas → schemas}/standard-list-item.ts +1 -1
- package/src/schemas/tests/claim-records.test.ts +52 -0
- package/src/schemas/tests/claims.test.ts +23 -0
- package/src/schemas/tests/effects.test.ts +116 -0
- package/src/schemas/tests/features.test.ts +61 -0
- package/src/schemas/tests/horizontal-align.test.ts +16 -0
- package/src/schemas/tests/icon-color.test.ts +16 -0
- package/src/schemas/tests/live-photo.test.ts +40 -0
- package/src/schemas/tests/material-credit.test.ts +51 -0
- package/src/schemas/tests/media.test.ts +26 -0
- package/src/schemas/tests/navigation.test.ts +58 -0
- package/src/schemas/tests/page-base.test.ts +19 -0
- package/src/schemas/tests/participant.test.ts +26 -0
- package/src/schemas/tests/person.test.ts +12 -0
- package/src/schemas/tests/print.test.ts +30 -0
- package/src/schemas/tests/section-background.test.ts +31 -0
- package/src/schemas/tests/section-body.test.ts +16 -0
- package/src/schemas/tests/section-cards.test.ts +17 -0
- package/src/schemas/tests/section-cta.test.ts +41 -0
- package/src/schemas/tests/section-header.test.ts +31 -0
- package/src/schemas/tests/section-image.test.ts +24 -0
- package/src/schemas/tests/section-motion.test.ts +29 -0
- package/src/schemas/tests/section-shell.test.ts +42 -0
- package/src/schemas/tests/section-stats.test.ts +17 -0
- package/src/schemas/tests/site-background.test.ts +33 -0
- package/src/schemas/tests/standard-list-item.test.ts +16 -0
- package/src/{share → sections}/rfc0042-utils.ts +1 -1
- package/src/{share/semantic → semantic}/block-extractors/index.ts +20 -1
- package/src/{share/semantic → semantic}/business-projection.ts +95 -1
- package/src/semantic/canonical-uri.ts +54 -0
- package/src/{share/semantic → semantic}/extract.ts +100 -1
- package/src/semantic/fact-extraction.test.ts +47 -0
- package/src/semantic/fact-extraction.ts +46 -0
- package/src/semantic/freshness.ts +67 -0
- package/src/{share/semantic → semantic}/ids.ts +6 -1
- package/src/{share/semantic → semantic}/index.ts +9 -2
- package/src/{share/semantic → semantic}/jsonld/breadcrumb.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/context.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/faq.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/initiative.ts +6 -1
- package/src/{share/semantic → semantic}/jsonld/organization.ts +6 -1
- package/src/{share/semantic → semantic}/jsonld/person.ts +6 -2
- package/src/{share/semantic → semantic}/jsonld/service.ts +6 -2
- package/src/semantic/jsonld/shared.ts +36 -0
- package/src/semantic/jsonld/tests/article.test.ts +89 -0
- package/src/semantic/jsonld/tests/breadcrumb.test.ts +62 -0
- package/src/semantic/jsonld/tests/collection-list.test.ts +39 -0
- package/src/semantic/jsonld/tests/faq.test.ts +60 -0
- package/src/semantic/jsonld/tests/initiative.test.ts +56 -0
- package/src/semantic/jsonld/tests/organization.test.ts +123 -0
- package/src/semantic/jsonld/tests/person.test.ts +89 -0
- package/src/semantic/jsonld/tests/service.test.ts +65 -0
- package/src/semantic/jsonld/tests/shared.test.ts +41 -0
- package/src/semantic/jsonld/tests/video.test.ts +84 -0
- package/src/semantic/jsonld/tests/webpage.test.ts +93 -0
- package/src/semantic/jsonld/tests/website.test.ts +27 -0
- package/src/semantic/jsonld/types.ts +24 -0
- package/src/{share/semantic → semantic}/jsonld/webpage.ts +6 -1
- package/src/semantic/jsonld/website.ts +32 -0
- package/src/{share/semantic → semantic}/jsonld.ts +6 -1
- package/src/{share/semantic → semantic}/llms.ts +41 -3
- package/src/{share/semantic → semantic}/models.ts +27 -38
- package/src/{share/semantic → semantic}/organization-profile.ts +3 -0
- package/src/{share/semantic → semantic}/page-builders/markdown-page.ts +6 -3
- package/src/{share/semantic → semantic}/page-utils.ts +6 -3
- package/src/semantic/tests/ai.test.ts +55 -0
- package/src/semantic/tests/block-extraction.test.ts +34 -0
- package/src/{share/tests/breadcrumbs.test.ts → semantic/tests/breadcrumbs-0229.test.ts} +1 -1
- package/src/semantic/tests/breadcrumbs.test.ts +127 -0
- package/src/{share → semantic}/tests/build-page-price-markers.test.ts +2 -2
- package/src/semantic/tests/build-page.test.ts +122 -0
- package/src/semantic/tests/business-projection.test.ts +367 -0
- package/src/semantic/tests/canonical-uri.test.ts +56 -0
- package/src/semantic/tests/extract.test.ts +209 -0
- package/src/semantic/tests/feed.test.ts +113 -0
- package/src/semantic/tests/freshness.pbt.test.ts +108 -0
- package/src/semantic/tests/helpers.ts +32 -0
- package/src/semantic/tests/ids.test.ts +118 -0
- package/src/{share/tests/image-sitemap.test.ts → semantic/tests/image-sitemap-0150.test.ts} +1 -1
- package/src/semantic/tests/image-sitemap.test.ts +109 -0
- package/src/{share → semantic}/tests/jsonld-service.test.ts +2 -2
- package/src/{share → semantic}/tests/jsonld-video.test.ts +3 -3
- package/src/{share → semantic}/tests/jsonld-webpage.test.ts +3 -3
- package/src/semantic/tests/jsonld.test.ts +61 -0
- package/src/{share → semantic}/tests/llms-0789.test.ts +2 -2
- package/src/semantic/tests/llms-policy.test.ts +60 -0
- package/src/semantic/tests/llms.test.ts +164 -0
- package/src/semantic/tests/markdown-hygiene.test.ts +72 -0
- package/src/semantic/tests/markdown-page.test.ts +97 -0
- package/src/{share → semantic}/tests/organization-jsonld.test.ts +3 -3
- package/src/semantic/tests/organization-profile.test.ts +107 -0
- package/src/{share/tests/output-projection.test.ts → semantic/tests/output-projection-0143.test.ts} +1 -1
- package/src/semantic/tests/output-projection.test.ts +77 -0
- package/src/semantic/tests/page-markdown.test.ts +98 -0
- package/src/semantic/tests/page-utils.test.ts +88 -0
- package/src/{share/tests/price-marker-resolver.test.ts → semantic/tests/price-marker-resolver-0767.test.ts} +2 -2
- package/src/semantic/tests/price-marker-resolver.test.ts +90 -0
- package/src/semantic/tests/project-claims.pbt.test.ts +60 -0
- package/src/semantic/tests/robots.test.ts +70 -0
- package/src/{share/semantic → semantic}/tests/split-sentences.test.ts +1 -1
- package/src/{share → semantic}/tests/twin-path.test.ts +1 -1
- package/src/semantic/tests/update-stamp.test.ts +102 -0
- package/src/signing/index.ts +13 -0
- package/src/signing/key.ts +139 -0
- package/src/signing/sign.ts +66 -0
- package/src/signing/types.ts +29 -0
- package/src/{share/slug → slug}/slug-url.ts +4 -1
- package/src/stack/run-tool.ts +108 -0
- package/src/stack/stack-checks.ts +124 -0
- package/src/stack/tests/run-tool.test.ts +138 -0
- package/src/stack/tests/stack-checks.test.ts +141 -0
- package/src/stack/tests/walk-files.test.ts +131 -0
- package/src/stack/walk-files.ts +114 -0
- package/src/surface/blueprint.ts +342 -66
- package/src/surface/decision-composer.ts +6 -1
- package/src/surface/geo.ts +6 -1
- package/src/surface/index.ts +11 -7
- package/src/surface/types.ts +2 -2
- package/src/surface/vitest.config.ts +1 -0
- package/src/tests/signing-core.test.ts +129 -0
- package/src/{share → text}/css-value-normalize.ts +6 -1
- package/src/text/string-utils.ts +24 -0
- package/src/{share → text}/tests/text-normalize.test.ts +5 -0
- package/src/{share → text}/text-normalize.ts +4 -3
- package/src/{share → text}/wrap-inline-numbers.ts +1 -1
- package/src/types/axiom-study.d.ts +36 -0
- package/src/types/hls-js-light.d.ts +21 -0
- package/src/types/qrcode-browser.d.ts +23 -0
- package/src/typography/fix.pbt.test.ts +208 -0
- package/src/typography/fix.ts +148 -0
- package/src/typography/index.ts +54 -0
- package/src/typography/locale-defaults.ts +97 -0
- package/src/typography/rules-tier1.test.ts +459 -0
- package/src/typography/rules-tier1.ts +925 -0
- package/src/typography/rules-tier2-locale.pbt.test.ts +190 -0
- package/src/typography/rules-tier2-locale.test.ts +286 -0
- package/src/typography/rules-tier2-locale.ts +399 -0
- package/src/typography/rules-tier2-structure.test.ts +330 -0
- package/src/typography/rules-tier2-structure.ts +378 -0
- package/src/typography/rules-tier3-advisory.test.ts +290 -0
- package/src/typography/rules-tier3-advisory.ts +239 -0
- package/src/typography/text-surface.test.ts +244 -0
- package/src/typography/text-surface.ts +448 -0
- package/src/index.ts +0 -14
- package/src/integration/port-barrel.ts +0 -105
- package/src/share/content/index.ts +0 -16
- package/src/share/content-discipline/index.ts +0 -14
- package/src/share/index.ts +0 -86
- package/src/share/legal/index.ts +0 -1
- package/src/share/redirects.ts +0 -54
- package/src/share/scripts/index.ts +0 -26
- package/src/share/semantic/jsonld/shared.ts +0 -31
- package/src/share/semantic/jsonld/types.ts +0 -18
- package/src/share/semantic/jsonld/website.ts +0 -27
- package/src/share/string-utils.ts +0 -19
- package/src/share/types/axiom-study.d.ts +0 -22
- package/src/share/types/hls-js-light.d.ts +0 -16
- package/src/share/types/qrcode-browser.d.ts +0 -18
- package/src/share/utility-registry.yaml +0 -36
- package/src/share/vitest.config.ts +0 -8
- package/src/surface/blueprint-schema.ts +0 -314
- package/src/surface/blueprint-types.ts +0 -256
- /package/src/{share/agent → agent}/api-catalog.ts +0 -0
- /package/src/{share/agent → agent}/capability.ts +0 -0
- /package/src/{share/agent → agent}/dns-aid.ts +0 -0
- /package/src/{share/agent → agent}/fleet-catalog.ts +0 -0
- /package/src/{share/agent → agent}/index.ts +0 -0
- /package/src/{share/agent → agent}/knowledge.ts +0 -0
- /package/src/{share/agent → agent}/mcp-card.ts +0 -0
- /package/src/{share/agent → agent}/omit-empty.ts +0 -0
- /package/src/{share/agent → agent}/openapi.ts +0 -0
- /package/src/{share/agent → agent}/proof.ts +0 -0
- /package/src/{share/agent → agent}/search.test.ts +0 -0
- /package/src/{share → attribution}/attribution-display.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-mountain-journey.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-parallax.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-reveal.ts +0 -0
- /package/src/{share/scripts → client-scripts}/gsap-stagger.ts +0 -0
- /package/src/{share/scripts → client-scripts}/live-photos.ts +0 -0
- /package/src/{share/scripts → client-scripts}/video-player.ts +0 -0
- /package/src/{share/content → content}/resolve-field-path.ts +0 -0
- /package/src/{share/content → content}/substitute-deep.ts +0 -0
- /package/src/{share → content}/tests/content-ref-index.test.ts +0 -0
- /package/src/{share/content-discipline → content-discipline}/types.ts +0 -0
- /package/src/{share → image-provider}/image-provider.ts +0 -0
- /package/src/{share/knowledge → knowledge}/claim.ts +0 -0
- /package/src/{share/knowledge → knowledge}/derivation.ts +0 -0
- /package/src/{share/knowledge → knowledge}/freshness.ts +0 -0
- /package/src/{share/knowledge → knowledge}/index.ts +0 -0
- /package/src/{share/knowledge → knowledge}/ledger.ts +0 -0
- /package/src/{share/knowledge → knowledge}/plan.ts +0 -0
- /package/src/{share/knowledge → knowledge}/source.ts +0 -0
- /package/src/{share/legal → legal}/translation-policy.ts +0 -0
- /package/src/{share/middleware → middleware}/language-redirect.ts +0 -0
- /package/src/{share/middleware → middleware}/tests/access-protection.test.ts +0 -0
- /package/src/{share → node}/fs/index.ts +0 -0
- /package/src/{share/tests/markdown-twin-provenance.test.ts → node/tests/markdown-twin-provenance-0613.test.ts} +0 -0
- /package/src/{share → offers}/offer-capacity.ts +0 -0
- /package/src/{share/onboarding-yaml/index.ts → onboarding/yaml.ts} +0 -0
- /package/src/{share → ontology}/tests/ontology-role-by-cosmic-name.test.ts +0 -0
- /package/src/{share/routes → routes}/tests/template-filter.test.ts +0 -0
- /package/src/{share/schemas → schemas}/claim-records.ts +0 -0
- /package/src/{share/schemas → schemas}/horizontal-align.ts +0 -0
- /package/src/{share/schemas → schemas}/icon-color.ts +0 -0
- /package/src/{share/schemas → schemas}/live-photo.ts +0 -0
- /package/src/{share/schemas → schemas}/material-credit.ts +0 -0
- /package/src/{share/schemas → schemas}/media.ts +0 -0
- /package/src/{share/schemas → schemas}/participant.ts +0 -0
- /package/src/{share/schemas → schemas}/print.ts +0 -0
- /package/src/{share/schemas → schemas}/section-background.ts +0 -0
- /package/src/{share/schemas → schemas}/section-body.ts +0 -0
- /package/src/{share/schemas → schemas}/section-cards.ts +0 -0
- /package/src/{share/schemas → schemas}/section-cta.ts +0 -0
- /package/src/{share/schemas → schemas}/section-header.ts +0 -0
- /package/src/{share/schemas → schemas}/section-image.ts +0 -0
- /package/src/{share/schemas → schemas}/section-motion.ts +0 -0
- /package/src/{share/schemas → schemas}/section-shell.ts +0 -0
- /package/src/{share/schemas → schemas}/section-stats.ts +0 -0
- /package/src/{share/schemas → schemas}/site-background.ts +0 -0
- /package/src/{share/semantic → semantic}/ai.ts +0 -0
- /package/src/{share/semantic → semantic}/block-extraction.ts +0 -0
- /package/src/{share/semantic → semantic}/breadcrumbs.ts +0 -0
- /package/src/{share/semantic → semantic}/build-page.ts +0 -0
- /package/src/{share/semantic → semantic}/feed.ts +0 -0
- /package/src/{share/semantic → semantic}/image-sitemap.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/article.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/collection-list.ts +0 -0
- /package/src/{share/semantic → semantic}/jsonld/video.ts +0 -0
- /package/src/{share/semantic → semantic}/llms-policy.ts +0 -0
- /package/src/{share/semantic → semantic}/markdown-hygiene.ts +0 -0
- /package/src/{share/semantic → semantic}/output-projection.ts +0 -0
- /package/src/{share/semantic → semantic}/page-markdown.ts +0 -0
- /package/src/{share/semantic → semantic}/price-marker-resolver.ts +0 -0
- /package/src/{share/semantic → semantic}/robots.ts +0 -0
- /package/src/{share/semantic → semantic}/update-stamp.ts +0 -0
- /package/src/{share/slug → slug}/heading-slugger.ts +0 -0
- /package/src/{share/slug → slug}/index.ts +0 -0
- /package/src/{share/slug → slug}/slug-id.ts +0 -0
- /package/src/{share/slug → slug}/strategies.ts +0 -0
- /package/src/{share/slug → slug}/tests/slug.test.ts +0 -0
- /package/src/{share → text}/tests/text-normalize.pbt.test.ts +0 -0
- /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>
|
|
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>
|
|
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>
|
|
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>
|
|
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,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 { 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 { 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,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
|
+
});
|