From e114fdf52f1448332e9d6c2ccc87e40c406eb892 Mon Sep 17 00:00:00 2001 From: ameerabuf Date: Wed, 13 May 2026 20:25:48 +0300 Subject: [PATCH 1/2] rewritten simpler more structured plan --- .../context_ssot_restructure_c6889ec0.plan.md | 658 ++++++++++++++++++ 1 file changed, 658 insertions(+) create mode 100644 .cursor/plans/context_ssot_restructure_c6889ec0.plan.md diff --git a/.cursor/plans/context_ssot_restructure_c6889ec0.plan.md b/.cursor/plans/context_ssot_restructure_c6889ec0.plan.md new file mode 100644 index 00000000..730d7e81 --- /dev/null +++ b/.cursor/plans/context_ssot_restructure_c6889ec0.plan.md @@ -0,0 +1,658 @@ +--- +name: Context SSOT Restructure +overview: "Restructure the rules/ and docs/ across all three packages (@wix/interact, @wix/motion, @wix/motion-presets) into a single-source-of-truth system where structured data (params, defaults, types, term definitions) lives in YAML glossary files, and final markdown outputs are assembled by a lightweight build script. Work proceeds one package at a time: Interact, then Motion, then Motion-Presets." +todos: + - id: phase-0-schema + content: Design YAML glossary schema and marker syntax for templates + status: pending + - id: phase-0-build + content: Build scripts/build-context.js (YAML + templates -> rules/ and docs/ markdown) + status: pending + - id: phase-0-validate + content: Build scripts/validate-context.js (check glossary data against TypeScript source) + status: pending + - id: phase-1-audit + content: "Interact: Audit and verify all ground truth claims via ad-hoc Vitest tests" + status: pending + - id: phase-1-glossary + content: "Interact: Create context/glossary.yaml with all verified terms, params, defaults" + status: pending + - id: phase-1-rules-templates + content: "Interact: Create rules template files (overview, config, triggers, effects, pitfalls)" + status: pending + - id: phase-1-docs-templates + content: "Interact: Create docs template files (guides, api, integration, examples)" + status: pending + - id: phase-1-build-validate + content: "Interact: Run build + validate, iterate until output is correct and readable" + status: pending + - id: phase-1-replace + content: "Interact: Replace old rules/ and docs/ with generated output, verify all builds pass" + status: pending + - id: phase-2-audit + content: "Motion: Audit and verify ground truth (API signatures, return types, scroll/pointer)" + status: pending + - id: phase-2-migrate + content: "Motion: Create glossary, templates, build, and replace (add new rules/ dir)" + status: pending + - id: phase-3-audit + content: "Motion-Presets: Audit all 74 presets params/defaults against source" + status: pending + - id: phase-3-migrate + content: "Motion-Presets: Create glossary, templates, build, and replace" + status: pending + - id: phase-3-cross-validate + content: "Cross-package validation: verify shared concepts are consistent across all three packages" + status: pending +isProject: false +--- + +# Context SSOT Restructure + +## Motivation and Idea + +### The problem + +This monorepo publishes three packages (`@wix/interact`, `@wix/motion`, `@wix/motion-presets`) alongside context files designed for two audiences: **LLM-facing rules** (so AI agents can correctly integrate the packages) and **human-facing docs** (for developer onboarding and reference). Today these context files suffer from five interconnected problems: + +1. **Multiple contradicting sources of truth.** The same concept is described in different files with different phrasing, different defaults, and sometimes outright conflicting claims. For example, `allowA11yTriggers` defaults to `false` in one file and `true` in another; `ParallaxScroll` accepts a `speed` param in docs but the code uses `parallaxFactor`; trigger counts vary between 7, 8, and 9 depending on which file you read. A deep audit found **8 critical discrepancies** where docs would cause broken integrations, and **10 more significant ones**. +2. **No common structure.** Each package organizes its context differently. Interact has flat trigger-specific rule files plus two overlapping hub files; Motion has no rules at all; Motion-Presets has YAML-frontmatter rule files split by category. The docs folders vary in depth, naming, and section layout. There is no template or convention that applies across packages. +3. **Stale or incorrect information.** Defaults, param names, return types, and API signatures in the context files do not match the current implementation. There is no mechanism to detect this drift. +4. **Heavy repetition.** FOUC prevention is explained in 6 different files; element resolution order appears in 4; entry-point setup is repeated across every package's getting-started material. Each copy drifts independently. +5. **Broken links and scaffolding.** Over 36 internal links point to files that do not exist. Multiple sections are marked "TBD". README index pages link to planned-but-never-written guides. This erodes trust in the documentation for both humans and LLMs. + +Together, these create a **continuous development problem**: making any change to the context requires touching many files across multiple directories, producing large PRs that are hard to review and prone to introducing new inconsistencies. The cost of keeping context accurate compounds over time. + +### The idea + +Replace the current ad-hoc markdown files with a **structured, build-based system** where: + +- **Each piece of information is defined once** in a YAML glossary file (one per package). The glossary holds the data that is most prone to going stale: parameter names and types, default values, API signatures, term definitions (with separate LLM and human phrasings), and known caveats. +- **Markdown template files** provide the document structure and prose. They contain markers (e.g., `{{term:trigger-viewEnter.params-table}}`) where glossary data should be injected. Templates are authored separately for rules (compact, LLM-optimized) and docs (narrative, human-friendly). +- **A lightweight build script** reads the glossary and templates, performs marker replacement, and writes the final `rules/` and `docs/` output files. +- **A validation script** checks glossary entries against TypeScript source code, catching drift before it reaches the published context. + +This means: + +- Changing a default value or param name is a **one-line YAML edit** that propagates everywhere. +- Rules and docs always agree because they draw from the same data. +- The validation script catches code-vs-context drift in CI. +- Each package follows the same structure, making the system predictable and reviewable. + +### Why YAML for the glossary + +The glossary contains prose-heavy entries (descriptions, caveats) that humans will frequently hand-edit. YAML supports multi-line strings and inline comments natively, which makes authoring and PR review substantially easier than JSON. The motion-presets rules already use YAML frontmatter, so the pattern is familiar in this repo. Since the build script parses YAML into a plain JS object, switching to JSON later would be a trivial change. + +### Sequencing + +The migration is designed to proceed **one package at a time** (Interact, then Motion, then Motion-Presets), with each package's old context files replaced only after the new mechanism is fully built, validated, and reviewed. This keeps PRs scoped and reviewable, and ensures no package is left in a half-migrated state. + +--- + +## Audit Findings (Baseline) + +This section captures the findings from the deep analysis of all 72 context files across the three packages and their comparison against source code. These findings serve as the ground truth for the migration. + +### Current State Inventory + +**File counts:** + + +| Package | `rules/` files | `docs/` files | Total | +| -------------------------------------- | -------------- | ------------- | ------ | +| Interact (`@wix/interact`) | 7 | 26 | 33 | +| Motion (`@wix/motion`) | 0 | 20 | 20 | +| Motion-Presets (`@wix/motion-presets`) | 5 | 14 | 19 | +| **Total** | **12** | **60** | **72** | + + +**Structural asymmetry:** + +- **Interact** has both `rules/` (flat trigger-focused files: `click.md`, `hover.md`, `viewenter.md`, `viewprogress.md`, `pointermove.md`, plus two hub files `full-lean.md` at 692 lines and `integration.md` at 329 lines) and `docs/` (nested into `guides/`, `api/`, `examples/`, `integration/`, `advanced/`). +- **Motion** has only `docs/` (nested into `api/`, `categories/`, `guides/`, `examples/`) plus a stale internal `PLAN_DOCS.md`. No `rules/` directory exists at all. +- **Motion-Presets** has both `rules/` (5 files under `presets/` with YAML frontmatter, split by category) and `docs/` (14 files nested per category with individual preset pages). + +**Existing patterns for selective LLM reading:** + +- Interact rules: `## Table of Contents` with `#anchor` links; `---` thematic breaks between sections; no YAML frontmatter. +- Motion-Presets rules: YAML frontmatter with `name`/`description` (agent-loading hints); per-file TOCs down to per-preset anchors. +- Typical file sizes: 190-280 lines for single-trigger interact docs, 330 lines for integration hub, 690 lines for `full-lean`, 210-398 lines for presets rule files. + +### Discrepancies: Docs/Rules vs Code + +#### Critical (would cause broken integrations if an LLM follows the docs) + + +| # | Topic | What docs/rules say | What the code does | Source files | +| --- | ----------------------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | +| 1 | `allowA11yTriggers` default | `rules/integration.md`: **false**; `docs/api/types.md`: **true** | Code: **true** -- `click` auto-maps to `activate`, `hover` to `interest` | `src/handlers/index.ts` | +| 2 | `ParallaxScroll` param name | All docs consistently use `**speed`** | Code: `**parallaxFactor`** (default `0.5`) | `motion-presets/src/library/scroll/ParallaxScroll.ts`, `types.ts` | +| 3 | `Pulse` intensity default | `docs/ongoing/pulse.md`: **1.0** | Code: **0** | `motion-presets/src/library/ongoing/Pulse.ts` | +| 4 | `ArcIn` default direction | `docs/entrance/arc-in.md`: `**'bottom'`** | Code: `**'right'`** | `motion-presets/src/library/entrance/ArcIn.ts` | +| 5 | `namedEffect` shape | Many docs use bare string: `namedEffect: 'FadeIn'` | Code requires object: `namedEffect: { type: 'FadeIn' }` | `motion/src/api/common.ts` `getNamedEffect` | +| 6 | `getCSSAnimation` return type | `api/core-functions.md`, `performance.md`: **string** | Code: **array of objects** `({ target, animation, keyframes, ... })` | `motion/src/api/cssAnimations.ts` | +| 7 | `AnimationEndParams.effectId` | Typed and documented as wiring mechanism | Handler **ignores it** (`__` param) | `interact/src/handlers/animationEnd.ts` | +| 8 | `viewProgress` params | `api/types.md` maps `viewProgress: ViewEnterParams` | Handler ignores those params; scroll options come from `Interact.setup({ scrollOptionsGetter })` | `interact/src/handlers/viewProgress.ts` | + + +#### Significant (causes confusion, may lead to subtle bugs) + + +| # | Topic | Discrepancy | +| --- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| 9 | Sticky/tall-wrapper ViewTimeline source | `viewprogress.md` Rule 3: `key` on tall wrapper = source. `full-lean.md`: sticky child = source. Contradicts itself. | +| 10 | Trigger count | `guides/README.md` says 7; `understanding-triggers.md` says 9; actual `TriggerType` union: **9** members | +| 11 | `pageVisible` trigger | Omitted from most trigger tables; only in `api/types.md`. Actually exists in code, uses viewEnter's IntersectionObserver handler | +| 12 | Mouse preset count | Rules: 9; `mouse/README.md`: 12; barrel export: **11** (plus CustomMouse = 12 total) | +| 13 | Ongoing preset count | `presets-main.md`: 14; `ongoing/README.md`: 16; barrel exports: **13**; DVD is not exported | +| 14 | Total preset count | Docs: "82+"; rules enumerate 61; barrel exports: **19 + 19 + 13 + 12 + 12 = 75** | +| 15 | Angle convention | `presets-main.md`: 0 = right; `_template.md`: 0 = up. Code: **0 = right** in most presets | +| 16 | `customEffect` signature | Varies: 2-arg in rules, 3-arg in some docs. Actual depends on context (time: `progress` number; pointer: `Progress { x, y, v, active }`) | +| 17 | `TurnScroll.rotation` param | Typed in `types.ts` but **ignored** in implementation (fixed +/-45deg) | +| 18 | `ParallaxScroll.range` param | Typed but **unused** in `ParallaxScroll.ts` implementation | + + +#### Minor (quality / completeness) + + +| # | Topic | +| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 19 | **36+ broken internal links** across all packages: references to nonexistent files like `testing.md`, `performance.md`, `playground/`, `scroll-animations.md`, etc. | +| 20 | **5+ TBD placeholder sections** in interact docs (`configuration-structure`, `effects-and-animations`, `state-management`, `lists`, `custom-elements`) | +| 21 | **Code typos in docs**: `sytle`, `hitAea`, `docuement`, `getScrgetWebAnimationubScene`, truncated/invalid snippets | +| 22 | `**unit` vs `type` for length objects**: rules use both `{ value, type: 'px' }` and `{ value, unit: 'px' }` interchangeably | +| 23 | `**Interact.getElement`** referenced in docs but does not exist as a public API | + + +### Repetition Analysis + +The same information is repeated across multiple files with inconsistent phrasing: + + +| Concept | Files that describe it | Copies | +| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | +| FOUC prevention (`generate` + `initial`) | `rules/integration.md`, `rules/viewenter.md`, `rules/full-lean.md`, `docs/api/functions.md`, `docs/examples/entrance-animations.md`, `docs/guides/getting-started.md` | 6 | +| Element resolution order | `rules/integration.md`, `rules/full-lean.md`, `docs/api/element-selection.md`, `docs/guides/configuration-structure.md` | 4 | +| Entry-point setup | `rules/integration.md`, `docs/README.md`, `docs/guides/getting-started.md`, `docs/integration/react.md` | 4 | +| Trigger inventory table | `rules/full-lean.md`, `rules/integration.md`, `docs/guides/understanding-triggers.md`, `docs/api/types.md` | 4 (with different counts) | +| `registerEffects` usage | motion `docs/getting-started.md`, interact `rules/integration.md`, presets `docs/presets/README.md`, presets `rules/presets-main.md` | 4 | +| Scroll range semantics | interact `rules/viewprogress.md`, presets `rules/scroll-presets.md`, motion `docs/core-concepts.md`, presets `docs/scroll/README.md` | 4 | +| Stagger/sequence formula | motion `docs/core-concepts.md`, `docs/api/sequence.md`, `docs/api/get-sequence.md`, interact `docs/guides/sequences.md` | 4 | +| Reduced motion / a11y | Described independently across ~8 files in all three packages | ~8 | + + +### Verified Ground Truth: What Each Package Actually Contains + +#### `@wix/interact` -- Declarative Interaction Layer + +**Entry points:** `@wix/interact` (vanilla), `@wix/interact/react`, `@wix/interact/web` + +**Exports:** + +- `Interact` class (static + instance API) +- Functions: `add`, `remove`, `generate` +- React: `Interaction` component, `createInteractRef` +- Web: `InteractElement` custom element (registered via `Interact.defineInteractElement`) + +**Config schema (`InteractConfig`):** `{ effects: Record, interactions: Interaction[], sequences?: Record, conditions?: Record }` + +**9 trigger types (from `TriggerType` union):** `hover`, `click`, `viewEnter`, `viewProgress`, `pointerMove`, `activate`, `interest`, `animationEnd`, `pageVisible` + +**Handler mappings (from `handlers/index.ts`):** + +- `viewEnter`, `pageVisible` -> IntersectionObserver handler +- `hover` -> `mouseenter`/`mouseleave` (or `interest` preset if `allowA11yTriggers`) +- `click` -> `['click']` (or `activate` preset if `allowA11yTriggers`) +- `activate` -> `['click', 'keydown']` +- `interest` -> enter: `mouseenter`+`focusin`, leave: `mouseleave`+`focusout` +- `animationEnd` -> listens on source `animationend`, plays on target +- `viewProgress` -> ViewTimeline scrub or `getScrubScene` fallback +- `pointerMove` -> `getScrubScene` + pointer library + +**3 effect types:** `TimeEffect` (has `duration`), `ScrubEffect` (has `rangeStart`/`rangeEnd`), `StateEffect` (has `transition`/`transitionProperties`) + +`**triggerType` values:** `'once' | 'repeat' | 'alternate' | 'state'`; defaults: `'once'` for viewEnter/pageVisible/animationEnd, `'alternate'` for hover/click/activate/interest + +`**stateAction` values:** `'add' | 'remove' | 'toggle' | 'clear'`; default: `'toggle'` + +**Condition types:** `'media' | 'container' | 'selector'` only (no `'custom'`) + +**Key param defaults:** + +- `ViewEnterParams.threshold`: `0.2` +- `PointerMoveParams.axis`: `'y'` +- `PointerMoveParams.hitArea`: undefined (covers document body) + +#### `@wix/motion` -- Core Animation Engine + +**Exported functions:** `getWebAnimation`, `getScrubScene`, `getCSSAnimation`, `prepareAnimation`, `getElementCSSAnimation`, `getElementAnimation`, `getSequence`, `createAnimationGroups`, `registerEffects` + +**Exported utilities:** `getCssUnits`, `getEasing`, `getJsEasing`, all Penner-style easings + `jsEasings`/`cssEasings` maps + +**Type-only exports (not constructable):** `AnimationGroup`, `Sequence` + +**Key behaviors:** + +- `getAnimation` (internal, used by interact): chooses CSS path (if preset has `style`) or WAAPI path (`getWebAnimation`) +- `getCSSAnimation` returns array of CSS rule descriptor objects, not a string +- ViewTimeline: native (`window.ViewTimeline`) with `duration: 'auto'`; fallback: `duration: 99.99` with manual scrub via `getScrubScene` +- Pointer: without keyframes uses factory `MouseAnimationInstance`; with keyframes uses `AnimationGroup.progress()` +- `registerEffects(effects)` merges into internal registry; presets resolve by `namedEffect.type` string +- `fastdom` used for DOM batching (measure/mutate), not re-exported + +`**AnimationGroup` API:** `play`, `pause`, `reverse`, `cancel`, `progress(p)`, `setPlaybackRate`, `getProgress`, `onFinish`, `ready`, `finished`, `playState` + +`**Sequence`:** extends `AnimationGroup`; stagger formula: `offset[i] = easing(i / last) * last * offsetMs` (integer-truncated); supports `addGroups`/`removeGroups` + +`**RangeOffset` names:** `'entry' | 'exit' | 'contain' | 'cover' | 'entry-crossing' | 'exit-crossing'` + +#### `@wix/motion-presets` -- Ready-Made Effects + +**5 categories, 75 exported presets (from barrel):** + +- **Entrance (19):** ArcIn, BlurIn, BounceIn, CurveIn, DropIn, ExpandIn, FadeIn, FlipIn, FloatIn, FoldIn, GlideIn, RevealIn, ShapeIn, ShuttersIn, SlideIn, SpinIn, TiltIn, TurnIn, WinkIn +- **Scroll (19):** ArcScroll, BlurScroll, FadeScroll, FlipScroll, GrowScroll, MoveScroll, PanScroll, ParallaxScroll, RevealScroll, ShapeScroll, ShuttersScroll, ShrinkScroll, SkewPanScroll, SlideScroll, Spin3dScroll, SpinScroll, StretchScroll, TiltScroll, TurnScroll +- **Ongoing (13):** Bounce, Breathe, Cross, Flash, Flip, Fold, Jello, Poke, Pulse, Rubber, Spin, Swing, Wiggle (DVD exists but is NOT barrel-exported) +- **Mouse (12):** AiryMouse, BlobMouse, BlurMouse, BounceMouse, CustomMouse, ScaleMouse, SkewMouse, SpinMouse, SwivelMouse, Tilt3DMouse, Track3DMouse, TrackMouse +- **Background Scroll (12):** BgCloseUp, BgFade, BgFadeBack, BgFake3D, BgPan, BgParallax, BgPullBack, BgReveal, BgRotate, BgSkew, BgZoom, ImageParallax + +**Registration:** `registerEffects` is in `@wix/motion`, not in this package. Presets are plain modules keyed by `namedEffect.type`. Typical usage: `registerEffects({ FadeIn, ParallaxScroll, ... })`. + +**Preset module shapes:** namespace with `web`/`style`/`getNames` (most), mouse presets export `create` factories, some presets have `prepare` (background-scroll). + +**Shared params:** All mouse presets share `inverted?: boolean` (default `false`). Scroll presets support `range?: 'in' | 'out' | 'continuous'` (default varies per preset). Ongoing presets support `iterationDelay?: number` (default `0`). + +**Angle convention in code:** 0 = right, counterclockwise increases (90 = top). + +**Known type-vs-implementation mismatches in presets:** + +- `TurnScroll.rotation`: typed but ignored (fixed +/-45deg) +- `ParallaxScroll.range`: typed but unused +- `DVD`: typed and implemented but not barrel-exported + +### Cross-Package Shared Concepts (need SSOT) + + +| Concept | Owner (should be SSOT) | Referenced by | +| ------------------------------------------------------ | ---------------------- | ----------------- | +| `registerEffects` API | motion | interact, presets | +| `AnimationGroup` / `Sequence` types | motion | interact | +| `namedEffect` shape (`{ type: '...' }`) | motion | interact, presets | +| Scroll ranges (`RangeOffset`, range names) | motion | interact, presets | +| Pointer progress (`Progress { x, y, v, active }`) | motion | interact, presets | +| `EffectScrollRange` (`in`/`out`/`continuous`) | presets | presets only | +| Direction type families (`EffectFourDirections`, etc.) | presets | presets only | +| Easing values (CSS + JS) | motion | interact, presets | +| `prefers-reduced-motion` pattern | interact | motion, presets | +| Length/unit convention (`{ value, type }`) | motion | presets | + + +### Build and Test Infrastructure (Existing) + +- **Monorepo:** Yarn 4 workspaces, no Turbo/Nx +- **Build:** Vite for library bundles, `tsc` for types +- **Unit tests:** Vitest in all three packages (`jsdom` environment for interact) +- **E2E:** Playwright exists for `@wix/motion` only (`packages/motion/e2e/`). Interact has a CI workflow referencing Playwright but no actual Playwright config or tests. +- **Docs deployment:** `apps/docs` copies `packages/interact/docs` into Vite dist via `scripts/copy-docs.js`. Rules are served raw from the docs app under `/rules/`. +- **No existing codegen, templating, or doc validation tooling** in the repo. + +--- + +## Recommendation: Glossary Format + +Use **YAML data files for structured/verifiable data** (parameter tables, defaults, type signatures, term definitions) combined with **markdown template files for prose and document structure**. A lightweight Node.js build script assembles the final rules/ and docs/ output. + +Why this over pure-markdown-with-frontmatter: + +- YAML is machine-parseable, so defaults and params can be **validated against TypeScript source** automatically +- The current rules already contain substantial structured data (param tables, trigger maps, preset catalogs) that maps naturally to YAML +- Prose stays in markdown where it belongs -- the YAML only holds the data that is prone to going stale +- motion-presets rules already use YAML frontmatter, so the pattern is familiar + +Why not a heavier templating system: + +- No existing codegen tooling in the repo; adding Handlebars/Nunjucks/etc. is overhead +- A simple marker-replacement script (find `{{glossary:term-id.field}}` in markdown, inject from YAML) is sufficient and easy to maintain + +--- + +## Directory Structure (per package) + +``` +packages// + context/ # NEW - single source of truth + glossary.yaml # All terms, params, defaults, descriptions + templates/ + rules/ # Markdown templates for LLM-facing output + overview.md # Template with {{markers}} for glossary data + ...per-package files... + docs/ # Markdown templates for human-facing output + README.md + guides/ + api/ + ... + rules/ # OUTPUT (generated, gitignored during dev) + docs/ # OUTPUT (generated, gitignored during dev) +``` + +A shared build script lives at the monorepo root: + +``` +scripts/ + build-context.js # Reads glossary YAML + templates, writes rules/ + docs/ + validate-context.js # Checks glossary data against TS source code +``` + +> **Open question for implementation:** Whether `rules/` and `docs/` should be gitignored (generated on CI) or committed (generated locally and checked in). The current `package.json` `"files"` field includes `"rules"` and `"docs"`, meaning they are published to npm. The simplest path is to **commit the generated output** and have CI verify it matches the source, similar to how lockfiles work. This avoids needing a pre-publish build step. Final decision deferred to implementation. + +--- + +## Glossary YAML Schema + +Each `glossary.yaml` contains entries like: + +```yaml +terms: + - id: trigger-viewEnter + name: viewEnter + category: trigger # trigger | effect-type | config | api | concept | preset + llm: "Fires when element crosses viewport threshold via IntersectionObserver." + human: "Triggers an animation when an element scrolls into the visible area of the page." + params: + - name: threshold + type: number + default: 0.2 + description: "Fraction of element that must be visible" + - name: inset + type: string + default: null + description: "Mapped to IntersectionObserver rootMargin" + caveats: + - "Same source+target: only triggerType 'once' is reliable" + sourceFile: src/types/triggers.ts # for validation + related: [trigger-pageVisible, concept-fouc] +``` + +Presets get a `presets` section (motion-presets only) with the same structure but preset-specific fields (`category: entrance|scroll|ongoing|mouse|backgroundScroll`, `triggerBinding`, etc.). + +> **Implementation note:** The exact YAML schema should be finalized during the Interact package phase after the full audit confirms which fields are actually needed. The schema above is a starting point. + +--- + +## Build Script Behavior + +`scripts/build-context.js`: + +1. For a given package, reads `context/glossary.yaml` +2. Reads each template file in `context/templates/rules/` and `context/templates/docs/` +3. Replaces markers like `{{term:trigger-viewEnter.params-table}}` with formatted markdown (table for params, inline text for descriptions, etc.) +4. Also supports `{{include:path/to/fragment.md}}` for shared prose blocks within the same package +5. Writes output to `rules/` and `docs/` + +`scripts/validate-context.js`: + +1. Reads `context/glossary.yaml` for a package +2. For each entry with a `sourceFile`, parses the TypeScript source (using regex or ts-morph -- to be decided during implementation) to extract: + - Type definitions (verify param names and types match) + - Default values in destructuring patterns (verify defaults match) +3. Reports mismatches as errors + +> **Scope constraint:** The build script should be simple -- under 300 lines. If the templating needs grow beyond simple marker replacement, reconsider the approach before adding complexity. + +--- + +## Phase 0: Infrastructure Setup + +Before any package migration, set up the shared tooling. + +### 0.1 Design the YAML glossary schema + +- Draft the schema based on the Interact package audit (from the previous conversation's findings) +- Decide on the marker syntax for templates (e.g., `{{term:id.field}}`, `{{table:id.params}}`) +- Decide on the set of "field renderers" needed (param-table, description, caveats-list, code-example) + +### 0.2 Build the context build script + +- `scripts/build-context.js` -- reads YAML, processes templates, writes output +- Must support running per-package: `node scripts/build-context.js --package interact` +- Add a `build:context` script to root `package.json` + +### 0.3 Build the validation script + +- `scripts/validate-context.js` -- reads YAML, checks against source +- Strategy for extracting ground truth from TS: Start with regex-based extraction of type members and destructuring defaults. If that proves brittle, evaluate [ts-morph](https://ts-morph.com/) (adds a devDep but is reliable). Decision deferred to implementation. +- Add a `validate:context` script to root `package.json` + +### 0.4 Decide on gitignore strategy + +- Test both approaches (committed vs generated) during Interact migration +- If committed: add a CI check that runs `build-context` and `git diff --exit-code` on the output + +--- + +## Phase 1: Interact Package (`@wix/interact`) + +### 1.1 Audit and verify ground truth + +Before writing any glossary entries, verify every claim in the current rules against the actual code. This is critical because the previous analysis found **8 critical discrepancies** and **10 significant ones**. + +**Verification approach:** Write ad-hoc Vitest tests (in a temporary test file, e.g., `packages/interact/test/context-audit.spec.ts`) that import source modules and assert the documented behavior. These tests serve as one-time verification and can be kept as regression tests afterward. + +Items to verify (from discrepancy list): + + +| # | What to verify | How | +| --- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| 1 | `allowA11yTriggers` default is `true` | Check `Interact` class static field and handler mapping in [handlers/index.ts](packages/interact/src/handlers/index.ts) | +| 2 | `namedEffect` requires object `{ type: '...' }`, not bare string | Test that `getRegisteredEffect` resolves `{ type: 'FadeIn' }` but not `'FadeIn'` | +| 3 | All 9 trigger types exist in `TriggerType` union | Import and enumerate from [types/triggers.ts](packages/interact/src/types/triggers.ts) | +| 4 | `pageVisible` uses same handler as `viewEnter` | Check handler mapping in [handlers/index.ts](packages/interact/src/handlers/index.ts) | +| 5 | `AnimationEndParams.effectId` is unused at runtime | Read [handlers/animationEnd.ts](packages/interact/src/handlers/animationEnd.ts) and verify the param is ignored | +| 6 | `viewProgress` handler ignores `ViewEnterParams` | Read [handlers/viewProgress.ts](packages/interact/src/handlers/viewProgress.ts) | +| 7 | `triggerType` defaults: `'once'` for viewEnter, `'alternate'` for event triggers | Check [core/resolvers.ts](packages/interact/src/core/resolvers.ts) and handler code | +| 8 | `stateAction` default is `'toggle'` | Check [handlers/effectHandlers.ts](packages/interact/src/handlers/effectHandlers.ts) `createTransitionHandler` | +| 9 | `Condition.type` accepts only `'media' | 'container' | +| 10 | Sticky/tall-wrapper ViewTimeline: which element is the source | Read [handlers/viewProgress.ts](packages/interact/src/handlers/viewProgress.ts) to determine actual behavior | +| 11 | Element resolution order (key cascade) | Read [core/Interact.ts](packages/interact/src/core/Interact.ts) `parseConfig` and [core/add.ts](packages/interact/src/core/add.ts) | +| 12 | `generate()` signature and return type | Import from [core/css.ts](packages/interact/src/core/css.ts) | + + +### 1.2 Create the Interact glossary + +Based on verified ground truth, populate `packages/interact/context/glossary.yaml` with entries for: + +**Categories and approximate entry counts:** + +- **Triggers** (9 entries): hover, click, viewEnter, viewProgress, pointerMove, activate, interest, animationEnd, pageVisible -- each with params, defaults, caveats +- **Effect types** (3 entries): TimeEffect, ScrubEffect, StateEffect -- each with all typed fields and defaults +- **Config types** (5-6 entries): InteractConfig, Interaction, Effect/EffectRef, SequenceConfig, Condition -- schema shapes +- **API** (8-10 entries): Interact.create, Interact.destroy, Interact.setup, add, remove, generate, Interact.registerEffects, Interact.getSequence, etc. -- signatures and behavior +- **Concepts** (5-6 entries): FOUC prevention, element resolution, a11y trigger mapping, conditions cascading, custom elements lifecycle +- **Enums/unions** (4-5 entries): triggerType (once/repeat/alternate/state), stateAction, Fill, CompositeOperation + +### 1.3 Design the Interact rules templates + +The current rules have two overlapping "hub" files (`full-lean.md` at 692 lines, `integration.md` at 329 lines) plus 5 trigger-specific files. The new structure should eliminate the overlap. + +**Proposed rules/ file set for Interact:** + + +| File | Purpose | Approx. lines | +| ------------- | ----------------------------------------------------------------------------------------- | ------------- | +| `overview.md` | Package purpose, entry points, imports, quick-start snippet | 60-80 | +| `config.md` | InteractConfig schema, Interaction shape, Effect/EffectRef, sequences, conditions | 150-200 | +| `triggers.md` | All 9 triggers: params, defaults, behavior, per-trigger caveats | 200-250 | +| `effects.md` | TimeEffect, ScrubEffect, StateEffect: fields, defaults, triggerType/stateAction semantics | 150-200 | +| `pitfalls.md` | FOUC, overflow:clip, same-element source+target, hit-area jitter, a11y mapping | 80-100 | + + +Each file gets: + +- YAML frontmatter (`name`, `description`) for agent-loading hints (matches existing presets pattern) +- A `## Table of Contents` with `#anchor` links for selective section reading +- `{{term:...}}` markers where glossary data should be injected + +**Key structural rule for LLM readability:** + +- Each file must be self-contained for its topic (no "see other file for the rest of this table") +- Cross-references between files use relative links but only for "related reading", never for completing a thought +- Param tables are compact: `| name | type | default | notes |` -- one row per param, no verbose descriptions +- Code examples are minimal (3-8 lines) and correct + +### 1.4 Design the Interact docs templates + +The current docs have 26 files but many are scaffolding (broken links, TBD sections, placeholder READMEs linking to nonexistent files). The new structure should contain only files with actual content. + +**Proposed docs/ file set for Interact:** + + +| File | Purpose | +| --------------------------- | ------------------------------------------------------- | +| `README.md` | Getting started, install, entry points, navigation | +| `guides/configuration.md` | Config structure explained for humans | +| `guides/triggers.md` | Trigger concepts, choosing triggers, combining triggers | +| `guides/effects.md` | Effect types explained, when to use which | +| `guides/sequences.md` | Sequence math, staggering, list integration | +| `guides/conditions.md` | Media queries, container queries, selector conditions | +| `guides/fouc.md` | FOUC prevention guide (generate + initial) | +| `guides/custom-elements.md` | `` usage and lifecycle | +| `api/README.md` | API overview and imports | +| `api/interact-class.md` | Static + instance methods | +| `api/functions.md` | add, remove, generate | +| `api/types.md` | Type reference | +| `integration/react.md` | React-specific guide | +| `examples/entrance.md` | Entrance animation recipes | +| `examples/hover-click.md` | Hover and click interaction recipes | + + +All other current files (broken-link READMEs, TBD placeholders, the nonexistent targets) are **dropped**. Content from `full-lean.md` that overlaps with docs is reconciled -- the rules version becomes the SSOT; the docs version becomes a prose rewrite of the same data. + +### 1.5 Build and validate + +1. Run `node scripts/build-context.js --package interact` to generate `rules/` and `docs/` +2. Run `node scripts/validate-context.js --package interact` to verify glossary against source +3. Manually review generated rules for LLM readability: + - Are tables compact and scannable? + - Can an LLM read just `triggers.md` and get everything it needs about triggers? + - Is the TOC + anchor pattern working for selective section reading? +4. Run the existing `apps/docs` build to verify docs still copy correctly + +### 1.6 Replace and verify + +1. Back up current `rules/` and `docs/` (they are in git, so this is just a safety step) +2. Replace with generated output +3. Run existing Vitest tests (`yarn workspace @wix/interact test`) to ensure nothing depends on specific rules/docs file paths internally +4. Verify `apps/docs` build still works (it copies from `packages/interact/docs`) +5. Verify the docs app `copy-docs.js` script handles the new file structure +6. Keep the `context-audit.spec.ts` tests as ongoing regression + +--- + +## Phase 2: Motion Package (`@wix/motion`) + +### 2.1 Audit and verify ground truth + +Motion currently has **no rules/** directory. Its docs have significant issues: `getCSSAnimation` return type is wrong in multiple files, `TriggerVariant` shape differs between tutorial and type docs, preset counts don't match, and several linked files don't exist. + +**Key items to verify:** + + +| # | What to verify | +| --- | -------------------------------------------------------------------------- | +| 1 | `getCSSAnimation` return type (array of objects, not string) | +| 2 | `AnimationGroup` is type-only export (not constructable by consumers) | +| 3 | `getWebAnimation` signature and return type union | +| 4 | `getScrubScene` signature, scroll vs pointer branches | +| 5 | `TriggerVariant` actual shape (`id`, `trigger`, `componentId`, `element?`) | +| 6 | `Sequence` stagger formula | +| 7 | `RangeOffset` range name enum | +| 8 | `prepareAnimation` behavior and `DomApi` contract | +| 9 | `CustomAnimation` rAF loop behavior for `customEffect` | +| 10 | ViewTimeline detection and fallback path | + + +### 2.2 Create glossary, templates, build, replace + +Same pattern as Interact: + +- `packages/motion/context/glossary.yaml` -- API functions, animation types, scroll/pointer concepts, `AnimationGroup`/`Sequence` APIs +- **New `rules/` directory** (Motion currently lacks one): `overview.md`, `api.md`, `animation-types.md`, `pitfalls.md` +- Restructured `docs/` -- drop `PLAN_DOCS.md`, fix or remove broken links, remove nonexistent targets, consolidate category docs that overlap with motion-presets docs + +**Important:** Motion's `docs/categories/` files (entrance-animations.md, scroll-animations.md, etc.) overlap heavily with motion-presets docs. These should be **removed or reduced to pointers** -- the preset details belong in motion-presets. Motion docs should cover the **engine API**, not preset catalogs. + +--- + +## Phase 3: Motion-Presets Package (`@wix/motion-presets`) + +### 3.1 Audit and verify ground truth + +Motion-presets rules are the most structured of the three (YAML frontmatter, per-category files), but have known data errors. + +**Key items to verify:** + + +| # | What to verify | +| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | All preset names match barrel exports (currently 19+19+13+11+12 = 74) | +| 2 | Per-preset params and defaults match implementation (ParallaxScroll `parallaxFactor` not `speed`, ArcIn default `'right'` not `'bottom'`, Pulse intensity default `0` not `1.0`) | +| 3 | Mouse preset count (11 exported, not 9 or 12) | +| 4 | DVD is NOT exported from barrel | +| 5 | Angle convention is 0 = right (not up) | +| 6 | `range` param on ParallaxScroll is typed but unused | +| 7 | `TurnScroll.rotation` is typed but unused | +| 8 | Background-scroll `data-motion-part` values and targeting | + + +**Verification approach:** For presets, the most effective verification is a script that: + +- Imports all barrel exports from `@wix/motion-presets` +- For each, checks it matches the glossary entry (name, category) +- For param defaults, reads the source destructuring patterns + +This can be a Vitest test file that programmatically checks all presets. + +### 3.2 Create glossary, templates, build, replace + +- `packages/motion-presets/context/glossary.yaml` -- includes a `presets` section with every preset's params and defaults +- **Rules:** Keep the current split-by-category approach (it works well) but generate tables from glossary: `overview.md`, `entrance.md`, `scroll.md`, `ongoing.md`, `mouse.md`, `background-scroll.md` +- **Docs:** Per-preset pages only for presets that warrant detailed explanation (not all 74 need a dedicated page). Category READMEs with param tables generated from glossary. Drop broken-link placeholder pages. + +### 3.3 Cross-package validation + +After all three packages are migrated: + +- Verify cross-package references (interact rules referencing motion concepts, presets referencing motion API) +- Ensure `registerEffects` is described consistently: defined in motion glossary, referenced in interact and presets +- Ensure shared concepts (scroll ranges, pointer progress, namedEffect shape) use the same terminology everywhere + +--- + +## Sequencing and Safety + +```mermaid +flowchart TD + P0[Phase 0: Build tooling] --> P1A[1.1: Audit Interact ground truth] + P1A --> P1B[1.2: Create Interact glossary] + P1B --> P1C[1.3-1.4: Create templates] + P1C --> P1D[1.5: Build and validate] + P1D --> P1E{Output matches expectations?} + P1E -->|No| P1C + P1E -->|Yes| P1F[1.6: Replace Interact rules+docs] + P1F --> P2A[2.1: Audit Motion ground truth] + P2A --> P2B[2.2: Create Motion glossary + templates + replace] + P2B --> P3A[3.1: Audit Presets ground truth] + P3A --> P3B[3.2: Create Presets glossary + templates + replace] + P3B --> P3C[3.3: Cross-package validation] +``` + + + +**Safety rule:** The old `rules/` and `docs/` files for a package are only deleted/replaced after: + +1. The new glossary is validated against source code +2. The build script produces output that passes manual review +3. Existing tests still pass +4. The docs app build still works (for interact) + +Each phase is a separate PR (or set of PRs) that can be reviewed independently. \ No newline at end of file From 9f8e67fe3dd6c8949d1f62d2578993cfc3cc2540 Mon Sep 17 00:00:00 2001 From: ameerabuf Date: Wed, 13 May 2026 20:30:28 +0300 Subject: [PATCH 2/2] spec for phase 0 - infra --- .../context_ssot_restructure_c6889ec0.plan.md | 2 +- context_ssot_spec.md | 978 ++++++++++++++++++ 2 files changed, 979 insertions(+), 1 deletion(-) create mode 100644 context_ssot_spec.md diff --git a/.cursor/plans/context_ssot_restructure_c6889ec0.plan.md b/.cursor/plans/context_ssot_restructure_c6889ec0.plan.md index 730d7e81..eb4afd6d 100644 --- a/.cursor/plans/context_ssot_restructure_c6889ec0.plan.md +++ b/.cursor/plans/context_ssot_restructure_c6889ec0.plan.md @@ -4,7 +4,7 @@ overview: "Restructure the rules/ and docs/ across all three packages (@wix/inte todos: - id: phase-0-schema content: Design YAML glossary schema and marker syntax for templates - status: pending + status: completed - id: phase-0-build content: Build scripts/build-context.js (YAML + templates -> rules/ and docs/ markdown) status: pending diff --git a/context_ssot_spec.md b/context_ssot_spec.md new file mode 100644 index 00000000..f8d2cb51 --- /dev/null +++ b/context_ssot_spec.md @@ -0,0 +1,978 @@ +# Phase 0 Specification: Context SSOT Infrastructure + +This document is the detailed implementation spec for **Phase 0** of the [Context SSOT Restructure plan](.cursor/plans/context_ssot_restructure_c6889ec0.plan.md). Phase 0 delivers the shared tooling (YAML schema, marker syntax, build script, validation script) that all subsequent per-package phases depend on. + +--- + +## Table of Contents + +- [1. YAML Glossary Schema](#1-yaml-glossary-schema) + - [1.1 Top-Level Structure](#11-top-level-structure) + - [1.2 Term Entry Schema](#12-term-entry-schema) + - [1.3 Param Schema](#13-param-schema) + - [1.4 Field Schema (for config/type categories)](#14-field-schema-for-configtype-categories) + - [1.5 Value Schema (for enum categories)](#15-value-schema-for-enum-categories) + - [1.6 Category Taxonomy](#16-category-taxonomy) + - [1.7 ID Convention](#17-id-convention) + - [1.8 Validation Constraints](#18-validation-constraints) + - [1.9 Annotated Example: Interact glossary excerpt](#19-annotated-example-interact-glossary-excerpt) +- [2. Template Marker Syntax](#2-template-marker-syntax) + - [2.1 Marker Format](#21-marker-format) + - [2.2 Text Renderers](#22-text-renderers) + - [2.3 Formatted Renderers](#23-formatted-renderers) + - [2.4 Structural Markers](#24-structural-markers) + - [2.5 Escaping and Edge Cases](#25-escaping-and-edge-cases) + - [2.6 Renderer Output Formats](#26-renderer-output-formats) +- [3. Build Script (`scripts/build-context.js`)](#3-build-script-scriptsbuild-contextjs) + - [3.1 CLI Interface](#31-cli-interface) + - [3.2 Algorithm](#32-algorithm) + - [3.3 Error Handling](#33-error-handling) + - [3.4 Output File Rules](#34-output-file-rules) + - [3.5 Generated File Header](#35-generated-file-header) +- [4. Validation Script (`scripts/validate-context.js`)](#4-validation-script-scriptsvalidate-contextjs) + - [4.1 CLI Interface](#41-cli-interface) + - [4.2 Extraction Strategy](#42-extraction-strategy) + - [4.3 What Gets Validated](#43-what-gets-validated) + - [4.4 Report Format](#44-report-format) + - [4.5 Limitations and Future Considerations](#45-limitations-and-future-considerations) +- [5. Directory Layout](#5-directory-layout) +- [6. Gitignore and CI Strategy](#6-gitignore-and-ci-strategy) +- [7. Root `package.json` Changes](#7-root-packagejson-changes) +- [8. Dependencies](#8-dependencies) +- [9. Template File Conventions](#9-template-file-conventions) + - [9.1 Rules Templates](#91-rules-templates) + - [9.2 Docs Templates](#92-docs-templates) +- [10. Acceptance Criteria](#10-acceptance-criteria) + +--- + +## 1. YAML Glossary Schema + +### 1.1 Top-Level Structure + +Each package gets one `context/glossary.yaml`. The file has two top-level keys: + +```yaml +meta: + package: "@wix/interact" + version: "2.2.2" # tracks which package version was audited + lastAudit: "2026-05-13" # ISO date of last source-code verification + +terms: + - id: trigger-viewEnter + # ... term fields ... + - id: effect-type-time + # ... +``` + +| Key | Type | Required | Description | +| ------ | -------- | -------- | --------------------------------------------------- | +| `meta` | object | yes | Package identity and audit provenance | +| `terms`| array | yes | All glossary entries (triggers, types, API, etc.) | + +`meta` fields: + +| Field | Type | Required | Description | +| ----------- | ------ | -------- | ------------------------------------------------- | +| `package` | string | yes | npm package name | +| `version` | string | yes | Package version at last audit | +| `lastAudit` | string | yes | ISO date of last manual source-code verification | + +### 1.2 Term Entry Schema + +Every entry in `terms` has these fields: + +| Field | Type | Required | Description | +| ------------ | ------------- | -------- | ------------------------------------------------------------------------ | +| `id` | string | yes | Unique identifier. See [ID convention](#17-id-convention). | +| `name` | string | yes | Display name (e.g., `viewEnter`, `TimeEffect`, `Interact.create`). | +| `category` | enum string | yes | One of the [category values](#16-category-taxonomy). | +| `llm` | string | yes | Compact LLM-facing description (1-2 sentences). | +| `human` | string | yes | Narrative human-facing description (1-3 sentences). | +| `params` | array | no | Parameter definitions. See [1.3](#13-param-schema). | +| `fields` | array | no | Object field definitions. See [1.4](#14-field-schema-for-configtype-categories). | +| `values` | array | no | Enum member definitions. See [1.5](#15-value-schema-for-enum-categories).| +| `signature` | string | no | TypeScript function signature (for `api` category entries). | +| `returns` | string | no | Return type description (for `api` category entries). | +| `caveats` | array[string] | no | Known gotchas, pitfalls, or non-obvious behaviors. | +| `code` | string | no | Canonical code example (fenced as TypeScript by the renderer). | +| `sourceFile` | string | no | Relative path to the TS source file (for validation). | +| `sourceName` | string | no | Exported symbol name in `sourceFile` (for validation). | +| `related` | array[string] | no | IDs of related terms (used for cross-reference links). | + +**Exactly one of `params`, `fields`, or `values` should be present** when the term describes a structured type. Plain concepts and API entries may have none. A term may have both `params` and `caveats`, or `fields` and `code`, etc. + +### 1.3 Param Schema + +Used for triggers, effect types, API function options, and preset parameters. + +| Field | Type | Required | Description | +| ------------- | ------------------------ | -------- | -------------------------------------------------------- | +| `name` | string | yes | Parameter name as it appears in TypeScript | +| `type` | string | yes | TypeScript type (e.g., `number`, `'x' \| 'y'`, `string`) | +| `default` | string \| number \| null | yes | Default value. Use `null` for "no default" (required param), use the string `"undefined"` for optional with no default. | +| `description` | string | yes | One-line description | +| `required` | boolean | no | Defaults to `false`. Set `true` for non-optional params. | + +Example: + +```yaml +params: + - name: threshold + type: number + default: 0.2 + description: "Fraction of element that must be visible (0–1)" + - name: inset + type: string + default: "undefined" + description: "Mapped to IntersectionObserver rootMargin" + - name: effectId + type: string + default: null + required: true + description: "ID of the preceding effect to chain after" +``` + +### 1.4 Field Schema (for config/type categories) + +Used for config shapes like `InteractConfig`, `Interaction`, `Condition`, etc. + +| Field | Type | Required | Description | +| ------------- | ------------------------ | -------- | ----------------------------------------------- | +| `name` | string | yes | Field name | +| `type` | string | yes | TypeScript type | +| `required` | boolean | yes | Whether the field is required | +| `description` | string | yes | One-line description | + +Example: + +```yaml +fields: + - name: effects + type: "Record" + required: true + description: "Reusable effect definitions keyed by effectId" + - name: interactions + type: "Interaction[]" + required: true + description: "Array of trigger-to-effect bindings" + - name: sequences + type: "Record" + required: false + description: "Reusable sequence definitions keyed by sequenceId" + - name: conditions + type: "Record" + required: false + description: "Named conditions referenced by interactions and effects" +``` + +### 1.5 Value Schema (for enum categories) + +Used for union types like `TriggerType`, `TimeAnimationTriggerType`, `StateAction`, `Fill`. + +| Field | Type | Required | Description | +| ------------- | ------ | -------- | --------------------------------- | +| `value` | string | yes | The literal string value | +| `description` | string | yes | What this value means | + +Example: + +```yaml +values: + - value: "once" + description: "Fires once then stops. Default for viewEnter, pageVisible, animationEnd." + - value: "repeat" + description: "Fires every time the trigger activates." + - value: "alternate" + description: "Alternates between forward and reverse. Default for hover, click, activate, interest." + - value: "state" + description: "Applies a CSS state change via stateAction." +``` + +### 1.6 Category Taxonomy + +The `category` field uses one of these values: + +| Category | For | Example entries | +| ------------- | -------------------------------------------------- | ------------------------------------------------ | +| `trigger` | Trigger types that initiate interactions | `viewEnter`, `hover`, `click`, `pointerMove` | +| `effect-type` | Discriminated effect shapes | `TimeEffect`, `ScrubEffect`, `StateEffect` | +| `config` | Configuration object shapes and their fields | `InteractConfig`, `Interaction`, `Condition` | +| `api` | Exported functions and class methods | `Interact.create`, `add`, `remove`, `generate` | +| `concept` | Cross-cutting ideas explained in prose | FOUC prevention, element resolution, a11y mapping | +| `enum` | Union/literal types with enumerable values | `TriggerType`, `StateAction`, `Fill` | +| `preset` | Named effect presets (motion-presets package only) | `FadeIn`, `ParallaxScroll`, `Tilt3DMouse` | + +### 1.7 ID Convention + +IDs are kebab-case, prefixed with the category: + +``` +{category}-{name} +``` + +Examples: +- `trigger-viewEnter` +- `trigger-hover` +- `effect-type-time` +- `effect-type-scrub` +- `config-InteractConfig` +- `config-Interaction` +- `config-Condition` +- `api-Interact.create` +- `api-generate` +- `concept-fouc` +- `concept-element-resolution` +- `enum-TriggerType` +- `enum-TimeAnimationTriggerType` +- `enum-StateAction` +- `preset-FadeIn` (motion-presets only) + +**Rules:** +- IDs are globally unique within a glossary file. +- The `name` portion preserves the original casing of the TypeScript identifier (e.g., `viewEnter`, not `view-enter`). +- Cross-package `related` references use the format `@package/term-id` (e.g., `@motion/api-registerEffects`). Within the same package, use bare IDs. + +### 1.8 Validation Constraints + +The build script validates these constraints before producing output: + +| Constraint | Error Level | Description | +| --------------------------------- | ----------- | ------------------------------------------------------------ | +| Unique IDs | error | No duplicate `id` values within a glossary | +| Required fields present | error | Every term has `id`, `name`, `category`, `llm`, `human` | +| Valid category | error | `category` is one of the enumerated values | +| Params have required fields | error | Each param has `name`, `type`, `default`, `description` | +| Fields have required fields | error | Each field has `name`, `type`, `required`, `description` | +| Values have required fields | error | Each value has `value`, `description` | +| No orphan related refs | warning | Every ID in `related` exists in the same glossary or uses `@package/` prefix | +| `sourceFile` path exists | warning | The referenced file exists on disk | + +### 1.9 Annotated Example: Interact glossary excerpt + +This is a representative sample showing how the Interact package glossary will look. It covers one entry from each category to demonstrate the schema in practice. + +```yaml +meta: + package: "@wix/interact" + version: "2.2.2" + lastAudit: "2026-05-13" + +terms: + # --- Trigger --- + - id: trigger-viewEnter + name: viewEnter + category: trigger + llm: >- + Fires when element crosses viewport threshold via IntersectionObserver. + pageVisible uses the same handler. + human: >- + Triggers an animation when an element scrolls into the visible area + of the page. Uses IntersectionObserver under the hood, so it works + even for elements that are already in the viewport on page load. + params: + - name: threshold + type: number + default: 0.2 + description: "Fraction of element that must be visible (0–1)" + - name: inset + type: string + default: "undefined" + description: "Mapped to IntersectionObserver rootMargin" + - name: useSafeViewEnter + type: boolean + default: false + description: "Use fallback observer strategy for edge cases" + caveats: + - >- + When source and target are the same element, only triggerType 'once' + is reliable. Other types cause the animation to shift the element + out of the viewport, triggering rapid re-fires. + sourceFile: src/types/triggers.ts + sourceName: ViewEnterParams + related: + - trigger-pageVisible + - concept-fouc + - enum-TimeAnimationTriggerType + + # --- Effect type --- + - id: effect-type-time + name: TimeEffect + category: effect-type + llm: >- + Time-based effect with explicit duration. Used with hover, click, + viewEnter, animationEnd, activate, interest triggers. + human: >- + An effect that runs over a fixed duration in milliseconds. This is + the most common effect type, used for entrance animations, hover + responses, and click interactions. + params: + - name: duration + type: number + default: null + required: true + description: "Animation duration in milliseconds" + - name: easing + type: string + default: "undefined" + description: "CSS easing function string" + - name: iterations + type: number + default: "undefined" + description: "Number of iterations" + - name: alternate + type: boolean + default: "undefined" + description: "Alternate direction on each iteration" + - name: fill + type: Fill + default: "undefined" + description: "Animation fill mode" + - name: reversed + type: boolean + default: "undefined" + description: "Reverse the animation direction" + - name: delay + type: number + default: "undefined" + description: "Delay before animation starts in ms" + - name: triggerType + type: TimeAnimationTriggerType + default: "undefined" + description: "How the trigger repeats. Per-trigger defaults apply." + - name: composite + type: CompositeOperation + default: "undefined" + description: "WAAPI composite operation" + caveats: + - >- + Must also include exactly one effect property: namedEffect, + keyframeEffect, or customEffect. + sourceFile: src/types/effects.ts + sourceName: TimeEffect + related: + - effect-type-scrub + - effect-type-state + - enum-TimeAnimationTriggerType + + # --- Config --- + - id: config-InteractConfig + name: InteractConfig + category: config + llm: >- + Top-level configuration object passed to Interact.create(). + effects is required (not optional despite some docs claiming otherwise). + human: >- + The root configuration object that defines all interactions for a + page or component. Contains effect definitions, interaction bindings, + and optional sequences and conditions. + fields: + - name: effects + type: "Record" + required: true + description: "Reusable effect definitions keyed by effectId" + - name: interactions + type: "Interaction[]" + required: true + description: "Array of trigger-to-effect bindings" + - name: sequences + type: "Record" + required: false + description: "Reusable sequence definitions keyed by sequenceId" + - name: conditions + type: "Record" + required: false + description: "Named conditions referenced by interactions and effects" + caveats: + - >- + effects is REQUIRED in the TypeScript type despite some + current docs/rules describing it as optional. + sourceFile: src/types/config.ts + sourceName: InteractConfig + related: + - config-Interaction + - config-Condition + + # --- API --- + - id: api-Interact.create + name: Interact.create + category: api + llm: >- + Creates a new Interact instance from config. Multiple calls create + independent instances. Returns the instance. + human: >- + Initializes the interaction system with a configuration object. + Call this once per logical scope (page, component, lazy section). + Returns an instance you can use to add elements or destroy later. + signature: "static create(config: InteractConfig, options?: { useCustomElement?: boolean }): Interact" + returns: "Interact instance" + code: | + const instance = Interact.create(config); + sourceFile: src/core/Interact.ts + sourceName: create + related: + - api-Interact.destroy + - config-InteractConfig + + # --- Concept --- + - id: concept-fouc + name: FOUC Prevention + category: concept + llm: >- + Requires BOTH generate(config) to produce critical CSS AND + data-interact-initial="true" on elements. Using only one has no effect. + human: >- + Flash of Un-animated Content (FOUC) occurs when entrance-animated + elements briefly show their final state before the animation runs. + Preventing it requires two coordinated steps: generating critical CSS + that hides elements in their pre-animation state, and marking those + elements so the runtime knows to apply the hiding styles. + caveats: + - "Only valid for viewEnter + triggerType 'once' where source and target are the same element." + - "generate() should run server-side or at build time." + code: | + import { generate } from '@wix/interact/web'; + const css = generate(config); + // Inject into : + // Mark elements: data-interact-initial="true" + related: + - api-generate + - trigger-viewEnter + + # --- Enum --- + - id: enum-TimeAnimationTriggerType + name: TimeAnimationTriggerType + category: enum + llm: >- + Controls repeat behavior: 'once' (default for viewEnter/pageVisible/animationEnd), + 'repeat', 'alternate' (default for hover/click/activate/interest), 'state'. + human: >- + Determines how an effect responds when its trigger fires multiple + times. The default depends on the trigger type. + values: + - value: "once" + description: "Fires once then stops. Default for viewEnter, pageVisible, animationEnd." + - value: "repeat" + description: "Fires every time the trigger activates." + - value: "alternate" + description: "Alternates forward/reverse. Default for hover, click, activate, interest." + - value: "state" + description: "Applies CSS state via stateAction instead of running an animation." + sourceFile: src/types/effects.ts + sourceName: TimeAnimationTriggerType + related: + - effect-type-time + - enum-StateAction +``` + +--- + +## 2. Template Marker Syntax + +### 2.1 Marker Format + +All markers use double-brace delimiters: + +``` +{{type:argument}} +``` + +There are two families of markers: + +1. **Term markers** — reference glossary data: `{{term:id.renderer}}` +2. **Structural markers** — control document assembly: `{{include:path}}` + +### 2.2 Text Renderers + +These inject a glossary field value as plain text (no formatting added): + +| Marker | Output | +| ------------------------- | ---------------------------------------- | +| `{{term:id.name}}` | The `name` field | +| `{{term:id.llm}}` | The `llm` description | +| `{{term:id.human}}` | The `human` description | +| `{{term:id.signature}}` | The `signature` field (inline code) | +| `{{term:id.returns}}` | The `returns` field | + +`signature` is wrapped in backtick fences when rendered: + +```markdown +`static create(config: InteractConfig, options?: { useCustomElement?: boolean }): Interact` +``` + +### 2.3 Formatted Renderers + +These generate structured markdown from array fields: + +| Marker | Source Field | Output Format | +| ---------------------------- | ------------ | ------------------------------ | +| `{{term:id.params-table}}` | `params` | Markdown table | +| `{{term:id.fields-table}}` | `fields` | Markdown table | +| `{{term:id.values-table}}` | `values` | Markdown table | +| `{{term:id.caveats-list}}` | `caveats` | Bullet list | +| `{{term:id.code}}` | `code` | Fenced TypeScript code block | + +### 2.4 Structural Markers + +| Marker | Behavior | +| ------------------------------- | -------------------------------------------------------------- | +| `{{include:path/to/fragment.md}}` | Replaced with the contents of the referenced file. Path is relative to the package's `context/templates/` directory. Included files are also processed for markers (single level of nesting; includes within includes are not resolved). | + +### 2.5 Escaping and Edge Cases + +| Scenario | Behavior | +| ---------------------------------- | ------------------------------------------------------------- | +| Marker inside fenced code block | **Not replaced.** The build script skips markers that appear between `` ``` `` fences. This allows documenting the marker syntax itself. | +| Marker with unknown term ID | **Build error.** The script exits with code 1 and reports the file, line number, and unresolved marker. | +| Marker with unknown renderer | **Build error.** Same as above. | +| Term exists but requested field is empty/missing | **Build error** for required renderers (`llm`, `human`, `name`). **Renders as empty string** for optional fields (`code`, `caveats`, `signature`). When `params`, `fields`, or `values` is missing and a table renderer is called, renders the string `*No parameters.*`, `*No fields.*`, or `*No values.*` respectively. | +| `{{include:...}}` target not found | **Build error.** Reports the missing file path. | +| Literal `{{` in output | Use `\{{` to produce a literal `{{` in the rendered output. | + +### 2.6 Renderer Output Formats + +**`params-table`** produces: + +```markdown +| Param | Type | Default | Description | +| ----- | ---- | ------- | ----------- | +| `threshold` | `number` | `0.2` | Fraction of element that must be visible (0–1) | +| `inset` | `string` | — | Mapped to IntersectionObserver rootMargin | +``` + +Rules: +- Param names and types are wrapped in backticks. +- `default: null` (required param) renders as `**required**`. +- `default: "undefined"` (optional, no default) renders as `—`. +- All other defaults are wrapped in backticks. +- If `required: true`, a bold `**required**` is used in the Default column regardless of the `default` value. + +**`fields-table`** produces: + +```markdown +| Field | Type | Required | Description | +| ----- | ---- | -------- | ----------- | +| `effects` | `Record` | yes | Reusable effect definitions keyed by effectId | +| `interactions` | `Interaction[]` | yes | Array of trigger-to-effect bindings | +| `sequences` | `Record` | no | Reusable sequence definitions keyed by sequenceId | +``` + +**`values-table`** produces: + +```markdown +| Value | Description | +| ----- | ----------- | +| `'once'` | Fires once then stops. Default for viewEnter, pageVisible, animationEnd. | +| `'repeat'` | Fires every time the trigger activates. | +``` + +Values are wrapped in single quotes inside backticks. + +**`caveats-list`** produces: + +```markdown +- ⚠️ When source and target are the same element, only triggerType 'once' is reliable. +- ⚠️ generate() should run server-side or at build time. +``` + +Each caveat gets a `⚠️` prefix for visual scanning. + +**`code`** produces: + +````markdown +```typescript +const instance = Interact.create(config); +``` +```` + +The `code` field value is wrapped in a TypeScript fenced block. + +--- + +## 3. Build Script (`scripts/build-context.js`) + +### 3.1 CLI Interface + +```bash +node scripts/build-context.js --package [--package ] [--all] [--dry-run] [--verbose] +``` + +| Flag | Description | +| ------------ | ------------------------------------------------------------- | +| `--package` | Package directory name (e.g., `interact`, `motion`, `motion-presets`). Repeatable. | +| `--all` | Process all packages that have a `context/` directory. | +| `--dry-run` | Validate and report what would be written, without writing. | +| `--verbose` | Print each marker resolution to stdout. | + +**Exit codes:** +- `0` — success, all files written. +- `1` — one or more errors (unresolved markers, missing terms, schema violations). No files are written on error. + +### 3.2 Algorithm + +For each package: + +1. **Read glossary.** Parse `packages//context/glossary.yaml` using the `yaml` npm package. Validate against the schema constraints in [1.8](#18-validation-constraints). Abort on error. + +2. **Index terms.** Build a `Map` for O(1) lookups. + +3. **Discover templates.** Recursively glob `packages//context/templates/rules/**/*.md` and `packages//context/templates/docs/**/*.md`. + +4. **Process each template.** + - Read the file contents. + - Identify fenced code blocks (``` regions) and mark their line ranges as "skip zones." + - For each line outside skip zones, find all `{{...}}` markers using regex: `/(? + +``` + +This prevents accidental edits to generated files and points to the source. + +--- + +## 4. Validation Script (`scripts/validate-context.js`) + +### 4.1 CLI Interface + +```bash +node scripts/validate-context.js --package [--all] [--fix] [--json] +``` + +| Flag | Description | +| ----------- | -------------------------------------------------------------- | +| `--package` | Package directory name. Repeatable. | +| `--all` | Validate all packages with a `context/` directory. | +| `--fix` | Write corrected values back to glossary (future; not in v1). | +| `--json` | Output report as JSON instead of human-readable text. | + +**Exit codes:** +- `0` — all checks pass. +- `1` — one or more validation errors. + +### 4.2 Extraction Strategy + +**Phase 0 uses regex-based extraction.** No additional dependencies. + +The script extracts ground truth from TypeScript source using these patterns: + +**Type member extraction** (for verifying param/field names and types): + +```javascript +// Match: name?: type; or name: type; +/^\s*(\w+)(\?)?:\s*(.+?);/gm +``` + +Applied to the content of the type block identified by `sourceName` in the file at `sourceFile`. + +**Type block identification:** + +```javascript +// Match: export type Name = { ... } (handles multi-line) +/export\s+type\s+NAME\s*=\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/s +``` + +Where `NAME` is the `sourceName` from the glossary entry. + +**Destructuring default extraction** (for verifying default values): + +```javascript +// Match: name = defaultValue in destructuring patterns +/(\w+)\s*=\s*([^,}\n]+)/g +``` + +**Enum/union extraction** (for verifying enum values): + +```javascript +// Match: | 'value' in union types +/'\s*([^']+)\s*'/g +``` + +**Fallback plan:** If regex extraction proves unreliable for a specific pattern (e.g., complex generic types, conditional types), the validation script should: +1. Log a warning that the entry could not be automatically verified. +2. Continue without failing the build. +3. The entry gets flagged for manual review. + +If more than 30% of entries cannot be automatically verified, evaluate adding `ts-morph` as a devDependency. This decision is deferred to implementation. + +### 4.3 What Gets Validated + +For each glossary entry that has both `sourceFile` and `sourceName`: + +| Check | What it does | Severity | +| ---------------------- | -------------------------------------------------------------------------------- | -------- | +| File exists | `sourceFile` resolves to an existing `.ts` file | error | +| Symbol exists | `sourceName` appears as an exported type/const/function in the file | error | +| Param names match | Every `params[].name` exists as a member of the source type | error | +| Param types match | Each param's `type` matches the source type annotation (normalized, see below) | warning | +| Field names match | Every `fields[].name` exists as a member of the source type | error | +| Value members match | Every `values[].value` appears in the source union type | error | +| No missing members | Source type has no members missing from the glossary params/fields | warning | +| Export still exists | For `api` entries, the symbol is still exported from the package entry point | warning | + +**Type normalization:** Before comparing types, both the glossary type and the source type are normalized: whitespace is collapsed, `readonly` is stripped, import-path qualifiers are removed. This prevents false positives from formatting differences. + +### 4.4 Report Format + +**Human-readable (default):** + +``` +Validating @wix/interact glossary (42 terms, 28 with sourceFile)... + +✓ trigger-viewEnter — ViewEnterParams in src/types/triggers.ts — 3/3 params match +✗ trigger-hover — no sourceFile (skipped) +✗ config-InteractConfig — InteractConfig in src/types/config.ts + ERROR: param "effects" marked required=false in glossary but is required in source + WARNING: source has member "interactions" not listed in glossary fields + +Summary: 26 passed, 1 error, 2 warnings, 14 skipped (no sourceFile) +``` + +**JSON (with `--json`):** + +```json +{ + "package": "@wix/interact", + "totalTerms": 42, + "validated": 28, + "skipped": 14, + "errors": [ + { + "termId": "config-InteractConfig", + "check": "field-required", + "message": "param 'effects' marked required=false in glossary but is required in source", + "sourceFile": "src/types/config.ts", + "sourceName": "InteractConfig" + } + ], + "warnings": [] +} +``` + +### 4.5 Limitations and Future Considerations + +- **Default values from destructuring** can only be verified when the handler code uses destructuring with defaults (e.g., `{ threshold = 0.2 }`). Many defaults are applied elsewhere in the logic (e.g., in handler initialization). The script does not trace runtime logic — it only checks static destructuring patterns. +- **Cross-file type resolution** is not supported. If a type extends or intersects another type from a different file, only the direct members are checked. Inherited members require manual review. +- **Generic types** like `Record` are compared as opaque strings after normalization. The script does not resolve generic parameters. +- If the regex strategy proves insufficient, `ts-morph` can be added later. The validation script's internal architecture uses an `Extractor` interface (`extractTypeMembers(file, name) → Member[]`) that can be swapped without changing the rest of the script. + +--- + +## 5. Directory Layout + +After Phase 0, the repo will have these new files: + +``` +interact/ # monorepo root +├── scripts/ +│ ├── build-context.js # NEW — glossary + templates → rules/ + docs/ +│ └── validate-context.js # NEW — glossary vs TS source checker +├── packages/ +│ ├── interact/ +│ │ ├── context/ # NEW — SSOT source +│ │ │ ├── glossary.yaml +│ │ │ └── templates/ +│ │ │ ├── rules/ +│ │ │ │ ├── overview.md +│ │ │ │ ├── config.md +│ │ │ │ ├── triggers.md +│ │ │ │ ├── effects.md +│ │ │ │ └── pitfalls.md +│ │ │ └── docs/ +│ │ │ ├── README.md +│ │ │ ├── guides/ +│ │ │ │ └── ... +│ │ │ ├── api/ +│ │ │ │ └── ... +│ │ │ └── ... +│ │ ├── rules/ # OUTPUT (generated) +│ │ │ ├── overview.md +│ │ │ └── ... +│ │ └── docs/ # OUTPUT (generated) +│ │ └── ... +│ ├── motion/ +│ │ └── context/ # Created in Phase 2 +│ └── motion-presets/ +│ └── context/ # Created in Phase 3 +``` + +Phase 0 delivers only `scripts/build-context.js` and `scripts/validate-context.js`. The `context/` directories are created during each package's phase, not during Phase 0. However, a minimal test fixture is created to verify the scripts work (see [Acceptance Criteria](#10-acceptance-criteria)). + +--- + +## 6. Gitignore and CI Strategy + +**Decision: Commit generated output.** + +Rationale: +- `packages/interact/package.json` includes `"rules"` and `"docs"` in its `"files"` array, meaning they are published to npm. If these were gitignored, a pre-publish build step would be required — adding complexity and a new failure mode. +- The `apps/docs` dev server mounts `packages/interact/rules/` and `packages/interact/docs/` directly via Vite aliases. Gitignoring them would require running the build script before `dev:docs` works. +- Committing generated output follows the same pattern as lockfiles: the source of truth is the input (glossary + templates), and CI verifies the output is in sync. + +**CI verification step** (added to the existing CI workflow): + +```yaml +- name: Verify context output is up to date + run: | + node scripts/build-context.js --all + git diff --exit-code rules/ docs/ +``` + +If someone edits the glossary or templates but forgets to re-run the build, CI catches it. + +**No `.gitignore` changes needed** — `rules/` and `docs/` remain tracked. + +**Generated file header** (from [3.5](#35-generated-file-header)) serves as the safety mechanism against accidental direct edits during development. + +--- + +## 7. Root `package.json` Changes + +Add two new scripts to the root `package.json`: + +```json +{ + "scripts": { + "build:context": "node scripts/build-context.js --all", + "validate:context": "node scripts/validate-context.js --all" + } +} +``` + +These sit alongside the existing `build`, `lint`, `test` scripts. They are not wired into the main `build` command — context building is a separate concern that runs less frequently than code builds. + +Per-package `package.json` files are **not modified**. The context scripts are monorepo-level tooling. + +--- + +## 8. Dependencies + +| Dependency | Purpose | Type | Notes | +| ---------- | ------------------------------ | ---------- | --------------------------------- | +| `yaml` | Parse YAML glossary files | devDep | Standard YAML parser; ~50KB | + +No other new dependencies. The scripts use: +- `node:fs` / `node:path` / `node:process` — built-in +- `node:url` — for `import.meta.url` resolution +- Regex — for TypeScript source extraction (no ts-morph in v1) + +**The `yaml` package is added to the root `package.json` devDependencies**, not to individual packages. + +--- + +## 9. Template File Conventions + +### 9.1 Rules Templates + +Rules templates target LLM consumers. Conventions: + +**Frontmatter:** Every rules template starts with YAML frontmatter matching the existing motion-presets pattern: + +```yaml +--- +name: interact-triggers +description: >- + Complete trigger reference for @wix/interact. Read when configuring + viewEnter, hover, click, pointerMove, viewProgress, or other triggers. +--- +``` + +| Field | Required | Purpose | +| ------------- | -------- | ------------------------------------------------------- | +| `name` | yes | Kebab-case identifier for this rule file | +| `description` | yes | Agent-facing routing hint (when to load this file) | + +The build script **preserves frontmatter as-is** in the output. It does not process markers inside frontmatter. + +**Table of Contents:** After frontmatter, include a `## Table of Contents` section with anchor links to all `##`/`###` headings. This supports LLM selective reading. + +**Section separators:** Use `---` between major sections. + +**Param tables:** Use `{{term:id.params-table}}` markers. Do not hand-write param tables. + +**Code examples:** Keep examples minimal (3-8 lines). Use `{{term:id.code}}` when the glossary has a canonical example, or write inline examples in the template for context-specific usage. + +**Cross-references:** Use relative links to sibling rule files: `[triggers](./triggers.md)`. Never reference docs from rules (different audience). + +### 9.2 Docs Templates + +Docs templates target human developers. Conventions: + +**No frontmatter.** Docs files use standard markdown. The `## Table of Contents` pattern is optional (docs may use auto-generated TOCs from the docs app). + +**Narrative style.** Docs explain "why" and "when", not just "what". Use `{{term:id.human}}` for descriptions, not `{{term:id.llm}}`. + +**Same param tables.** Use the same `{{term:id.params-table}}` markers as rules. The tables are identical — only the surrounding prose differs. + +**Longer code examples.** Docs may include multi-step examples that combine multiple concepts. These are written directly in the template, not pulled from the glossary. + +--- + +## 10. Acceptance Criteria + +Phase 0 is complete when: + +1. **`scripts/build-context.js` exists** and can: + - Parse a `glossary.yaml` file + - Process template files with all marker types (text renderers, formatted renderers, includes) + - Write output files preserving directory structure + - Fail with clear errors on unknown terms, missing fields, or broken includes + - Skip markers inside fenced code blocks + +2. **`scripts/validate-context.js` exists** and can: + - Parse a `glossary.yaml` file + - For each term with `sourceFile`/`sourceName`, extract type members via regex + - Report mismatches between glossary entries and source code + - Exit 0 on success, 1 on errors + +3. **Test fixture passes.** A minimal test glossary and template set (can live in `scripts/test-fixtures/` or be inline in the test) exercises: + - All six renderers (name, llm, human, params-table, fields-table, values-table, caveats-list, code, signature, returns) + - The `{{include:...}}` marker + - Error cases: unknown term, missing field, include-not-found + - Fenced-code-block skipping + +4. **Root `package.json`** has `build:context` and `validate:context` scripts. + +5. **`yaml` devDependency** is installed at the root. + +6. **No existing tests break.** Running `yarn test` at the root still passes. + +7. **No existing builds break.** Running `yarn build` and `yarn workspace @wix/interact-docs run build` still pass (the scripts don't modify any existing files).