Skip to content

chore: technical debt audit remediation — batch 1 (quick wins) - #474

Merged
jackgranatowski merged 28 commits into
mainfrom
claude/pr-469-audit-rebase-ggp0e4
Jul 2, 2026
Merged

jackgranatowski merged 28 commits into
mainfrom
claude/pr-469-audit-rebase-ggp0e4

Conversation

@jackgranatowski

@jackgranatowski jackgranatowski commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

First batch from the technical-debt audit (#469): the audit's "Top 5" + "Quick wins" lists, tackled together since they overlap. This batch touches pieces of the plan's PR2 (docs/test hygiene), PR3 (script dedup), PR4 (codec readability), PR5 (config UX/deps), and PR7 (test coverage) — later PRs will finish out each of those themes individually.

Findings addressed

  • SL-016 — de-obfuscate codec.ts's internal names (Wa, Ra, za... → readable), updating the 4 call sites that imported them directly. No behavior change; public export surface unchanged.
  • SL-018 — add a saveState: 'error' state so a failed save is distinguishable from "never attempted" instead of silently reverting to 'idle'. Verified live against a mocked failing REST endpoint.
  • SL-023 — start closing the configurator's zero-test-coverage gap (the audit's only High finding): persistence.ts (18 tests, both standalone/WP-embedded branches) and SliderRow/TokenRow components (12 tests).
  • SL-007 — extract scripts/lib/parse.js; dedupe stripComments/readValue/readFile that were copy-pasted across 6 generator scripts. maskComments/maskStrings (offset-preserving, NOT interchangeable) kept as distinct exports per the audit's explicit warning. Verified npm run docs output is byte-identical before/after.
  • SL-030 — annotate docs/llm-guide.md's utilities.css entry as staged/inactive (it was listed as if live).
  • SL-029 — reorder README's à-la-carte <link> example to match core/layers.css's real @layer order.
  • SL-027 — move coverage.spec.jscoverage.test.js, off Playwright (was never touching a browser) onto node:test.
  • SL-021 — remove the unused motion dependency.

Verification

  • tsc --noEmit, svelte-check — clean
  • Configurator unit + component suites — 89/89 pass
  • Root check:version, check:llm-guide, check:macros, check:registry, audit:check — all pass
  • Root node --test unit suite — 31/31 pass
  • npm run docs output diffed byte-for-byte before/after the scripts refactor

Not verified here

Full Playwright e2e (npm test) couldn't be run in this environment — the pinned browser build doesn't match what's pre-installed in this sandbox (pre-existing environment limitation, unrelated to this diff).


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Enhanced the configurator’s shareable-link generation to use the updated URL hash format.
  • Bug Fixes
    • Improved configurator save flow with a dedicated “Save failed” error state and retry behavior.
    • Fixed export/preview CSS generation so layering and persistence/share behavior follow the intended @layer order.
    • Smoother preview DOM updates and more reliable persistence decoding/encoding.
  • Documentation
    • Updated quick-start and contribution/test guidance, plus added clarifications in migration and developer notes.
  • Tests
    • Added/expanded unit and persistence/token-editing coverage, plus new shared parser helper tests.

claude added 6 commits July 2, 2026 03:47
Rename cryptic 1-2 letter symbols in the persistence/encode/CSS-export
module (Wa, Ra, za, Ha, fa, Ga, qa...) to their existing readable
export-alias names, and update the four call sites that imported the
obfuscated names directly (persistence.ts, App.svelte,
PreviewPanel.svelte, ExportPanel.svelte). No behavior change — public
export surface (encode/decode/generateCSS/etc.) is unchanged, verified
by tsc, svelte-check, and the full codec/css/share unit test suite.
…g (SL-018)

Add an 'error' state to the saveState union so a failed REST/network
save is distinguishable from "never attempted" — previously handleSave's
catch block reset straight back to 'idle'. StudioHeader now renders a
red "Save failed" state with a retry-hint tooltip; a further edit clears
it back to 'idle' the same way 'saved' already did.

Verified live: booted the configurator against a window.slashedApp REST
target that 404s, confirmed the button renders "Save failed" after a
failed save and clears to "Save" on the next edit. Also tsc,
svelte-check, and the full unit suite pass unchanged.
…(SL-023)

The audit's top finding: persistence.ts (save/load/error paths,
WP-vs-standalone branching) and every panel's token-editing interaction
had zero test coverage — only pure codec/data logic was tested.

Add tests/persistence.test.js: loadInitialOverrides and saveOverrides
across both the standalone (localStorage + URL hash) and WP-embedded
(REST) branches, including the malformed-localStorage edge case from
SL-019 — pinned to today's real (unguarded) behavior since SL-019's fix
itself is out of scope here.

Add tests-components/token-editing.test.js: SliderRow and TokenRow,
covering bind (initial render), override-set (interacting calls
onChange/onSet), and reset (reset affordance calls onReset), plus
SliderRow's raw-CSS-value editing mode.

89 unit + component tests pass; tsc/svelte-check remain clean.
…parse.js (SL-007)

stripComments/maskComments, readValue, and readFile were each
reimplemented 3-4 times across audit.js, check-macro-catalog.js,
gen-token-index.js, gen-api-index.js, gen-token-reference.js, and
version-sync.js, with no shared module despite gen-api-index.js's header
claiming a "canonical parsing contract."

Extract byte-identical stripComments/stripStrings/readValue into
scripts/lib/parse.js. gen-api-index.js's maskComments/maskStrings are
NOT the same contract (they blank comment/string bodies in place to
preserve character offsets for banner lookups, rather than removing
them) — kept as distinct exports per the audit's explicit warning not
to merge them. readFile is split into a bare readFile(rel, root) (no
existence check, matching version-sync.js's original behavior exactly)
and requireFile(rel, root, message) for the four call sites that throw
a custom "missing file" error; each site's original error message text
is preserved unchanged.

No behavior change: npm run docs output (docs/api-index.json,
docs/token-index.json, docs/tokens.md, docs/registry.json,
token-registry.json) is byte-identical before/after, verified by diffing
a pre-refactor snapshot against a post-refactor regeneration. check:macros,
check:registry, check:llm-guide, audit:check, check:version, and the
root node --test unit suite all pass.

Also:
- SL-030: annotate docs/llm-guide.md's optional/utilities.css entry as
  staged/commented-out, matching the file's own header and README's
  existing wording — the guide previously listed it as if live.
- SL-029: reorder README's a-la-carte <link> example to match
  core/layers.css's real @layer order (themes.css was listed right
  after base.css; the real order puts it after states.css), and note
  that @layer order, not <link> order, controls the cascade.
…027)

tests/coverage.spec.js never touched a browser (pure fs/regex work
comparing core/*.css selectors against docs/demo.html) but was
Playwright-discovered, so it ran redundantly 3x across
chromium/firefox/webkit. Rename to coverage.test.js, port from
@playwright/test's test/expect to node:test/node:assert to match the
tier1-p*.test.js convention, and wire it into pretest/test:unit.
Confirmed Playwright's test list no longer includes it and node --test
runs it once. Update CONTRIBUTING.md's reference to the new filename.
motion (^12.23.24) had zero imports anywhere in configurator/src.
Verified via grep before removing. npm install updates the lockfile;
version-sync/check:version confirm nothing drifted. tsc, svelte-check,
unit + component tests, and vite build all pass unchanged.
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR updates configurator codec, persistence, preview, and save-state handling; adds shared parsing and API-index helpers for scripts; and refreshes docs, core CSS comments, and test infrastructure references.

Changes

Configurator codec, preview, and save-state updates

Layer / File(s) Summary
Codec and shared types
configurator/src/lib/codec.ts, configurator/src/types.ts, configurator/src/lib/savedThemes.ts
The codec API becomes explicit named exports, share-link helpers are split out, and the configurator types gain typed API-index, class-index, token-registry, and codec option shapes.
Preview, export, and persistence wiring
configurator/src/App.svelte, configurator/src/components/panels/ExportPanel.svelte, configurator/src/components/shell/PreviewPanel.svelte, configurator/src/lib/persistence.ts
The app, export panel, preview shell, and persistence layer switch to the new codec helpers and adjust iframe update scheduling.
Save-state UI
configurator/src/App.svelte, configurator/src/components/shell/StudioHeader.svelte
The save flow and header button add an error save state with retry-oriented rendering.
Configurator component and persistence tests
configurator/package.json, configurator/tests-components/token-editing.test.js, configurator/tests-components/header.test.js, configurator/tests/persistence.test.js, configurator/tests/previewResolver.test.js
Configurator dependencies and Vitest coverage are updated for token editing, save-state behavior, persistence seams, and preview resolver behavior.

Estimated code review effort: 5 (Critical) | ~90 minutes

Shared script parsing, API index generation, and test harness

Layer / File(s) Summary
Shared parse helpers
scripts/lib/parse.js
A shared module adds comment/string masking and stripping, value scanning, and file-loading helpers.
Script adoption and guards
scripts/audit.js, scripts/check-macro-catalog.js, scripts/gen-token-reference.js, scripts/gen-token-index.js, scripts/version-sync.js
Generator and audit scripts switch to the shared parsing helpers and add output-shrink regression checks.
API index extraction and rendering
scripts/gen-api-index.js, scripts/lib/api-index/extract.js, scripts/lib/api-index/render.js
API-index generation moves into shared extraction/rendering modules and adds regression checks around generated entries.
Shared Playwright helpers and test updates
tests/render-helpers.js, tests/*.spec.js, tests/README.md, tests/tier1-*.{test,spec}.js, tests/coverage.test.js, package.json, CONTRIBUTING.md
Playwright setup is centralized, the coverage test is renamed for node:test, and the related test scripts and docs are updated.
Script docs
scripts/README.md
Script-maintenance conventions and environment notes are documented.

Estimated code review effort: 5 (Critical) | ~90 minutes

Core docs and repository guidance updates

Layer / File(s) Summary
Docs and guidance
README.md, CONTRIBUTING.md, docs/llm-guide.md, docs/migration.md, CLAUDE.md
Repository-facing guidance updates Quick Start text, test references, architecture notes, the migration heading, and the script command table.
Core CSS comments and annotations
core/accessibility.css, core/layout.css, core/themes.css, core/tokens.css, docs/token-annotations.json
Comment blocks and token annotations are expanded to describe container, theme, and dark-mode token behavior.

Estimated code review effort: 2 (Simple) | ~15 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes this PR as a first batch of technical-debt audit remediation and matches the scope of the changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/pr-469-audit-rebase-ggp0e4

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Audit quick wins: codec readability, save error state, scripts dedupe, tests

🐞 Bug fix 🧪 Tests ✨ Enhancement 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Rename configurator codec internals and update imports; no public API changes.
• Add explicit save error state and UI feedback for failed persistence.
• Add unit/component tests for persistence and token editing primitives.
• Deduplicate CSS parsing helpers across scripts and update docs/test harness.
Diagram

graph TD
  UI["Configurator UI"] --> P["persistence.ts"] --> C["codec.ts"] --> R[("token-registry.json")]
  UI --> C
  S["scripts/*.js"] --> L["scripts/lib/parse.js"]
  T["Unit tests"] --> P --> C
  T --> X["coverage.test.js"]
  D["Docs/README"] --> X
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Adopt a real CSS parser (PostCSS/CSSTree) for script parsing
  • ➕ More robust handling of edge cases than regex-based scanning
  • ➕ Easier to evolve parsing logic as CSS constructs grow
  • ➖ Adds dependency/weight to repo tooling
  • ➖ Bigger refactor than a 'quick win' and may change output subtly
2. Collapse strip/mask helpers into a single function with options
  • ➕ Smaller public helper surface in scripts/lib/parse.js
  • ➕ Less duplication in documentation/comments
  • ➖ Higher risk of misuse (offset-preserving vs non-preserving semantics are easy to confuse)
  • ➖ Contradicts the audit’s explicit warning to keep these separate

Recommendation: The PR’s approach is appropriate for an audit “batch 1” quick-win: it keeps behavior stable while improving readability, test coverage, and maintenance cost. Using a full CSS parser could be revisited later if parsing requirements expand, but the current explicit strip vs mask helper separation is the safer trade-off for now.

Files changed (21) +614 / -300

Enhancement (1) +9 / -4
StudioHeader.svelteRender explicit save failure state in header +9/-4

Render explicit save failure state in header

• Extends the saveState prop union with 'error' and updates the Save button UI to show a red “Save failed” state with a retry tooltip.

configurator/src/components/shell/StudioHeader.svelte

Bug fix (1) +5 / -5
App.svelteUse readable codec exports and track save error state +5/-5

Use readable codec exports and track save error state

• Replaces direct imports of obfuscated codec symbols with named exports (generateCSS). Extends saveState with an 'error' state and sets it on save failures; subsequent edits clear back to idle.

configurator/src/App.svelte

Refactor (11) +258 / -273
ExportPanel.svelteSwitch to named codec helpers for export/share +3/-3

Switch to named codec helpers for export/share

• Updates CSS generation and share-link creation to use generateCSS/buildShareUrl instead of importing internal/obfuscated names.

configurator/src/components/panels/ExportPanel.svelte

PreviewPanel.svelteSwitch preview CSS injection to generateCSS +4/-4

Switch preview CSS injection to generateCSS

• Replaces usages of the obfuscated CSS generator with generateCSS when generating and updating preview override styles.

configurator/src/components/shell/PreviewPanel.svelte

codec.tsDe-obfuscate internal names while preserving public API +137/-147

De-obfuscate internal names while preserving public API

• Renames cryptic internal identifiers to descriptive names across encoding/decoding, CSS generation, CSS parsing, and share URL helpers. Keeps the same exported function names (encode/decode/generateCSS/etc.) and behavior while improving readability and maintainability.

configurator/src/lib/codec.ts

persistence.tsUpdate persistence to use renamed codec exports +4/-4

Update persistence to use renamed codec exports

• Replaces imports of obfuscated codec functions with the new named exports and updates hash/localStorage save + live preview injection to use encodeOverrides/generateCSS.

configurator/src/lib/persistence.ts

audit.jsUse shared parse helpers for file and CSS preprocessing +2/-14

Use shared parse helpers for file and CSS preprocessing

• Removes local stripComments/stripStrings/readFile implementations in favor of scripts/lib/parse.js helpers, keeping error messaging consistent via requireFile.

scripts/audit.js

check-macro-catalog.jsDeduplicate CSS comment/string stripping +1/-8

Deduplicate CSS comment/string stripping

• Imports stripComments/stripStrings from scripts/lib/parse.js and removes local duplicates.

scripts/check-macro-catalog.js

gen-api-index.jsCentralize masking/value parsing and file reads +6/-49

Centralize masking/value parsing and file reads

• Imports maskComments/maskStrings/readValue/requireFile from scripts/lib/parse.js, removes duplicate implementations, and documents the offset-preserving contract distinction.

scripts/gen-api-index.js

gen-token-index.jsUse shared stripComments/readValue/requireFile +2/-24

Use shared stripComments/readValue/requireFile

• Replaces local parsing helpers with shared implementations and updates readSource to route through requireFile + stripComments.

scripts/gen-token-index.js

gen-token-reference.jsUse shared stripComments/readValue/requireFile +2/-19

Use shared stripComments/readValue/requireFile

• Removes local readValue and replaces manual file existence checks with requireFile + stripComments.

scripts/gen-token-reference.js

parse.jsAdd shared parsing + file IO helper module +95/-0

Add shared parsing + file IO helper module

• Introduces a shared helper module exporting strip/mask comment/string utilities, readValue for CSS custom properties, and readFile/requireFile for consistent script IO behavior.

scripts/lib/parse.js

version-sync.jsReuse shared readFile helper +2/-1

Reuse shared readFile helper

• Switches version-sync.js to use scripts/lib/parse.js readFile for consistent repo-root file reading behavior.

scripts/version-sync.js

Tests (3) +333 / -10
token-editing.test.jsAdd component tests for SliderRow and TokenRow +150/-0

Add component tests for SliderRow and TokenRow

• Introduces Vitest + Testing Library coverage for the token-editing primitives, validating bind behavior, change callbacks, and reset flows (including raw CSS mode for SliderRow).

configurator/tests-components/token-editing.test.js

persistence.test.jsAdd persistence seam tests for standalone and WP modes +169/-0

Add persistence seam tests for standalone and WP modes

• Adds Vitest coverage for loadInitialOverrides/saveOverrides across standalone (hash/localStorage) and embedded WP REST paths, including error cases and embedding detection helpers.

configurator/tests/persistence.test.js

coverage.test.jsMigrate selector coverage test from Playwright to node:test +14/-10

Migrate selector coverage test from Playwright to node:test

• Reworks the selector inventory coverage test to run under node:test with assert, removing Playwright-specific APIs while keeping the same coverage assertions.

tests/coverage.test.js

Documentation (3) +6 / -4
CONTRIBUTING.mdUpdate coverage test filename reference +1/-1

Update coverage test filename reference

• Renames the referenced selector coverage test from coverage.spec.js to coverage.test.js to match the new test file name.

CONTRIBUTING.md

README.mdClarify @layer ordering and reorder link snippet +4/-2

Clarify @layer ordering and reorder link snippet

• Explains that @layer order (declared in core/layers.css) governs cascade, and reorders the example <link> list to mirror that order for readability.

README.md

llm-guide.mdMark utilities.css as staged/inactive +1/-1

Mark utilities.css as staged/inactive

• Clarifies that optional/utilities.css is staged/commented out and not active yet.

docs/llm-guide.md

Other (2) +3 / -4
package.jsonRemove unused motion dependency +1/-2

Remove unused motion dependency

• Drops the unused motion package from configurator dependencies, leaving lucide-svelte and fflate unchanged.

configurator/package.json

package.jsonRun selector coverage test under node:test unit suite +2/-2

Run selector coverage test under node:test unit suite

• Adds tests/coverage.test.js to pretest and test:unit commands so it runs under node --test instead of Playwright.

package.json

@coderabbitai coderabbitai Bot added the codex label Jul 2, 2026
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Qodo Logo

Comment-only changes across core/*.css, no selector/property/value diffs
(verified: npm run build's badge-optimal.json is byte-identical).

- SL-001: cross-link the duplicated dark/light OKLCH clamp() derivation
  formula between core/tokens.css:364-373 (inside light-dark()) and
  core/themes.css's SECTION-LEVEL THEMING block (flat re-declaration for
  [data-theme] overrides on non-:root elements). Left unmerged per the
  audit's open question - themes.css genuinely needs flat values outside
  light-dark(), so this may be intentional; deferred to the maintainer.
- SL-002: doc-comment above .sf-clickable-parent warning against applying
  it to large (100+ node) grids, since its selectors combine multiple
  :has() branches.
- SL-003: strengthen --sf-is-dark's "internal, don't set directly"
  comment to be unmistakable against the immediately-following
  --sf-is-active/-current/-pressed/-open block's "public hooks" comment -
  same --sf-is-* naming pattern, opposite contract. Grepped every
  var(--sf-is-dark) read site first to confirm nothing external sets it.
- SL-004: add short section labels to core/layout.css's 11 previously
  unlabeled blank-line section dividers, matching sibling files' style.
- SL-005: consolidate the "var() not allowed inside @container" note
  (previously stated at only 1 of 4 @container sites) into one comment
  near the top of core/layout.css.

Adding SL-004's /* Section */ label directly above .sf-section shadowed
gen-api-index.js's fallback-to-previous-description logic for the
uncommented .sf-section--guttered sitting in the same section (its
description regenerated as the literal string "Section" instead of its
existing curated text). Fixed by promoting that description into
docs/token-annotations.json's classes overlay - the durable, intended
mechanism for exactly this - rather than removing the section label.

Verified: lint:css, build, test:unit, check:llm-guide, audit:check all
pass; docs/api-index.json, docs/api-index.md, and the configurator's
generated data files are byte-identical to base after the fix.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
scripts/lib/parse.js (1)

70-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding unit tests for this new shared module.

readFile/requireFile (and the other helpers above) are now the single source of truth for parsing/loading logic reused across 6 generator/audit scripts, and the PR relies on "byte-identical docs output" as its correctness signal. A small node:test suite covering stripComments vs maskComments offset preservation, readValue's paren-depth handling, and the readFile/requireFile existence-check divergence would guard this shared surface against regressions that a manual diff-check might miss.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/lib/parse.js` around lines 70 - 95, Add unit tests for the shared
helpers in parse.js, since they now back multiple generator/audit scripts and
the docs diff relies on exact output. Cover the key behaviors in stripComments
and maskComments for offset preservation, readValue for parenthesis-depth
parsing, and readFile versus requireFile for the missing-file behavior
difference. Use the helper names themselves so the tests clearly guard this
shared surface against regressions.
configurator/src/lib/codec.ts (1)

108-110: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

CODEC_VERSION/SHARE_PARAM constants defined but never used internally.

encode/decode still hardcode the version byte 2 (Line 109, Line 120) instead of CODEC_VERSION, and readShareFromHash/buildShareUrl hardcode the "c=" param (Line 260, Line 272) instead of SHARE_PARAM. If either constant is ever bumped without updating all the literal occurrences (including the c= check in persistence.ts), encode/decode or share-link parsing will silently desync.

♻️ Proposed fix
-  out[0] = 2;
+  out[0] = CODEC_VERSION;
...
-  if (rawBytes[0] !== 2) {
-    console.warn(`[codec] unknown config-code version ${rawBytes[0]} (expected 2); ignoring.`);
+  if (rawBytes[0] !== CODEC_VERSION) {
+    console.warn(`[codec] unknown config-code version ${rawBytes[0]} (expected ${CODEC_VERSION}); ignoring.`);
     return {};
   }
...
-  const match = trimmed.match(/[#&]?c=([^&]+)/);
+  const match = trimmed.match(new RegExp(`[#&]?${SHARE_PARAM}=([^&]+)`));
...
-  url.hash = code ? `c=${code}` : "";
+  url.hash = code ? `${SHARE_PARAM}=${code}` : "";

(Constants would need to be moved above their first use, or declared via let/hoisted const at module top.)

Also applies to: 120-123, 260-260, 272-272, 281-282

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/src/lib/codec.ts` around lines 108 - 110, The codec helpers are
still hardcoding protocol literals instead of using the shared constants, so
update the version byte handling in encode/decode to use CODEC_VERSION and
replace the `"c="` share-link parameter checks/building in readShareFromHash,
buildShareUrl, and the related persistence parsing with SHARE_PARAM. Make sure
the constants are defined in a scope accessible to those functions in codec.ts
and the corresponding parser in persistence.ts so all version/parameter
references stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@configurator/src/lib/codec.ts`:
- Around line 108-110: The codec helpers are still hardcoding protocol literals
instead of using the shared constants, so update the version byte handling in
encode/decode to use CODEC_VERSION and replace the `"c="` share-link parameter
checks/building in readShareFromHash, buildShareUrl, and the related persistence
parsing with SHARE_PARAM. Make sure the constants are defined in a scope
accessible to those functions in codec.ts and the corresponding parser in
persistence.ts so all version/parameter references stay in sync.

In `@scripts/lib/parse.js`:
- Around line 70-95: Add unit tests for the shared helpers in parse.js, since
they now back multiple generator/audit scripts and the docs diff relies on exact
output. Cover the key behaviors in stripComments and maskComments for offset
preservation, readValue for parenthesis-depth parsing, and readFile versus
requireFile for the missing-file behavior difference. Use the helper names
themselves so the tests clearly guard this shared surface against regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bfe54e0e-d584-47e0-8211-0dd956efaff3

📥 Commits

Reviewing files that changed from the base of the PR and between e81c36e and c839ae9.

⛔ Files ignored due to path filters (1)
  • configurator/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (21)
  • CONTRIBUTING.md
  • README.md
  • configurator/package.json
  • configurator/src/App.svelte
  • configurator/src/components/panels/ExportPanel.svelte
  • configurator/src/components/shell/PreviewPanel.svelte
  • configurator/src/components/shell/StudioHeader.svelte
  • configurator/src/lib/codec.ts
  • configurator/src/lib/persistence.ts
  • configurator/tests-components/token-editing.test.js
  • configurator/tests/persistence.test.js
  • docs/llm-guide.md
  • package.json
  • scripts/audit.js
  • scripts/check-macro-catalog.js
  • scripts/gen-api-index.js
  • scripts/gen-token-index.js
  • scripts/gen-token-reference.js
  • scripts/lib/parse.js
  • scripts/version-sync.js
  • tests/coverage.test.js

claude added 4 commits July 2, 2026 06:12
- codec.ts: encode/decode now reference CODEC_VERSION instead of a
  hardcoded 2, and readShareFromHash/buildShareUrl/readShareFromHashIfPresent
  reference SHARE_PARAM instead of a hardcoded "c=" - previously the
  exported constants existed but every call site hardcoded the literal,
  so bumping either constant would have silently desynced encode/decode
  or share-link parsing. Moved both consts above their first use.
- scripts/lib/parse.js: add tests/parse-lib.test.js covering
  stripComments/maskComments offset preservation, readValue's
  paren-depth handling (including nested clamp()/oklch() shapes), and
  the readFile-vs-requireFile missing-file divergence - the shared
  surface backing 6 generator scripts previously had no direct test,
  only the "docs output is byte-identical" verification a manual diff
  could miss.

Verified: tsc, svelte-check, configurator unit suite (72 tests) unchanged,
root unit suite now 44 tests (was 31), lint:css/build/check:llm-guide/
check:version all pass.
core/themes.css's cross-link to core/tokens.css cited a specific line
range (364-373) that will drift as either file is edited independently.
Replaced with a stable text anchor (the section's own comment text) to
search for instead, matching how the tokens.css side already referenced
themes.css by section name (SECTION-LEVEL THEMING) rather than by line
number. Caught by Qodo's review on PR 475.
…r commit

A stray 'rm -rf badges' before rebuilding removed the tracked
badge-optimal.json along with the gitignored bundle files, and the
subsequent commit picked up the deletion. Restoring it unchanged - no
content difference, it was regenerated identically by npm run build.
…033)

- SL-014: add the 12 previously-undocumented npm scripts to CLAUDE.md's
  Key scripts table, prioritizing the CI-gate scripts (check:macros,
  check:registry, audit:check, lint:css).
- SL-028: add tests/README.md documenting the *.spec.js (Playwright) vs
  *.test.js (node:test) convention, referencing SL-027 as the motivating
  example of what happens when it's unenforced.
- SL-031: retitle docs/migration.md's stale "0.6.25 -> next" header to
  "0.6.25 -> 0.6.26" (traced via CHANGELOG.md to the release that
  actually shipped the base-alias removal it documents), matching the
  versioned-header style used elsewhere in the same file.
- SL-032: document the tier1-p2/p7/p8/p10 numbering gaps (no P1, P3-P6,
  P9 files) in tests/README.md plus a one-line pointer in each of the 4
  files - they trace back to the tier-1-color-fallback feature's
  original property enumeration; not renumbered.
- SL-033: extract tests/render-helpers.js (BUNDLE path + renderWithBundle
  helper) after diffing all 8 page.setContent call sites individually -
  5 files (a11y-patterns, layout, macros, states-full, typography) had
  near-identical setup() functions differing only in viewport/margin/
  extra-stylesheet, now parameterized through one shared function;
  layers.spec.js/print.spec.js/container-queries.spec.js only shared the
  BUNDLE path constant (their setContent bodies are genuinely per-test or
  differently-shaped) and were left otherwise untouched, per the plan's
  explicit caution not to force uniformity where the originals differ.

Verified with real browser execution (not just --list): all 5 refactored
setup()-using spec files plus layers/print/container-queries ran under a
temporary local executablePath override (this sandbox's pinned Playwright
browser build isn't pre-installed) - 150/150 tests pass unchanged.
lint:css, build, test:unit (31/31), check:llm-guide, check:version all
pass; the temporary config was not committed.
claude and others added 13 commits July 2, 2026 06:29
…, 012, 013)

- SL-006 (highest-risk item in this wave): split gen-api-index.js's 829
  lines along its existing section-comment boundaries into
  scripts/lib/api-index/extract.js (annotations, previous-index reading,
  banner/comment parsing, bundle mapping, token/class extraction and
  merging) and render.js (the Markdown companion). gen-api-index.js is
  now just path constants, the write-time regression guard, and
  orchestration in main(). One small behavior-neutral cleanup along the
  way: replaced the hoisted-closure `bundlesFor_` mutable-variable hack
  with a normal parameter, since extractClassesFromFile now takes
  bundlesFor directly. No logic changed otherwise - verified by diffing
  docs/api-index.json, docs/api-index.md, docs/token-index.json, and
  token-registry.json byte-for-byte against a pre-refactor snapshot
  (empty diff on all four, both from a single `node
  scripts/gen-api-index.js` run and from the full `npm run docs`
  pipeline).
- SL-010 + SL-012: add regression guards before the three
  fs.writeFileSync sites that downstream tooling trusts
  (gen-api-index.js, gen-token-registry.js, gen-token-index.js) - a
  >20% entry-count drop (or, for the append-only token-registry.json,
  any drop at all) now throws instead of silently writing a truncated
  artifact. No new dependency; hand-rolled against each file's existing
  "read the previous version" logic.
- SL-008/009/011/013: new scripts/README.md documenting the
  intentionally-unenforced error-handling convention, the hand-rolled
  CLI flag parsing, the 3 environment variables the scripts read
  (SLASHED_ROOT, GITHUB_REF/GITHUB_REF_NAME, GITHUB_BASE_REF), and the
  execSync(shell:true) pattern in check-artifacts.js/changelog-release.js
  (config-sourced today, not attacker input, but worth flagging for
  future artifacts.json entries).

Verified: npm run docs before/after diff empty on every generated
artifact; check:macros, check:registry, check:llm-guide, audit:check,
lint:css, build, test:unit (44/44) all pass.
- check:macros was labeled a CI gate; verified against
  .github/workflows/ci.yml directly and it isn't wired into any job
  (the audit's own SL-014 finding had asserted it was — that
  assumption was wrong). Corrected the label rather than adding it to
  CI, since wiring in a new gate is a separate decision with its own
  blast radius.
- test:unit's new row didn't mention it needs badges/ built first;
  hit this myself moments earlier while verifying PR3 (5 spurious test
  failures from a stale badges/ dir). CI works around it by downloading
  the badges artifact from the build job before this step; a clean
  local checkout needs npm run build first.
…ared limit constants

SL-017/024: replace `any` at codec.ts's 7 registry/options parameters and
its two consumers (App.svelte, CheatsheetPanel.svelte) with new TokenRegistry,
ApiIndex, SlashedClass and DecodeOptions interfaces in types.ts, so malformed
JSON or option shapes are caught at compile time instead of silently
no-op'ing at runtime.

SL-026: document why MAX_VALUE_BYTES and MAX_ID share the same numeric
value (65535) despite being independent limits, so a future edit doesn't
assume they're the same constant in disguise.
…re-link readers

readShareFromHash()/readShareFromHashIfPresent() were typed to accept
DecodeOptions.sanitize but always forced sanitizeValue internally (a
CSS-injection safeguard that must not be caller-overridable) and silently
discarded any options.sanitize passed in. Narrow their accepted options to
a new ShareOptions type (isKnown only) so the signature matches actual
behavior instead of advertising a knob that does nothing.
…e-svelte, document preview singleton

SL-019: reuse savedThemes.ts's isStringRecord() shape guard in
persistence.ts's loadInitialOverrides() instead of trusting
JSON.parse(local) as Record<string,string> unchecked — malformed or
wrong-shaped localStorage now falls back to {} instead of passing through.

SL-021: verified already resolved (no motion dependency or imports remain).

SL-022: migrate lucide-svelte -> @lucide/svelte across all 9 importing
files (import-source only, no icon renames needed — verified via tsc/
svelte-check/build after the full migration).

SL-025: document why previewResolver.svelte.ts's previewVersion is a
deliberate module-level singleton rather than component-scoped state.

SL-018 and SL-015 were investigated but required no changes: SL-018's
'error' saveState and its StudioHeader render branch were already fully
implemented in an earlier batch; SL-015's premise (two competing
`overrides` membership idioms to unify) doesn't hold in this codebase —
only the `in` idiom is used, consistently, across ~120 call sites in ~15
files, well beyond the ~15-site estimate in the remediation plan.
…verride changes

SL-020: PreviewPanel.svelte's two $effects that write to the preview
iframe(s) and call bumpPreviewVersion() re-run on every overrides tick —
e.g. every input event while dragging a slider, often several times per
animation frame. Deferred the DOM-writing work (style/attribute mutation,
font injection, resolver cache invalidation) inside each effect to a
requestAnimationFrame callback that captures the latest values, cancelling
any not-yet-fired frame from a superseded run via the effect's cleanup.
This collapses a same-frame burst down to one apply using the last state,
instead of one apply per keystroke.

Reactive dependency tracking is unaffected — overrides/previewTheme/
loadCount/etc. are still read synchronously at the top of each effect, so
Svelte's re-run triggering is unchanged; only the actual DOM writes are
deferred. previewResolver.svelte.ts's bumpPreviewVersion() itself is
untouched, preserving its existing "pure write, no reactive read"
invariant.

Verified via a manual rapid-drag simulation against a real built preview
server (40 input events fired faster than one per frame): zero console/
page errors (no effect_update_depth loop), and the preview iframe's
applied CSS exactly matched the last dragged value with no dropped
trailing frame.
…it-mode effect when inactive

Qodo review on PR6 flagged two real, pre-existing (not introduced by the
rAF-coalescing itself) inefficiencies in the two effects this PR touches:

1. The single-iframe effect called bumpPreviewVersion() explicitly right
   after registerPreviewDoc(doc), but registerPreviewDoc() already bumps
   internally on every path (both its activeDoc-unchanged early-return and
   its replace-doc branch) — the explicit call double-bumped
   previewVersion and double-cleared resolveCache on every apply.

2. The split-mode effect unconditionally scheduled an rAF, computed CSS,
   and bumped previewVersion even when splitMode is false and no split
   iframes exist in the DOM (they're gated behind {#if splitMode}),
   making every apply pure overhead outside split mode.

Fixed both: dropped the explicit bump in the single-iframe effect, added
an early return on !splitMode in the split effect, and made its trailing
bump conditional (only fires when the dark pane updated without the light
pane also updating, since registerPreviewDoc's internal bump already
covers the light-pane case) so consumers are still notified exactly once
whenever a split pane actually changed.

Re-verified via the same rapid-drag simulation as the original PR6
commit, now covering both single- and split-preview modes: zero console/
page errors, and both modes' applied CSS exactly matched the last
dragged value.
docs: CSS source cleanup — comment-only fixes (SL-001..005)
…-hygiene

docs: docs & test hygiene remainder (SL-014, 028, 031, 032, 033)
…ardening

refactor(scripts): build script hardening (SL-006, 008-013)
refactor(configurator): type codec.ts and its call sites (SL-016/017/024/026)
…ebounce

perf(configurator): rAF-coalesce preview iframe writes during rapid drags (SL-020)
PR4 (codec.ts types) and PR6 (preview debounce) merged into the
integration branch after this branch diverged, both touching import
blocks adjacent to PR5's lucide-svelte migration in App.svelte and
CheatsheetPanel.svelte. Resolved by keeping both sides: the
@lucide/svelte import source (PR5) plus the ApiIndex/SlashedClass type
imports (PR4). PreviewPanel.svelte's lucide import (PR5) and its
rAF-coalescing effect bodies (PR6) merged cleanly with no conflict.

Verified post-merge: tsc/svelte-check clean, 73/73 unit tests + 17/17
component tests pass, full vite build succeeds.
jackgranatowski and others added 4 commits July 2, 2026 14:11
…tor-ux

refactor(configurator): shape-guard localStorage, migrate lucide-svelte (SL-015/018/019/021/022/025)
…s save-error state

SL-023 continuation, the last item in the remediation plan's PR7. Two
real gaps remained after PR4/PR5/PR6 merged and the earlier batch's
persistence.ts/SliderRow/TokenRow coverage:

- previewResolver.svelte.ts had zero test coverage. Added tests/previewResolver.test.js
  covering registerPreviewDoc/getActiveTheme, resolveColor/resolveColorForTheme's
  per-expression and per-theme caching, resolveRgb/resolveBackground's
  no-preview-doc fallbacks, and — closing the loop on PR6's SL-020
  rAF-coalescing — that bumpPreviewVersion() reliably invalidates the
  cache and is safe to call repeatedly (a pure counter, no reactive
  loop). Uses a real <iframe>'s contentDocument rather than a detached
  document, since jsdom's getComputedStyle only resolves colors on
  documents with a defaultView — matching how PreviewPanel.svelte
  registers a real iframe in production.

- StudioHeader's 'error' saveState (added by SL-018 in an earlier
  batch) had no test coverage of its own. Added a save-state describe
  block to tests-components/header.test.js covering all four states
  (idle/saving/saved/error), specifically that a failed save surfaces
  a distinct title/label from a fresh save and stays clickable to
  retry, while an in-progress retry is still correctly disabled.
  Corrected the file's baseProps fixture, which was missing
  hasPendingChanges/saveState/onSave (present since SL-018 but never
  added to this test file).

Verified: npm run test:unit (89/89), npm run test:components (23/23),
npm run test (112/112 combined), npx tsc --noEmit, npx svelte-check
(0 errors/warnings), npm run build, and a real-browser e2e smoke pass
against a built preview server (5/6 — the 1 failure is the same
pre-existing /favicon.ico 404 noted in PR5/PR6, unrelated).
…est from environment

Qodo review on PR7 flagged two real issues in tests/previewResolver.test.js:

1. makePreviewDoc() appended a new <iframe> to document.body on every
   call with no teardown — registerPreviewDoc() only cleans up the
   resolver's internal probe elements, not the test-created iframes
   themselves, so they accumulated across the file's 16+ calls. Added
   an afterEach that removes every iframe makePreviewDoc() created and
   unregisters the preview doc.

2. The canvas-degrades-to-null test asserted resolveRgb('red') returns
   null based on this sandbox's jsdom lacking the optional `canvas` npm
   package, rather than the documented contract — if canvas support
   ever became available the test would start failing for an unrelated
   reason. Replaced with an explicit getContext('2d') stub, plus a new
   companion test asserting the positive path (a real 2D context
   available) returns an [r,g,b] triple.

   Fixing this surfaced a real subtlety Qodo's own suggested fix would
   have missed: each iframe is a separate jsdom realm with its own
   HTMLCanvasElement constructor, so a canvas created via
   activeDoc.createElement('canvas') is an instance of
   doc.defaultView.HTMLCanvasElement, not the top-level test file's
   global HTMLCanvasElement — spying on the wrong one is a silent no-op
   (verified: the positive-path test failed until the spy target was
   corrected to the iframe's own realm).

Verified: npm run test:unit (90/90, up from 89 with the new positive-
path test), npx tsc --noEmit, npx svelte-check (0 errors/warnings), and
a standalone check confirming zero leftover <iframe> elements in
document.body after the full previewResolver suite runs.
…rage

test(configurator): cover previewResolver.svelte.ts and save-error state (SL-023, PR7)
@coderabbitai coderabbitai Bot removed the codex label Jul 2, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@configurator/tests-components/header.test.js`:
- Around line 73-77: The saved-state test in StudioHeader is overstating what it
verifies because it still looks for the same title as the idle state. Update the
`saved` case in `header.test.js` to either assert a truly different title on the
button rendered by `StudioHeader`, or rename the test so it only claims to check
the label text. Use the existing `saved` and idle tests as a reference and keep
the assertion aligned with the actual behavior of `screen.getByTitle` and the
button text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2cd408af-332a-4a75-84de-5bfa400066d7

📥 Commits

Reviewing files that changed from the base of the PR and between c839ae9 and 2dcc34f.

⛔ Files ignored due to path filters (1)
  • configurator/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (47)
  • CLAUDE.md
  • configurator/package.json
  • configurator/src/App.svelte
  • configurator/src/components/DomainPanel.svelte
  • configurator/src/components/panels/CheatsheetPanel.svelte
  • configurator/src/components/panels/ExportPanel.svelte
  • configurator/src/components/panels/HomePanel.svelte
  • configurator/src/components/panels/ThemesPanel.svelte
  • configurator/src/components/shell/PreviewPanel.svelte
  • configurator/src/components/shell/SidebarNav.svelte
  • configurator/src/components/shell/StudioHeader.svelte
  • configurator/src/lib/codec.ts
  • configurator/src/lib/persistence.ts
  • configurator/src/lib/previewResolver.svelte.ts
  • configurator/src/lib/savedThemes.ts
  • configurator/src/types.ts
  • configurator/tests-components/header.test.js
  • configurator/tests/persistence.test.js
  • configurator/tests/previewResolver.test.js
  • core/accessibility.css
  • core/layout.css
  • core/themes.css
  • core/tokens.css
  • docs/migration.md
  • docs/token-annotations.json
  • package.json
  • scripts/README.md
  • scripts/gen-api-index.js
  • scripts/gen-token-index.js
  • scripts/gen-token-registry.js
  • scripts/lib/api-index/extract.js
  • scripts/lib/api-index/render.js
  • tests/README.md
  • tests/a11y-patterns.spec.js
  • tests/container-queries.spec.js
  • tests/layers.spec.js
  • tests/layout.spec.js
  • tests/macros.spec.js
  • tests/parse-lib.test.js
  • tests/print.spec.js
  • tests/render-helpers.js
  • tests/states-full.spec.js
  • tests/tier1-p10-contrast.test.js
  • tests/tier1-p2-coverage.test.js
  • tests/tier1-p7-oldengine.test.js
  • tests/tier1-p8-modern.spec.js
  • tests/typography.spec.js
✅ Files skipped from review due to trivial changes (16)
  • configurator/src/components/DomainPanel.svelte
  • tests/tier1-p7-oldengine.test.js
  • configurator/src/components/panels/HomePanel.svelte
  • tests/README.md
  • tests/tier1-p8-modern.spec.js
  • core/themes.css
  • tests/tier1-p2-coverage.test.js
  • core/accessibility.css
  • configurator/src/lib/previewResolver.svelte.ts
  • scripts/README.md
  • docs/migration.md
  • docs/token-annotations.json
  • core/tokens.css
  • core/layout.css
  • tests/tier1-p10-contrast.test.js
  • CLAUDE.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • configurator/src/components/panels/ExportPanel.svelte
  • configurator/src/App.svelte
  • configurator/src/components/shell/StudioHeader.svelte
  • configurator/src/lib/persistence.ts
  • configurator/tests/persistence.test.js
  • package.json
  • configurator/src/lib/codec.ts

Comment on lines +73 to +77
test('saved: button reads "Saved" with a distinct title from a fresh, unattempted save', () => {
render(StudioHeader, { props: { ...baseProps, hasPendingChanges: false, saveState: 'saved' } });
const btn = screen.getByTitle('No unsaved changes');
expect(btn).toHaveTextContent('Saved');
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test name overstates its coverage: "saved" and "idle" titles are actually identical.

This test's description says the "saved" title is "distinct... from a fresh, unattempted save," but it queries getByTitle('No unsaved changes') — the exact same title text used by the idle test (Line 56). No assertion here actually verifies title distinctness between the two states; only text content ("Saved" vs "Save") is distinguished. Either fix the test to check a genuinely distinct title, or rename it to reflect that it only asserts label text.

💡 Suggested fix (if titles should differ) or reword test name
-    test('saved: button reads "Saved" with a distinct title from a fresh, unattempted save', () => {
-      render(StudioHeader, { props: { ...baseProps, hasPendingChanges: false, saveState: 'saved' } });
-      const btn = screen.getByTitle('No unsaved changes');
-      expect(btn).toHaveTextContent('Saved');
-    });
+    test('saved: button reads "Saved" (title matches the idle "no pending changes" state)', () => {
+      render(StudioHeader, { props: { ...baseProps, hasPendingChanges: false, saveState: 'saved' } });
+      const btn = screen.getByTitle('No unsaved changes');
+      expect(btn).toHaveTextContent('Saved');
+    });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('saved: button reads "Saved" with a distinct title from a fresh, unattempted save', () => {
render(StudioHeader, { props: { ...baseProps, hasPendingChanges: false, saveState: 'saved' } });
const btn = screen.getByTitle('No unsaved changes');
expect(btn).toHaveTextContent('Saved');
});
test('saved: button reads "Saved" (title matches the idle "no pending changes" state)', () => {
render(StudioHeader, { props: { ...baseProps, hasPendingChanges: false, saveState: 'saved' } });
const btn = screen.getByTitle('No unsaved changes');
expect(btn).toHaveTextContent('Saved');
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@configurator/tests-components/header.test.js` around lines 73 - 77, The
saved-state test in StudioHeader is overstating what it verifies because it
still looks for the same title as the idle state. Update the `saved` case in
`header.test.js` to either assert a truly different title on the button rendered
by `StudioHeader`, or rename the test so it only claims to check the label text.
Use the existing `saved` and idle tests as a reference and keep the assertion
aligned with the actual behavior of `screen.getByTitle` and the button text.

@jackgranatowski
jackgranatowski merged commit 5ab644b into main Jul 2, 2026
13 checks passed
jackgranatowski added a commit that referenced this pull request Jul 2, 2026
…le-claim

test(configurator): fix header.test.js saved-state test claim (CodeRabbit finding on #474)
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.

2 participants