Skip to content

Holistic refactor: modularize core, recycle shared helpers, generate the CSS light twin#47

Draft
ryankiley wants to merge 4 commits into
mainfrom
claude/codebase-refactoring-review-w7p8lt
Draft

Holistic refactor: modularize core, recycle shared helpers, generate the CSS light twin#47
ryankiley wants to merge 4 commits into
mainfrom
claude/codebase-refactoring-review-w7p8lt

Conversation

@ryankiley

Copy link
Copy Markdown
Owner

Structural simplification with no behavior change — both build outputs stay equivalent (dist/tweaks.css verified byte-identical; bundle sizes unchanged at ~18 KB split / ~34 KB single gzipped). Four commits, one concern each:

1. Split core.ts into focused modules

core.ts (1,470 lines) carried five jobs. It's now the public entry (re-exports only), with the implementation split by responsibility: schema.ts (schema/[data-tw] → control meta), controls/basic.ts (the always-loaded controls), panel.ts (tweaks() + panel API), enhance.ts (markup path + auto-run), feedback.ts (toast/hint/toolbar), icons.ts, lazy.ts (dynamic-import map). Pure code moves.

2. Recycle duplicated logic into shared helpers

The slider and interval had drifted-in-parallel copies of the same machinery, now tokenized once in shared.ts:

  • normalizeRange() — the non-finite/inverted/degenerate range guard
  • rangeStep() — one keyboard model (arrows / ⇧-coarse / Page / Home / End) for the slider, both interval handles, and the colour picker's alpha strip
  • overlapsText() — the label/value dodge overlap test
  • EASE_SPRING / EASE_GLIDE — JS-side motion tokens (previously four inline copies of the CSS --tw-ease-spring literal)
  • chevronIcon(cls) — the folder/select chevron declared once
  • interval now rides the shared dragGesture(); the FPS graph and numeric monitor share one strokeSeries() sparkline pen

3. Generate the forced-light CSS twin

The [data-tw-scheme="light"] block was a hand-maintained copy of the light media block, policed by a ~40-line drift checker. The build now derives the twin from the media block (each :not([data-tw-scheme="dark"] …) guard maps to its positive form), so drift is impossible. Guards remain: no hand-written forced-light rules, every media-block rule must be derivable, and a pair floor catches the system dropping out. Output is byte-identical.

4. Table-sync types + state-machinery tests

  • TYPED_META / DATA_VALUE are typed against the public SchemaObject["type"] union — tsc fails the build if the tables and types.ts drift.
  • test/registry.test.mjs cross-checks TYPED_META / DATA_VALUE / the registry / LAZY_IMPORT via minimal fixtures per control type.
  • test/state.test.mjs backfills the previously untested subsystems: persistence round-trip, presets (incl. __proto__ key), undo/redo with redo-branch drop, toJSON/fromJSON path escaping, setMany single-notify batching, gradient normalisation.

Suite: 39 → 52 tests, all green (npm test = build + tsc + jsdom suite).

Deliberately not done (flagged in review, needs a product call)

  • Moving the on-import enhance() auto-run to an opt-in entry (breaking change)
  • Full strict/noImplicitAny TypeScript (large churn; the table typing starts the ratchet)

🤖 Generated with Claude Code

https://claude.ai/code/session_01QjnFzWxPppzdRLgMuHyC4y


Generated by Claude Code

claude added 4 commits July 2, 2026 13:33
core.ts (1,470 lines) carried five jobs: schema derivation, the core control
constructors, the panel factory, markup enhancement, and the feedback chrome.
Pure code moves, no behavior change — core.ts stays the public entry and
re-exports tweaks/enhance/types, so both build outputs are unchanged:

  schema.ts          — metaFor/TYPED_META + the [data-tw] DATA_VALUE parsers
  controls/basic.ts  — slider, toggle, radiogrid, select, buttons, text,
                       number, folder + createControl
  panel.ts           — tweaks() and the panel API
  enhance.ts         — enhance() + the on-load auto-run
  feedback.ts        — toast, hint tooltip, toolbar button factories, copyText
  icons.ts           — the shared inline SVG icons
  lazy.ts            — the dynamic-import map + schema scan

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjnFzWxPppzdRLgMuHyC4y
…nto shared helpers

The slider and interval had drifted-in-parallel copies of the same machinery;
tokenize it once in shared.ts and point every surface at it:

- normalizeRange() — the non-finite/inverted/degenerate range guard the slider
  and interval each hand-rolled (interval keeps its tuple-derived fallbacks
  via the defMin/defMax parameters)
- rangeStep() — one keyboard model (arrows, shift-coarse, Page, Home/End) for
  the slider track, both interval handles, and the colour picker's alpha strip
- overlapsText() — the label/value dodge overlap test, shared per handle
- EASE_SPRING / EASE_GLIDE — the JS-side motion tokens; EASE_SPRING is the
  literal of the CSS --tw-ease-spring, previously duplicated inline at four
  call sites (slider glides, park transition, pill stretch)
- chevronIcon(cls) — the folder and select chevrons were the same glyph
  declared twice
- interval now rides the shared dragGesture() instead of re-implementing
  pointer capture, the single-pointer guard, and the four end paths
- monitor.ts: strokeSeries() — the FPS graph and the numeric monitor shared
  the ring-buffer sparkline pen in two hand-rolled variants

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjnFzWxPppzdRLgMuHyC4y
The [data-tw-scheme="light"] block was a manual copy of the light media block,
policed by a ~40-line drift checker that failed the build when the two diverged.
Author the palette once: build.mjs now derives the attribute-forced twin from
the @media (prefers-color-scheme: light) block — each :not([data-tw-scheme="dark"] …)
:where() guard maps to its positive [data-tw-scheme="light"] … form, emitted
right after the media block so the source-order precedence is unchanged.

The generator keeps three guards: no hand-written forced-light rules may creep
back into src, every rule inside the media block must carry a derivable
:where() dark-guard, and the pair floor (≥3) still catches the twin system
silently dropping out. dist/tweaks.css is byte-identical to the previous
hand-maintained output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjnFzWxPppzdRLgMuHyC4y
Adding a control touches several hand-synced places (TYPED_META, DATA_VALUE,
the registry, LAZY_IMPORT, single.ts). Two new guards pin them together:

- TYPED_META is now typed Record<Exclude<SchemaObject["type"], "button">, …>
  and DATA_VALUE Partial<Record<SchemaObject["type"], …>>, so tsc itself fails
  the build when the tables and the public type union drift.
- test/registry.test.mjs derives a meta from a minimal fixture of every
  declared type (bundled from src in split mode, so LAZY_IMPORT is populated)
  and asserts each resolves to a core-registered or lazily-loadable control,
  that no lazy chunk is orphaned, and that core/lazy registration don't overlap.

test/state.test.mjs backfills the previously uncovered subsystems: persistence
round-trip, presets (including the __proto__ key), undo/redo with redo-branch
drop, toJSON/fromJSON with the JSON-pointer path escaping, setMany
single-notify batching, and gradient value normalisation.

CONTRIBUTING.md updated for the new module map, the generated CSS twin, and
the registry test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjnFzWxPppzdRLgMuHyC4y
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