Fix five defects found by running Motion rather than reading it
The markdown parser was in the eager entry chunk. `manualChunks` in its object form does not leave an unlisted vendor package to Vite's async splitting, so react-markdown was hoisted into the entry even though its only importers are lazy routes — 327.70 kB gzip against a 314 kB baseline, on the one download every route pays for. Naming it as its own chunk puts it back behind the Motion pages and takes the entry to 282.21 kB, below where it was before Motion existed. The starter library could never be improved. Seeding was insert-only, so a deployment seeded in August was frozen on August's wording for ever with no upgrade path short of editing production rows by hand — for a feature whose entire premise is that the library gets better. A second run now refreshes a starter row, but only while it is still ours: `is_system`, `usage_count = 0` and no owner. That is the same condition §7a already enforces on the API, so a template an engagement was cut from is left alone and reported by name rather than silently overwritten. The refresh was not idempotent, and the seed lied about it. `jsonb` does not preserve key order — Postgres sorts keys by length then bytewise — so comparing `JSON.stringify(stored)` against `JSON.stringify(authored)` marked every template as changed on every run, and the seed rewrote nine rows each time while reporting itself clean. Comparison is now canonical. Found by running the seed three times and reading the counts. `motion-overflow-check.mjs` measured less than it claimed. It seeded `pig.sidebar` and `pig.piggy.dock`, neither of which anything reads (the keys are `pig.sidebarOpen` and `pig.piggyDockOpen`), so the layout it pinned was whatever the last run left. Its dark pass set `colorScheme` only, and the appearance preference is stored server-side and adopted after hydration, so the dark pass measured the light palette a beat after first paint. It now rewrites the profile response as `screenshots.mjs` does, asserts the rendered `data-theme`, and fails a page that renders almost no text — a page that throws inside its own body otherwise measures zero overflow and passes. The stage rail rendered "1 templates", in the visible label and in every aria-label. Singular and plural are now both passed. Also normalised `artifact` to `artefact` in the seeded prose, which had drifted American in the playbook. The `artifacts` field key is untouched: FieldsView reads it, and already labels it in British.
This commit is contained in:
@@ -18,12 +18,17 @@
|
||||
import { DEMAND_OPEN_STAGES, DEMAND_STAGE_LABELS, type DemandStage } from '@pig/core';
|
||||
import { cn } from '@/components/ui';
|
||||
|
||||
/** Zero takes the plural, as it does in English: "no templates", "0 templates". */
|
||||
function noun(count: number, [one, many]: readonly [string, string]): string {
|
||||
return count === 1 ? one : many;
|
||||
}
|
||||
|
||||
export function StageRail({
|
||||
counts,
|
||||
coverage,
|
||||
stages = DEMAND_OPEN_STAGES,
|
||||
countLabel = 'engagements',
|
||||
coverageLabel = 'templates',
|
||||
countNoun = ['engagement', 'engagements'],
|
||||
coverageNoun = ['template', 'templates'],
|
||||
activeStage,
|
||||
onSelect,
|
||||
className,
|
||||
@@ -33,8 +38,9 @@ export function StageRail({
|
||||
/** The second figure, if the caller has one — library cover, typically. */
|
||||
coverage?: Partial<Record<DemandStage, number>>;
|
||||
stages?: readonly DemandStage[];
|
||||
countLabel?: string;
|
||||
coverageLabel?: string;
|
||||
/** Singular and plural, because "1 engagements" is read aloud by a screen reader. */
|
||||
countNoun?: readonly [one: string, many: string];
|
||||
coverageNoun?: readonly [one: string, many: string];
|
||||
activeStage?: DemandStage | null;
|
||||
/** Omit to render a read-only rail: a non-interactive button is a trap. */
|
||||
onSelect?: (stage: DemandStage) => void;
|
||||
@@ -61,7 +67,7 @@ export function StageRail({
|
||||
// surface — it is where the motion stops repeating — so it is
|
||||
// called out rather than shown as another grey zero.
|
||||
<span className={cn('nums truncate text-xs', covered === 0 ? 'text-warning' : 'text-muted')}>
|
||||
{covered === 0 ? `No ${coverageLabel}` : `${covered} ${coverageLabel}`}
|
||||
{covered === 0 ? `No ${coverageNoun[1]}` : `${covered} ${noun(covered, coverageNoun)}`}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
@@ -79,13 +85,13 @@ export function StageRail({
|
||||
type="button"
|
||||
onClick={() => onSelect(stage)}
|
||||
aria-pressed={active}
|
||||
aria-label={`${DEMAND_STAGE_LABELS[stage]}: ${count} ${countLabel}`}
|
||||
aria-label={`${DEMAND_STAGE_LABELS[stage]}: ${count} ${noun(count, countNoun)}`}
|
||||
className={cn(shape, 'transition-colors hover:bg-surface-2')}
|
||||
>
|
||||
{body}
|
||||
</button>
|
||||
) : (
|
||||
<div className={shape} aria-label={`${DEMAND_STAGE_LABELS[stage]}: ${count} ${countLabel}`}>
|
||||
<div className={shape} aria-label={`${DEMAND_STAGE_LABELS[stage]}: ${count} ${noun(count, countNoun)}`}>
|
||||
{body}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -314,7 +314,7 @@ export function Engagement() {
|
||||
<StageRail
|
||||
counts={counts}
|
||||
stages={railStages}
|
||||
countLabel="artefacts"
|
||||
countNoun={['artefact', 'artefacts']}
|
||||
activeStage={stageFilter ?? engagement.stage}
|
||||
onSelect={(stage) => setStageFilter((current) => (current === stage ? null : stage))}
|
||||
/>
|
||||
|
||||
@@ -180,7 +180,7 @@ export function MotionLibrary() {
|
||||
|
||||
<StageRail
|
||||
counts={railCounts}
|
||||
countLabel="templates"
|
||||
countNoun={['template', 'templates']}
|
||||
activeStage={filters.stage}
|
||||
onSelect={(stage) =>
|
||||
setParam(setParams, STAGE_PARAM, stage === filters.stage ? '' : stage)
|
||||
|
||||
@@ -44,6 +44,14 @@ export default defineConfig({
|
||||
react: ['react', 'react-dom', 'react-router-dom'],
|
||||
supabase: ['@supabase/supabase-js'],
|
||||
query: ['@tanstack/react-query'],
|
||||
/*
|
||||
* Named explicitly, or the object form of `manualChunks` hoists the
|
||||
* markdown parser into the entry chunk even though the only importers
|
||||
* are lazy routes — measured at 327.70 kB gzip entry with it hoisted
|
||||
* against 314 kB without. As its own chunk it is fetched when a
|
||||
* Motion page is opened and never on any other route.
|
||||
*/
|
||||
markdown: ['react-markdown', 'remark-gfm'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user