Skip to content

feat(parity): export + i18n key parity gates (§8b)#347

Merged
jedrazb merged 2 commits into1.0.0-releasefrom
feat/parity-gate-scripts
May 4, 2026
Merged

feat(parity): export + i18n key parity gates (§8b)#347
jedrazb merged 2 commits into1.0.0-releasefrom
feat/parity-gate-scripts

Conversation

@jedrazb
Copy link
Copy Markdown
Contributor

@jedrazb jedrazb commented May 4, 2026

Summary

Implements §8b.3-8b.8 of the vue-editor-robust-implementation OpenSpec change. Two new gates fail the pre-commit hook (and CI) when React and Vue adapter public surfaces drift.

What ships

scripts/check-export-parity.mjs

Two-tier gate:

Tier What Mode Today's state
1 package.json exports subpaths STRICT 9 subpaths match (8 documented divergences)
2 Named exports from src/index.ts (AST-walked) INFORMATIONAL 518 react-only, 29 vue-only — visible but non-blocking

Tier 2 stays informational during hardening; task §13b flips STRICT_NAMED_EXPORTS = true once the parity matrix turns green. Documented in the notes file.

scripts/check-i18n-parity.mjs

Walks every locale present in both packages/react/i18n/ and packages/vue/i18n/. Diffs key paths via the shared getLeafPaths helper.

Shared libs (DRY)

  • scripts/lib/i18n-keys.mjsgetLeafPaths hoisted from scripts/validate-i18n.mjs. METADATA_KEYS broadened from ['_lang'] to "any key starting with _" to cover future metadata.
  • scripts/lib/named-exports.mjs — TS-compiler-API-based AST walker for top-level exports + barrel re-exports + cycle-safe star re-exports.

Plumbing

  • New scripts: bun run check:export-parity, bun run check:i18n-parity, bun run check:parity
  • Pre-commit hook gains bun run check:parity before lint-staged. Combined gate <100ms — no commit slowdown.
  • packages/vue/i18n/en.json synced with React's source-of-truth (633 keys; PR feat: add vue version of docx editor #245 had drift).
  • New notes/intentional-export-divergence.md — 8 documented React-only subpaths with lifecycle (permanent vs hardening-temporary).

Reviewer findings addressed

Two BUGs from round-1 review:

  1. Spec required named-export parity from src/index.ts (not just package.json exports) — implemented via TS AST walker, but tiered as INFORMATIONAL during hardening because 547 names of drift today would block every commit. Strict-mode flag flipped at un-stub.
  2. Vue-only locale drift unflagged — fixed; Vue-only locales fail the gate.

NITs addressed: _* metadata skip (was hardcoded _lang), shared getLeafPaths (was duplicated across validate-i18n.mjs), opt-out parser anchored to first-backtick-on-list-item (so prose with secondary backticks doesn't widen the opt-out).

Verification

  • bun run check:parity — subpath strict pass, named informational, i18n en pass
  • bun run typecheck — clean across all 5 packages
  • bun run i18n:validate — pre-existing he.json drift NOT regressed by this PR (verified via stash test)
  • Smoke: drift detection on a fake ./vue-only subpath added to packages/vue/package.json exports — gate fails as expected with clear message

Tasks

  • §8b.3 check-export-parity.mjs
  • §8b.4 bun run check:export-parity
  • §8b.5 wired to pre-commit + CI (CI runs bun run check:parity via the same lint job)
  • §8b.6 check-i18n-parity.mjs
  • §8b.7 Vue keeps its own i18n/ dir (mirrors React)
  • §8b.8 bun run check:i18n-parity + pre-commit
  • §13.6a (new) flip STRICT_NAMED_EXPORTS at un-stub

🤖 Generated with Claude Code

Implements §8b.3-8b.8 of the vue-editor-robust-implementation OpenSpec
change. Two new scripts gate React/Vue adapter drift in the dev loop
and CI, fast (<1s combined).

scripts/check-export-parity.mjs
- Tier 1, STRICT: package.json `exports` subpaths must match between
  React and Vue, with documented opt-outs in
  notes/intentional-export-divergence.md (`./react`, `./ui`,
  `./styles.css` are permanent react-only; `./core`, `./headless`,
  `./core-plugins`, `./mcp`, `./i18n/*.json` are temporary while Vue
  catches up).
- Tier 2, INFORMATIONAL: AST-walked named exports from src/index.ts
  on both adapters. Logs the drift count (518 react-only, 29 vue-only
  today) so the gap stays visible. Task §13b flips this to strict on
  un-stub. Uses the TS compiler API for accurate parsing of barrel
  re-exports, named exports, type exports, and aliases.
- Pulls the divergence opt-out from a markdown notes file so reasons
  live next to the path; regex matches only the FIRST backtick on a
  list-item line so prose secondary backticks don't widen the opt-out.

scripts/check-i18n-parity.mjs
- Walks every locale present in BOTH packages/react/i18n/ and
  packages/vue/i18n/, diffs key paths via the shared getLeafPaths
  helper.
- Vue-only locales fail the gate (Vue can't ship a translation React
  doesn't have without breaking the source-of-truth contract).
- React-only locales are informational; Vue catches up via community
  translation issues #341-344, #302, #303.

scripts/lib/i18n-keys.mjs (new)
- Hoisted `getLeafPaths` helper used by both scripts. The existing
  `validate-i18n.mjs` had its own copy; refactored to import from the
  shared lib. METADATA_KEYS expanded from `['_lang']` to "any key
  starting with `_`" to cover future metadata fields without script
  edits.

scripts/lib/named-exports.mjs (new)
- TS-compiler-API-based AST walker. Captures top-level `export const`,
  `export function`, `export class`, `export type`, `export interface`,
  `export enum`, `export default`, `export { X, Y as Z }`, and
  `export * from './foo'` (recursively, with cycle protection).

Pre-commit hook gains a `bun run check:parity` step before
lint-staged. Combined gate runs in <100ms — no perceptible commit
slowdown.

packages/vue/i18n/en.json: synced with React's source-of-truth so the
English key set matches (Vue PR #245 had drift; bulk-copied from
packages/react/i18n/en.json — 633 keys total, including agentPanel.*
which Vue doesn't yet use but ships forward-looking).

intentional-export-divergence.md: 8 React-only subpaths documented
with reason and lifecycle (permanent vs hardening-temporary).

Tasks 8b.3, 8b.4, 8b.5, 8b.6, 8b.7, 8b.8 — done.
New task 13.6a — flip STRICT_NAMED_EXPORTS at un-stub.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docx-editor Ready Ready Preview, Comment May 4, 2026 1:48pm

Request Review

Round-2 review feedback:

HIGH (simplifier): both parity scripts repeated the same set-diff +
formatted-report pattern. Extracted into scripts/lib/parity-report.mjs
exporting diffSets() and formatDiff(). Both scripts now compose: build
two Sets, call diffSets, hand the result to formatDiff with the
appropriate strict/informational flag and labels. Truncation policy
(at 25) becomes a parameter — divergence between gates is one number
per call site, not three implementations.

NIT (reviewer): spec said "diff dist/index.d.ts named exports" but
implementation walks src/index.ts via the TS compiler API. The src
walk is more accurate (resolves through TS path aliases, no build
required, follows barrel re-exports recursively). Updated the spec
requirement wording to match implementation reality + add a sentence
explaining the choice. Also documented the two-tier (strict subpath /
informational named-export) gate split there.

Verified end-to-end:
- bun run check:parity — clean (subpath strict pass, named
  informational with the same 518/29 count, i18n en pass)
- bun run typecheck — clean across all 5 packages
- Smoke: planted `export const TestExport123 = 1` in
  packages/react/src/index.ts → count became 519 react-only;
  reverted cleanly. The drift detection works exactly as before
  the refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jedrazb jedrazb merged commit 6a00800 into 1.0.0-release May 4, 2026
2 checks passed
@jedrazb jedrazb deleted the feat/parity-gate-scripts branch May 4, 2026 13:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant