feat(component): table of contents and anchor links on docs pages#6
Conversation
a26b688 to
a6b898b
Compare
Consolidated PR Review
PR SummaryAdds a floating table-of-contents FAB and ¶ heading anchor links to every component docs page. Introduces two new Stencil components ( Merge Readiness — MERGE WITH CAVEATS
|
| Dimension | Verdict |
|---|---|
| Backward Compatibility | GOOD |
| Code Quality | ACCEPTABLE |
| Architecture | GOOD |
| Security | GOOD |
| Observability | GOOD |
| Performance | ACCEPTABLE |
| Lime Platform Issues | SAFE |
| Merge Readiness | MERGE WITH CAVEATS |
Top Recommendations
- [Medium from Code Quality] Add unit tests for the riskiest new pure logic:
slugify,uniqueExampleSlugs(dedup suffixing),currentRoute/anchorHrefhash parsing,firstLine, andtoc.tsx'scollectIds/findAncestorsOf/prune-and-expand. These are silent-regression-prone and trivially testable. - [Medium from Code Quality] Make
slugIdconsistently required across all six*Listtemplates (the only caller always supplies it) and drop the now-deadslugId ? … : nullguards — or, if the Type-page reuse ofPropertyList/MethodListgenuinely needs it optional, apply the same guarded pattern to all six. Either way, pick one contract. - [Medium from Architecture] Decide on the dual id scheme: migrate fully to slug ids and drop the vestigial route-based heading ids (or the section-anchor spans), or add a comment explaining why both must coexist.
- [Medium from Performance] Compute
uniqueExampleSlugs(examples)once inrender()and pass the result to bothExampleListandbuildTocEntriesinstead of recomputing in each. - [Medium from Performance] Consider a single shared
hashchangelistener (onkompendium-component, broadcasting to anchors) or a CSS:target/IntersectionObserver approach instead of one global listener perkompendium-anchor, to avoid 60-100+ listeners on large component pages. Not a leak — purely a scaling cleanup. - [Low from Code Quality] Resolve the remaining active CodeRabbit thread (component.tsx ~213): it does not reproduce in the shipped code, but deriving the empty-title fallback from
example.tagdirectly would also drop the dedup-suffix number and let the thread close cleanly. - [Low from Code Quality] Optional cleanups: share a first-line extractor between
splitDocsandfirstLine; extract a sharedAnchoredHeadinghelper for the six repeated section-heading blocks; collapse the three SVG render helpers into oneicon()helper.
Addressed review feedbackThanks for the thorough review. I pushed 6 fixup commits plus one standalone test commit. Summary of what changed and what I consciously left alone. Fixed
Consciously not changed
Lint and tests both pass ( |
Consolidated PR Review — Re-review (iteration 2)
What this re-review coveredVerified the 7 commits pushed in response to the first review — that each fix is correct and that none introduced a regression — and re-assessed every prior finding at consistent (absolute) severity. Merge Readiness — MERGE WITH CAVEATS
|
| Iteration-1 finding | Severity | Status |
|---|---|---|
slugId required-vs-optional inconsistency across six templates |
[Medium] | ✅ Fixed — all six now slugId? + guarded; verified consistent and the Type-page reuse still compiles |
| No tests for core new logic | [Medium] | ✅ Fixed — anchors.spec.ts + toc.spec.tsx with meaningful assertions (slugify/dedup/hash-parsing/tree helpers/prune/auto-expand) |
uniqueExampleSlugs computed twice per render |
[Medium] | ✅ Fixed — computed once in render(), threaded as slugs/exampleSlugs; verified no second call site |
| Dual id scheme | [Medium] | ✅ Documented — explanatory comment added; reasoning verified accurate & sound → drops to [Low] informational |
| Active CodeRabbit "fallback title prefix" | [Low] | ✅ Fixed — fallback now derives from example.tag |
splitDocs/firstLine duplication |
[Low] | ✅ Fixed — splitDocs reuses firstLine (behavior-preserving) |
| Three duplicated SVG helpers | [Low] | ✅ Fixed — collapsed into one icon(d, size) |
Per-anchor hashchange listener fan-out |
[Medium] | ⏸️ Consciously deferred (non-blocking, not a leak) |
| Section-anchor block copy-pasted across six templates | [Low] | ⏸️ Declined (drift risk gone after guard consistency) |
buildTocEntries/findExamples run every render |
[Low] | Carried (now strictly less work than iter 1) |
| Example titles render as escaped text not markdown | [Low] | Carried (by design) |
Dimension verdicts (iteration 2)
| Dimension | Verdict | Note |
|---|---|---|
| Backward Compatibility | GOOD ✅ | Every ExampleList caller passes the new required slugs; required→optional loosening breaks no caller; anchor-scroll surface untouched |
| Code Quality | GOOD ✅ | Fixups verified correct; logic hoisted into tested pure helpers. Residual [Low]: section-block duplication (declined), curried-renderer sectionSlug: string type is misleading under strictNullChecks off, splitDocs not directly unit-tested |
| Architecture | GOOD ✅ | exampleSlugs single-source-of-truth is a genuine data-flow improvement; dual-id decision documented accurately. Residual [Low]: three modules still parse the hash differently |
| Security | GOOD ✅ | No new sink; icon() args are static literals; escaping posture unchanged (still tighter than pre-PR) |
| Observability | GOOD ✅ | No new silent-failure path; optional-slugId guards can't hide a real misconfiguration (caller always passes constants) |
| Performance | ACCEPTABLE |
Double-compute resolved; no new cost. Remaining: deferred listener fan-out [Medium], per-render TOC build [Low], double scroll trigger [Low] |
| Lime Platform Issues | SAFE ✅ | FE-6/7/8/10 re-verified clean on the refactored toc.tsx; exports are visibility-only |
| Merge Readiness | MERGE WITH CAVEATS |
No blockers; one deferred non-blocking [Medium] |
Remaining recommendations (all non-blocking)
- [Medium from Performance] (deferred) Replace the per-
kompendium-anchorhashchangelistener with a single shared listener onkompendium-component(or:target/IntersectionObserver) to avoid 60-100+ listeners on large component pages. Tracked as a follow-up. - [Low from Code Quality] Optionally type the curried
render*factories'sectionSlugasstring | undefinedto match theslugId?reality (cosmetic understrictNullChecks: off). - [Low from Code Quality] Optionally add a direct
splitDocsunit test for the empty-docs and title-only branches. - [Low from Architecture] Optionally consolidate the three hash-parsing implementations (
anchor-scroll.ts/anchors.ts/ inlinesplit('#')[0]) behind one location helper.
Nice work on the turnaround — the fixups are precise, the fixup-commit targeting is correct, and the new tests cover exactly the silent-regression-prone units that were flagged.
Iteration-2 polishThanks for the re-review. I took two of the four optional items and consciously declined the other two. Three more fixup commits pushed. Fixed
Consciously not changed
Lint and tests both pass ( |
Consolidated PR Review — Verification pass (iteration 3)
Scope of this passThe only change since the previous review (head
This delta is runtime-inert: a TypeScript type annotation is type-erased on emit, exporting a function adds no runtime path, and the rest is test-only. The five runtime-oriented dimensions (Architecture, Security, Observability, Performance, Lime Platform) therefore carry their iteration-2 verdicts unchanged by construction — there is no new runtime surface for them to assess. The two dimensions where a type/test change could matter were re-reviewed in full:
Resolution of iteration-2 items
Merge Readiness — MERGE WITH CAVEATS
|
| Dimension | Verdict |
|---|---|
| Backward Compatibility | GOOD ✅ |
| Code Quality | GOOD ✅ |
| Architecture | GOOD ✅ (unchanged — no runtime delta) |
| Security | GOOD ✅ (unchanged — no runtime delta) |
| Observability | GOOD ✅ (unchanged — no runtime delta) |
| Performance | ACCEPTABLE |
| Lime Platform Issues | SAFE ✅ (unchanged — no runtime delta) |
| Merge Readiness | MERGE WITH CAVEATS |
Nothing further is required to merge. The remaining recommendations are optional polish and a tracked performance follow-up.
Fixed the CI build failureThe Fix (fixup →
|
e113560 to
3ad00a8
Compare
|
This is bloody fantastic @TommyLindh2! ❤️ I've added a bunch of minor fixups, that I'm about to squash now. If you want to have a look at them, the HEAD before squashing is at 3ad00a8 (you can then navigate to the parent commit, until you reach one of your own ones). |
3ad00a8 to
1c2c376
Compare
Introduce a shared kompendium-anchor component that renders a ¶ paragraph link next to a heading and highlights persistently when its slug matches the current URL anchor. Clicking an active anchor removes the fragment via history.replaceState so the scroll position is preserved. Add URL/slug helpers in anchors.ts (slugify, firstLine, exampleAnchorId, currentRoute, anchorHref, entrySlug, uniqueExampleSlugs, SECTION_SLUGS) shared by the table of contents and the heading anchors, with unit tests. Co-authored-by: Adrian Schmidt <adrian.schmidt@lime.tech> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
New floating action button that opens an overlay listing the table of contents. Entries can be collapsible and optionally default-expanded; user toggles are tracked explicitly so a default-expanded section stays open on load but can still be closed, and the active URL anchor auto-expands its ancestor sections. Includes focus management on open/close, Escape/scrim dismissal, modifier-click handling, and pruning of stale toggle state when the entries change. Tree helpers live in toc.tree.ts so they can be unit tested without importing the component module. Co-authored-by: Adrian Schmidt <adrian.schmidt@lime.tech> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Render kompendium-toc on each component docs page with entries for Examples and the Properties/Events/Methods/Slots/Styles sections, and add a ¶ anchor next to every section heading, per-entry heading, and example title. Example slugs are derived from the title text and de-duplicated, computed once and shared between the TOC and the rendered ids so they stay in sync. The playground renders the example title in-place as a heading (split out via split-docs.ts) so the anchor can sit beside it, and the secondary-hash change now scrolls from the hashchange handler since stencil-router does not re-render for fragment-only URL changes. Closes jgroth#165. Co-authored-by: Adrian Schmidt <adrian.schmidt@lime.tech> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1c2c376 to
64972f7
Compare
Summary
history.replaceState) without scrolling.Details
kompendium-anchorcomponent + URL helpers insrc/components/component/anchors.ts.kompendium-toccomponent with explicit user-toggle state,defaultExpandedsupport, and auto-expand when the URL matches a child.h5so the anchor can sit next to the text; the rest of the docs still flow throughkompendium-markdown.kompendium-component: the secondary-hash change now scrolls directly from the hashchange handler, since stencil-router'smatchdoesn't re-render for fragment-only URL changes.#with-icons) so the URL matches what the user sees.Test plan
#slug, page scrolls to that heading, anchor icon turns blue.#/component/<tag>#basic-exampledirectly — page scrolls to that example and its anchor highlights on load.#/component/<tag>/examples/still scroll to the Examples section (backward compatibility).Demo video of how it works in Lime elements
On desktop size
Kompendium.-.Lime.elements.demo-20260422_143935-Meeting.Recording.mp4
On mobile size
Kompendium.-.Lime.elements.Mobile.view.-20260427_111702-Meeting.Recording.mp4
Summary by CodeRabbit
New Features
Style
Fork copy of the upstream PR jgroth#179, branched off
mainand targetinglimeper FORK.md. Merging here releases@limetech/kompendiumwithout waiting on upstream review.