Adds app-wide prop defaults and refines Vue bindings - #275
Open
razorfever wants to merge 10 commits into
Open
Conversation
…-0048)
A consumer sets a defaultable prop once for every instance of a tag —
`applyDefaults({ 'c-text-field': { labelOnTop: true } })` — instead of
repeating it per element; `resetDefaults()` clears it. Later calls merge,
a key set to `undefined` clears it, and mounted elements follow live.
- Only props tagged `@defaultable <built-in>` take part: labelOnTop,
hideDetails, shadow and size on c-text-field, c-select, c-autocomplete
and c-tree-select; itemsPerPage on the three list fields; texts on those
three and c-data-table. Each declares `undefined` in withDefaults and
resolves host attribute → own property → app default → built-in through
the shared useAppDefault helper. An explicit instance value always wins.
- The analyzer derives the manifest default and `csc.defaultable` from the
tag, generates the `AppDefaults` type and the `DEFAULTABLE_PROPS`
runtime allow-list into the tag map, and lints tag, withDefaults,
resolver call and template bindings into agreement.
- Docs: "app default" badge and footnote in the props tables, a new
Customization section with per-flavor code and a live playground, and a
sentence in the three Texts usage docs. Both functions are re-exported
from @cscfi/csc-ui-react.
- Visible changes: unset defaultable props read `undefined` on the element
instead of the built-in, and hosts no longer carry the reflected default
attributes (`size="default"`, `items-per-page="6"`).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t in the parity check Vue sets every declared prop of a csc-ui element as a property on its own (camelizing hyphenated names), while `.prop` forces the literal key, so `:level-labels.prop` wrote a dead `el['level-labels']` and the tree-select demos fell back to "Choose Level 1". The c-data-table selection and expansion examples had the same dead bindings (`get-row-id`, `expanded-content`), so the expansion demo rendered no expanded content. Remove all 52 `.prop` bindings from the 26 Vue canons, reword the four usage.md notes that recommended `:items.prop` / `:value.prop`, and make `check-example-parity.mjs` fail on any `.prop` in a canon. Verified on the production preview: every tree-select header names its level, and the expansion demo expands. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Introduce automated tests for packages/csc-ui: Vitest 5 browser mode on the devcontainer's Playwright Chromium for anything that touches a registered custom element, plus a node project for pure code. Specs compile SFC source through the SFC plugins shared with the build (vite.plugins.ts), are colocated with the component, and are excluded from the published declarations. - src/test/harness.ts: mount/settle/recordEvents/consoleSpy and per-theme-mode visual baselines; fonts pinned to Liberation via fonts.conf so arm64 and x64 rasterise alike - src/test/dist-smoke.spec.ts: the built bundle upgrades every tag, exposes the public API, registers @Property once and shares one Tailwind sheet - src/test/toolchain.node.spec.ts: the Dockerfile's playwright pin equals the installed client - c-button proof spec with light and dark baselines - ADR-0049, glossary "Verification" terms (value control defined), CLAUDE.md testing policy, plan record in _plan/component-tests.md - lint:tokens is strict; `pnpm lint` aggregates the passing guards Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tests Add the repository's first verification workflow: install, build csc-ui and the React wrapper, run the csc-ui lint aggregate (tokens strict, a11y, ramp, chart) and type-check, lint the docs package and its example parity, then run every Vitest project in Playwright Chromium. The contrast audit runs as a report only — its failing pairs are brand colours. Screenshot diffs are uploaded on failure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…elper specs
Conformance suites (src/test/conformance) run the shared contract of a kind
over every tag, enrolled from generated data: all 72 components (upgrade,
no console output, root part, host-attribute fallthrough, adopted sheets),
the 14 value controls (no emission on programmatic value, one interaction →
one update:value + one input with the host value already set, '' and null
read as empty, a plain v-model round trip) and the 6 anchored overlays (open
through the trigger, Escape, light dismiss, focus home, clean removal). The
analyzer now emits VALUE_TAGS into src/tag-name-map.ts for the enrolment.
The API snapshot (src/api-snapshot.node.spec.ts) condenses each tag's
manifest surface into src/components/<tag>/api.snapshot.json plus an entry
snapshot of runtime exports and public types; the strict analyzer keeps the
manifest consistent, the snapshot keeps it unchanged without a reviewed diff.
Three deviations are pinned in the inverse so a fix must shrink the list:
c-pagination emits on mount and on programmatic value change; c-checkbox and
c-switch mount checked from a null v-model ('' casts to true for a
boolean | number | string prop); c-menu drops focus to <body> on Escape with
a slotted c-button trigger. See _plan/component-tests.md.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…u and the peek cap Behaviour specs colocated with the components, seeded from the last three library fixes: c-select's selected-row check, scalar value resolution and highlight seeding on every open path (d54ae8a); c-modal's initial focus after slotted elements upgrade and the ring-free dialog fallback (5e501b9); the peek cap measured on real row boxes, the scrollbar-free menu ending on a half-visible row, and the re-measure a frame after the row set changes (8ef62d1). Six visual baselines per theme mode. Harness: the pointer is parked at the page corner before every test (files share one page, and a pointer left over a control painted its hover state into a baseline); the browser context runs with prefers-reduced-motion so modal keyframes do not gate assertions. Two more deviations pinned in the inverse: c-modal dispatches the value events twice per dismissal and once on a programmatic close (the native close event fires in a later task than the internalClose flag covers), and c-select emits at mount for a slotted <c-option selected>. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…elect and applyDefaults c-autocomplete: content nested in a slotted option renders on the first open and follows later attribute changes (2d3d34b), match marking stays inside the label region with metacharacters taken literally (ADR-0045), change:query per open and keystroke, multiple mode with virtual highlight and select-all, external mode's loading row and committed labels, and the items-per-page peek cap. c-tree-select: the 127-check smoke run of 2026-09-11 as seventeen specs — open paths, level browsing with breadcrumb and step counter, leaf commit and the closed-field format, climbing and descending, reopening at the committed level, return-object, search with paths and marks, select-branch row, disabled branches, clear, peek re-cap and uneven trees. applyDefaults: the six element-level cases from _plan/app-defaults.md. Eight visual baselines. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…uilt package A Vitest browser project in the docs package mounts each of the 122 canon examples with plain @vitejs/plugin-vue and the built @cscfi/csc-ui, then asserts a clean upgrade: no console output or Vue warning, every c-* descendant defined with a shadow root, no hyphenated own property (the dead `.prop` binding class of fault), and the scroll lock released. Fourteen representative canons carry a visual baseline per theme mode. The `<docs>` custom-block plugin moves out of nuxt.config.ts into scripts/ignore-example-docs-blocks.mjs so the Nuxt build and the smoke share it, and the browser options every project shares (Playwright Chromium, reduced motion, pinned fonts, screenshot comparator) live once in vitest.browser.shared.ts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`pnpm test` in csc-ui failed intermittently in CTreeSelect.spec.ts: a Playwright click on the chevron or the clear button did nothing — no error, focus left on the button, panel closed. The event reached its target and bubbled to the document, but none of Vue's listeners on the path ran. The devcontainer's wall clock steps backwards by ~135 ms every few seconds (measured in Node and in Chromium). Vue stamps every native event with `Date.now()` and drops it in each listener attached at or after the stamp (`createInvoker`, the vuejs/vue#6566 guard), so a click landing within a step of a mount was silently discarded. - `src/test/monotonicClock.ts`, installed over `Date.now` by the browser setup: never decreasing, one millisecond past the last reading over a step, then following real time. A node spec pins the arithmetic; a browser spec reproduces the dropped click with a stepping fake clock and shows the shim closing it. - c-tree-select fixture: the block host collapsed to 46 px in the inline-block stage, putting the field's centre on the chevron and pinning the panel and the `search` baseline to 46 px. It now mounts 320 px wide and `open()` asserts the panel opened. Baselines regenerated. - `pnpm type-check` had been red since the shared browser block moved to the repo root: `vue-tsc --build` pins rootDir to the package, so tsconfig.json sets `rootDir: "../.."`; the unhandled-error filter accepts Vitest's `TestError` shape. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Running the suite on a macOS host failed six screenshot comparisons and nothing else: fontconfig has no effect there, so text renders narrower (212 px instead of 246 px panels) with 2–4% glyph differences. Baselines are authored in the devcontainer and compared in CI, both Linux, where `fonts.conf` pins the glyphs (ADR-0049). `matchScreenshotInBothModes` and the example smoke now compare only when `server.platform === 'linux'` and print one notice per file elsewhere; the behaviour assertions around a skipped comparison still run, and `test:update` cannot write a baseline from a non-Linux checkout. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduces a new API for managing app-wide default values for component properties, as defined in ADR-0048.
App-wide Prop Defaults (
applyDefaults/resetDefaults):labelOnTop,size,texts) once per tag, applying them to all instances.undefinedand default attributes (e.g.,size="default") are no longer reflected on the host.@cscfi/csc-ui-react.Vue
.propModifier Removal:.propmodifier on custom element bindings (:attribute.prop) silently broke hyphenated names or was unnecessary, leading to incorrect property assignment.:attribute="value") for custom element properties.