utils/web_ui
Krzysztof kuhy Rudnicki 0e78c8b752 ts_core + web_ui: latest stable, pnpm 11, and a quarantine-aware gate
Finishes the last two of the eleven manifests in this repo. Exact-pins every
devDependency and takes three majors -- @testing-library/jest-dom 6 -> 7,
jsdom 29 -> 30, pnpm 10.13.1 -> 11.24.0 -- with lint and coverage green in
both (ts_core 42/42, web_ui 102/102, 100% branches).

**TypeScript stays at 6.0.3, and this is the campaign's first real
unsatisfiable conflict.** TS 7.0.2 is latest, but typescript-eslint 8.68.0 --
itself the latest release -- declares `typescript: ">=4.8.4 <6.1.0"` and throws
"does not support TS 7.0" as eslint loads its config. There is no 9.x. Taking
TS 7 would mean dropping every type-aware lint rule from both packages, so the
compiler waits for the linter. Recorded as a `transitive:` allowlist entry,
which is a predicate rather than a date: the gate re-checks the blocker each
run and the entry clears itself when upstream ships support. First use of the
allowlist, and the loud banner now prints on every run here.

**pnpm 11 also made the gate wrong, and the gate was fixed rather than pnpm.**
pnpm 11 quarantines packages published inside a 24h window -- a supply-chain
measure against the period when a compromised publish is live -- and refused
@testing-library/react 16.3.3, published 18h earlier. Reporting a repo as
behind a version its package manager will not install is a finding nobody can
act on: the hook fails, and so does the fix it asks for. `quarantine.py` now
narrows npm's "latest" to the newest release old enough to install, bounded
above by `dist-tags.latest` so a maintenance-line release never outranks the
tag. It fetches the FULL packument (7MB for react, 16MB for typescript) and so
runs only after a finding already exists -- a clean run pays nothing, which a
test asserts by making the fetch raise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CRykabMwPgDzFHcARoqpaK
2026-08-28 13:50:25 +02:00
..
dist Freeze the motion, haptic and sound vocabularies, and ship them as tokens 2026-08-22 17:28:02 +02:00
src Freeze the motion, haptic and sound vocabularies, and ship them as tokens 2026-08-22 17:28:02 +02:00
.gitignore web_ui: shared web token + component layer (Phase 3) 2026-08-15 14:40:45 +02:00
.pre-commit-config.yaml Fix the cap hook in CI, and the re-exports its --all-files run exposed 2026-08-22 15:52:59 +02:00
eslint.config.js web_ui: add the CI + pre-commit gate, and make pnpm lint actually run 2026-08-15 15:08:18 +02:00
package.json ts_core + web_ui: latest stable, pnpm 11, and a quarantine-aware gate 2026-08-28 13:50:25 +02:00
pnpm-lock.yaml ts_core + web_ui: latest stable, pnpm 11, and a quarantine-aware gate 2026-08-28 13:50:25 +02:00
README.md Add the shared markdown-naming gate and migrate utils' own docs 2026-08-24 15:53:12 +02:00
tsconfig.json web_ui: shared web token + component layer (Phase 3) 2026-08-15 14:40:45 +02:00
tsconfig.lint.json web_ui: add the CI + pre-commit gate, and make pnpm lint actually run 2026-08-15 15:08:18 +02:00
vitest.config.ts web_ui: shared web token + component layer (Phase 3) 2026-08-15 14:40:45 +02:00

@kuhyx/web-ui

The shared token + component layer for kuhy's web apps — the web counterpart to ~/utils/design_system (Dart). Owns the frozen palette, the spacing/radius/type scales, and the components that were each reimplemented in two or more repos.

Consumers import the tokens rather than transcribing the token table by hand. Source of truth for the values is ~/utils/unified-design-system/DOCS-tokens.md; this package is its executable form for the web.

Install

// package.json — pnpm only, see "Why the &path: suffix" below.
"dependencies": {
  "@kuhyx/web-ui": "github:kuhyx/utils#web_ui-v0.3.1&path:/web_ui"
}
import "@kuhyx/web-ui/tokens.css";        // once, at the app entry point
import "@kuhyx/web-ui/range-slider.css";  // only if you use RangeSlider

import { RangeSlider, fuzzyMatch, quantileValue } from "@kuhyx/web-ui";

Why the &path:/web_ui suffix

utils is a monorepo with no root package.json, and npm cannot install from a subdirectory of a git repo — github:user/repo#tag clones the root, looks for package.json there, and fails with ENOENT (verified against npm 11.16). pnpm supports the subdirectory via &path:/<dir>, which is why both consumers are pnpm. This is the only difference from the Dart (git: {url, ref, path}) and Python (#subdirectory=) conventions the rest of utils follows.

Why dist/ is committed

Vite does not transpile dependencies, so shipping raw .tsx would force per-repo bundler config on every consumer. The tag carries a prebuilt dist/ (ESM + .d.ts, JSX already compiled, .ts specifiers rewritten to .js), so consumers stay at zero config. pnpm build before tagging, always — a tag whose dist/ is stale ships old code to every consumer with no error.

What's here

Export Notes
tokens.css The :root block: palette, spacing, radius, type, shadows, focus ring. Also sets :focus-visible globally.
RangeSlider Dual-thumb slider over a value distribution, not a linear ramp.
range-slider.css Its styling, entirely in tokens.
quantile.ts helpers nth, clamp01, quantileValue, valueQuantile, fractionFromPointer.
fuzzyMatch Case-insensitive subsequence match.

The categorical ramp

--cat-1 … --cat-6 are for charts, tags and category dots — N mutually distinguishable colours with no inherent meaning. Never use them for semantic state; --success/--warning/--danger own that.

They deliberately leave the warm wedge the rest of the palette sits in, because hue separation is the whole point of a categorical scale. The values were chosen by search under a ≥20 CIE ΔE floor across normal vision and deuteranopia, protanopia and tritanopia, ≥3:1 contrast on both backgrounds, and a monotonic lightness staircase so the ramp survives greyscale. Re-run the proof with python3 ~/utils/unified-design-system/scripts/ramp_check.py — it exits non-zero if an edit breaks any of it. A seventh hue is not available: the best candidate reaches only ΔE 17.9. Group the tail into "other" instead.

fractionFromPointer is deliberately pure geometry: it makes the slider's behaviour testable without layout, which jsdom does not implement.

RangeSlider is keyboard-operable

Both donor copies were pointer-only. The design system requires that every action be reachable with the keyboard alone, and a role="slider" that ignores arrow keys is an ARIA role that lies — so the shared version adds:

Key Effect
← ↓ / → ↑ Step to the next distinct value
PageDown / PageUp Step ten distinct values
Home / End Jump to the distribution's bounds

Pass label and format to get the built-in head (name + value readout); omit them for a bare track when the caller draws its own chrome, as dufs-cloud's SizeRange/DurationRange wrappers do.

Stepping targets the next distinct value, not the next index. Real distributions are lumpy — ~1500 of awesome-mcp-explorer's ~2981 star counts are 0 — and an index step inside a run of equal values moves the index while leaving the value, the thumb and the filter untouched. That reads as a dead arrow key, and jsdom's evenly-spaced fixtures do not catch it.

Development

pnpm install
pnpm test       # vitest
pnpm coverage   # 100% branches/functions/lines/statements, enforced
pnpm lint       # tsc --noEmit && eslint
pnpm build      # tsc -b + copy CSS into dist/

The 100% bar matches Phase 1's design_system: a shared layer two repos depend on does not get to be the least-tested code in the tree.

Releasing

pnpm test && pnpm build
git add web_ui && git commit -m "web_ui: <what changed>"
git tag web_ui-v<major>.<minor>.<patch> && git push origin main --tags

Then bump the tag in each consumer's package.json and re-run pnpm install.