diff --git a/.agents/skills/scaffold-code-app/SKILL.md b/.agents/skills/scaffold-code-app/SKILL.md index 62736e9..31d8714 100644 --- a/.agents/skills/scaffold-code-app/SKILL.md +++ b/.agents/skills/scaffold-code-app/SKILL.md @@ -116,6 +116,14 @@ globals into the production compile, yet they must still be type-checked by the This is why the app build never needs `vitest/globals` bolted into `tsconfig.app.json`: the tests get their globals from their own project, and a wrong fixture shape still fails `tsc` at the gate. +**e. Keep the Power Apps Vite plugin (CDN-safe asset base path).** The template's `vite.config.ts` +includes `powerApps()` from `@microsoft/power-apps-vite`. **Leave it in the `plugins` array.** A Code +App is served from the Power Platform CDN at a non-root base path, and this plugin sets the asset base +so Vite's bundled asset URLs and images resolve once deployed. Remove it (or drop the `base`) and the +app's images and hashed assets 404 on the CDN. If a project ever does not use the plugin, set +`base: './'` in `vite.config.ts` instead. Images are referenced by **bundled import from `src/`**, not +from `public/` by absolute path (see the instantiation's "Brand assets and wiring"). + See **AGENTS.md (repo root) → Stack notes** for the exact pins and the test-authoring rules (typed fixtures, one un-mocked smoke render). After this, `npm run verify` runs clean on the empty app, so the first task gate (Task 1) has a working harness to run against rather than being set up live. diff --git a/.claude/skills/scaffold-code-app/SKILL.md b/.claude/skills/scaffold-code-app/SKILL.md index 62736e9..31d8714 100644 --- a/.claude/skills/scaffold-code-app/SKILL.md +++ b/.claude/skills/scaffold-code-app/SKILL.md @@ -116,6 +116,14 @@ globals into the production compile, yet they must still be type-checked by the This is why the app build never needs `vitest/globals` bolted into `tsconfig.app.json`: the tests get their globals from their own project, and a wrong fixture shape still fails `tsc` at the gate. +**e. Keep the Power Apps Vite plugin (CDN-safe asset base path).** The template's `vite.config.ts` +includes `powerApps()` from `@microsoft/power-apps-vite`. **Leave it in the `plugins` array.** A Code +App is served from the Power Platform CDN at a non-root base path, and this plugin sets the asset base +so Vite's bundled asset URLs and images resolve once deployed. Remove it (or drop the `base`) and the +app's images and hashed assets 404 on the CDN. If a project ever does not use the plugin, set +`base: './'` in `vite.config.ts` instead. Images are referenced by **bundled import from `src/`**, not +from `public/` by absolute path (see the instantiation's "Brand assets and wiring"). + See **AGENTS.md (repo root) → Stack notes** for the exact pins and the test-authoring rules (typed fixtures, one un-mocked smoke render). After this, `npm run verify` runs clean on the empty app, so the first task gate (Task 1) has a working harness to run against rather than being set up live. diff --git a/AGENTS.md b/AGENTS.md index de0b992..32e0f6c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,6 +84,12 @@ diverge from its spec. - **Per task:** typecheck + lint (near-instant). **On the data-layer task:** the audit pass (the checklist in tasks.md Task 1). The test suite exists from Task 1's smoke test and peaks at the modal milestone (Task 3). +- **Design-bearing tasks get a visual check (`verify` is necessary, not sufficient).** `tsc`, lint, + and test cannot see a broken hero image, a rating picker whose stars never fill, a monospace input, + or stats rendered as boxes instead of the contract's cluster. On Tasks 2, 3, and 4, **run the app + and look at it** against the conformance checklist (contract §8) and the instantiation before + calling the task done. This is the design analogue of the data-layer audit pass: green `verify` plus + eyes on the rendered surface, not green `verify` alone. - **Commit each task when its gate is green.** After `npm run verify` passes (and the audit pass where the task requires it), commit that task with a short message naming it, before starting the next. The git log is the build record; never bundle two tasks into one commit. diff --git a/specs/design-contract.md b/specs/design-contract.md index c26e58c..23527f3 100644 --- a/specs/design-contract.md +++ b/specs/design-contract.md @@ -352,6 +352,14 @@ one interactive mode and two static read-out modes. It is reusable wherever a qu judgement, score, or progress-out-of-N is shown; an app that has no such concept simply does not use it. The glyph and range are parameters; everything below is fixed. +> **Build it once.** This is **one component** with a `mode` (picker / inline / confirmation), not +> three look-alike blocks of star markup. Every surface that shows marks (the card read-out, the modal +> picker, the success confirmation) renders the *same* component; only `mode`, `value`, and the +> handlers differ. Per-surface bespoke star code is the failure this section exists to prevent: it is +> how the fills drift, how the picker ends up with unfilled outline marks, and how the same rating +> renders three different ways. If you find yourself writing a second set of star classes, stop and +> reuse the first. + Two fills carry meaning and must not be swapped: - **Active fill** = `signal/primary` — used while the user is *choosing* and on the immediate *confirmation*. It is the live signal. @@ -360,13 +368,18 @@ Two fills carry meaning and must not be swapped: **Picker (interactive).** A centred row of N mark-buttons, each `clamp(46px,11vw,60px)`, no fill or border on the button itself, `motion/control` (slight overshoot, selected mark `scale(1.06)`). A -mark is "on" when its index is `<= max(hover, draft)`. On marks: glyph filled `signal/primary`, -stroke `signal/primary`. Off marks: glyph filled `rgba(255,255,255,.04)`, stroke -`rgba(255,255,255,.32)` at `stroke-width:1.4`. Hover/focus previews without committing; click -commits the draft; pointer-leave clears the preview. A live caption sits beneath at a reserved -height (~26px, so nothing shifts): it names the current value, tinted `signal/primary` at the top of -the range and `text/label` below, and is blank at zero. Every mark-button carries an `aria-label` -("3 stars"). +mark is "on" when its index is `<= max(hover, draft)`. On marks: glyph **filled** `signal/primary` +(`fill: #F5400D`, not just a coloured stroke), stroke `signal/primary`. Off marks: glyph filled +`rgba(255,255,255,.04)`, stroke `rgba(255,255,255,.32)` at `stroke-width:1.4`. Hover/focus previews +without committing; click commits the draft; pointer-leave clears the preview. A live caption sits +beneath at a reserved height (~26px, so nothing shifts): it names the current value, tinted +`signal/primary` at the top of the range and `text/label` below, and is blank at zero. Every +mark-button carries an `aria-label` ("3 stars"). + +> The single most common build error here is a picker whose marks are **thin grey outlines that never +> fill** on hover or select. That is wrong: a selected mark is a solid orange star. If your stars look +> like the off state when a rating is chosen, the `fill` is missing or is being applied to a stroke +> only. A picker that does not visibly fill fails conformance. **Inline read-out (settled).** A compact, non-interactive row of small marks (~17px) showing a persisted rating: filled marks `accent/gold`, the remainder `accent/gold-empty`. Pairs with a short @@ -385,6 +398,10 @@ filled marks `signal/primary` up to the chosen value, the remainder `accent/gold `outline:none`, placeholder in `text/faint`. Label above in 14px 700 `text/muted-2`, with an "(optional)" / "(required)" qualifier in `text/faint` 500 where relevant. +> Set `font-family` to `font/ui` (Barlow) explicitly on inputs and textareas. Browsers default a +> `