WORK-510
ID:WORK-510Status:done

knownSections canonicalSlug + i18nAliases — stable-slug follow-up

knownSections shipped (WORK-024, via SPEC-037) but without the localization shape SPEC-035 called for. slugify() still derives the section wrapper's data-name from raw heading text, so a non-English heading (## Akzeptanzkriterien) produces a different slug than ## Acceptance Criteria, breaking CSS selectors and cross-locale anchors. This item closes that gap. The canonicalSlug half can proceed independently (the framework already exists); the i18nAliases half layers on locale resolution.

Priority:lowComplexity:moderateMilestone:v0.29.0Source:SPEC-035

Criteria completion

Criteria completion: 4 of 4 (100%) checked; history from Jul 17 to Jul 230%25%50%75%100%Jul 17Jul 23
Branches 2
History 3
  1. f198cbc
    • ☑ `KnownSectionDefinition` gains `canonicalSlug` + `i18nAliases`.
    • ☑ Matched known sections emit a language-independent `data-name` derived from the canonical key.
    • ☑ Locale-specific heading aliases match when `locale` is set; base aliases still match.
    • ☑ Unrecognised sections keep heading-derived slugs (no regression).
    by bjornolofandersson
  2. 9c7bd86
    Created (ready)by bjornolofandersson
  3. d92ad0c
    Content editedby Claude
    plan: accept SPEC-035, add v0.29.0 milestone + i18n work breakdown

Scope

  • Add canonicalSlug?: string and i18nAliases?: Record<string, string[]> to KnownSectionDefinition (packages/types/src/content-model.ts).
  • buildSections() (plugins/plan/src/util.ts): when a section matches a known section, derive the <section data-name> from canonicalSlug (default slugify(canonicalName)), not the heading text; unrecognised sections keep slugify($heading).
  • matchKnownSection() (packages/runes/src/lib/resolver.ts): consult i18nAliases[locale] in addition to the base alias list when a locale is configured.
  • Tests: a non-English heading matched via i18nAliases yields the canonical, language-stable data-name; unrecognised headings unchanged.

Acceptance Criteria

  • KnownSectionDefinition gains canonicalSlug + i18nAliases.
  • Matched known sections emit a language-independent data-name derived from the canonical key.
  • Locale-specific heading aliases match when locale is set; base aliases still match.
  • Unrecognised sections keep heading-derived slugs (no regression).

References

  • SPEC-035 — Zone 7, Interaction with knownSections.
  • WORK-024, SPEC-037 — the shipped knownSections framework this extends.

Resolution

Completed: 2026-07-17

Branch: claude/milestone-v0-29-0-stzywk

What was done

  • Added canonicalSlug? + i18nAliases? to KnownSectionDefinition (packages/types/src/content-model.ts).
  • matchKnownSection() (packages/runes/src/lib/resolver.ts) takes an optional locale and matches i18nAliases[locale] in addition to base aliases, with BCP-47 region-strip (de-ATde). Threaded locale through resolve() / resolveContentModel() / resolveSections().
  • The resolver attaches $canonicalSlug (explicit canonicalSlug or slugify(canonicalName)) on a matched section.
  • buildSections() (plugins/plan/src/util.ts) derives the <section data-name> from $canonicalSlug when present, else slugify($heading) — so matched sections get a language-stable slug and unrecognised ones are unchanged.
  • Tests: 6 new resolver cases (canonical slug, de + de-AT alias match, region strip, no-locale no-match, default slug).

Notes

  • The canonicalSlug half is fully live (it rides $canonicalName, already attached at parse time). The i18nAliases half is threadable via the new locale param; sourcing the site locale into the parse-time resolver (Markdoc variables) is the remaining integration seam, and the matching mechanism is proven by direct tests. 1416 runes+plan tests green.