From f74dd31beac03b77b0f4fe2a5d6c4fe61aca2dce Mon Sep 17 00:00:00 2001 From: Matei Date: Thu, 11 Jun 2026 20:27:53 -0400 Subject: [PATCH] =?UTF-8?q?docs(mapgen-studio):=20Pass-3=20frame=20?= =?UTF-8?q?=E2=80=94=20spacing=20substrate=20root=20cause,=20config=20surf?= =?UTF-8?q?ace,=20console=20split,=20toolbar=20groups,=20no-hardcoded-over?= =?UTF-8?q?rides=20law?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Grounding found the root of 'padding/margins are really bad': an unlayered * {margin:0;padding:0} reset in index.html outranks Tailwind v4's @layer utilities, zeroing every spacing utility app-wide. Frame + 5 strict-valid OpenSpec changes (D0 substrate, D1 config surface, D2 footer consoles, D3 explore toolbar groups, D4 no hard-coded defaults). Co-Authored-By: Claude Fable 5 --- .../mapgen-studio/.interface-design/system.md | 28 ++++ .../pass-3-design-fixes.md | 130 ++++++++++++++++++ .../mapgen-studio-config-surface/design.md | 52 +++++++ .../mapgen-studio-config-surface/proposal.md | 52 +++++++ .../specs/mapgen-studio/spec.md | 46 +++++++ .../mapgen-studio-config-surface/tasks.md | 20 +++ .../proposal.md | 39 ++++++ .../specs/mapgen-studio/spec.md | 31 +++++ .../tasks.md | 17 +++ .../mapgen-studio-footer-consoles/proposal.md | 49 +++++++ .../specs/mapgen-studio/spec.md | 41 ++++++ .../mapgen-studio-footer-consoles/tasks.md | 19 +++ .../proposal.md | 45 ++++++ .../specs/mapgen-studio/spec.md | 21 +++ .../tasks.md | 14 ++ .../proposal.md | 48 +++++++ .../specs/mapgen-studio/spec.md | 28 ++++ .../mapgen-studio-spacing-substrate/tasks.md | 17 +++ 18 files changed, 697 insertions(+) create mode 100644 docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md create mode 100644 openspec/changes/mapgen-studio-config-surface/design.md create mode 100644 openspec/changes/mapgen-studio-config-surface/proposal.md create mode 100644 openspec/changes/mapgen-studio-config-surface/specs/mapgen-studio/spec.md create mode 100644 openspec/changes/mapgen-studio-config-surface/tasks.md create mode 100644 openspec/changes/mapgen-studio-explore-toolbar-groups/proposal.md create mode 100644 openspec/changes/mapgen-studio-explore-toolbar-groups/specs/mapgen-studio/spec.md create mode 100644 openspec/changes/mapgen-studio-explore-toolbar-groups/tasks.md create mode 100644 openspec/changes/mapgen-studio-footer-consoles/proposal.md create mode 100644 openspec/changes/mapgen-studio-footer-consoles/specs/mapgen-studio/spec.md create mode 100644 openspec/changes/mapgen-studio-footer-consoles/tasks.md create mode 100644 openspec/changes/mapgen-studio-no-hardcoded-defaults/proposal.md create mode 100644 openspec/changes/mapgen-studio-no-hardcoded-defaults/specs/mapgen-studio/spec.md create mode 100644 openspec/changes/mapgen-studio-no-hardcoded-defaults/tasks.md create mode 100644 openspec/changes/mapgen-studio-spacing-substrate/proposal.md create mode 100644 openspec/changes/mapgen-studio-spacing-substrate/specs/mapgen-studio/spec.md create mode 100644 openspec/changes/mapgen-studio-spacing-substrate/tasks.md diff --git a/apps/mapgen-studio/.interface-design/system.md b/apps/mapgen-studio/.interface-design/system.md index 6ecedc0ae3..64caf017d5 100644 --- a/apps/mapgen-studio/.interface-design/system.md +++ b/apps/mapgen-studio/.interface-design/system.md @@ -168,3 +168,31 @@ decisions (see `docs/projects/mapgen-studio-redesign/pass-2-design-fixes.md`): - **One Run CTA.** The footer is the run console (seed, auto-run, run, run-in-game); panel-local duplicates are removed. - Control density (button/input/switch/field-row dimensions above) is unchanged. + +## Pass-3 amendment (2026-06-11, user-grounded): spacing substrate + form elevation + console split + +Pass-3 grounding found the spacing scale above had **never rendered**: an +unlayered `* { margin:0; padding:0 }` reset in `index.html` outranks Tailwind +v4's `@layer utilities`, zeroing every padding/margin utility app-wide. Decisions +(see `docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md`): + +- **No unlayered author CSS.** `index.html` may carry only the pre-paint flash + guard (`body` colors); resets belong to Tailwind preflight (layered). Any + future global CSS outside `index.css` must live in a cascade layer. +- **Form surface elevation (config panel):** nesting is expressed by *surfaces, + not indent rules*. Panel (popover) → stage card (card, one recess) → **group + well** (one further recess: `bg-background/40`-class tint, `border-border-subtle`, + rounded, padded). Two surface tiers maximum; deeper nesting differentiates by + eyebrow heading + rhythm only. Arrays ride the same well treatment. `border-l` + indent ladders are removed. +- **Form rhythm (4px base):** 4px within a field block (label/input/help), + 8px between fields, 12px between groups and between stage cards. Owned by the + FORM constant in `rjsfTemplates.tsx`. +- **Footer = two consoles.** A centered **Studio console** (status, last run, + seed, reroll, auto-run, Run) and a right-docked, named **Game console** + (live Civ7 status/sync, autoplay, Run in Game + status, save-deploy chip). + Studio↔game relation chips (Current/Stale/Previous, stale sync ring) live on + the Game console; studio dirty state stays on the Studio console. +- **Explore toolbar groups by target:** VIEW (camera fit, edges overlay) and + DATA (render, space, era, variant, overlay) clusters with eyebrow labels; + the debug toggle is a DATA-list filter and lives on the DATA section header. diff --git a/docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md b/docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md new file mode 100644 index 0000000000..98339c3193 --- /dev/null +++ b/docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md @@ -0,0 +1,130 @@ +# Pass 3 — Spacing substrate, config surface, console split, explore toolbar + +> Frame for the third user-grounded fix wave (2026-06-11, evening). Same +> discipline as Pass 2 (`pass-2-design-fixes.md`): OpenSpec slice per change, +> Graphite stack (never submitted), a change is done only when SEEN in the +> running app. + +## The reframe (perspective pass) + +The user asked for "a full, thoughtful design pass from the ground up" on the +config UI's padding and margins. Grounding in the live page found something +prior passes missed: **the spacing system has never rendered.** + +`index.html` ships an inline, *unlayered* reset — `* { margin: 0; padding: 0 }`. +Tailwind v4 emits every utility inside `@layer utilities`, and unlayered author +styles take cascade priority over ALL layered styles regardless of specificity. +Result: every `p-*`/`px-*`/`py-*`/`m-*` utility in the app computes to **0px**. +Gaps, heights, and widths survive (the reset doesn't set them), which is why the +layout half-works while every surface's internal texture is crushed. The old +Tailwind v3 build emitted unlayered utilities (specificity won, the reset was +harmless); the P1 migration to v4's native cascade layers silently inverted the +cascade. Proven live: deleting the rule in the running page restores 12px row +padding, 10px card padding, padded buttons/inputs/headers app-wide. + +So the highest-leverage move is not redesigning spacing values — it is +**restoring the substrate (D0), then designing on truth (D1–D3)**. Every visual +decision made in Passes 1–2 was tuned against a zero-padding render; expect a +small re-tune wave after D0, owned by the slices that own those surfaces. + +Second bootstrap defect found in the same file: the pre-paint theme script +reads `localStorage["mapgen-studio:theme"]`, but the app persists the +preference under `theme-preference`. The bootstrap reads a key nobody writes — +a light-theme user gets a dark pre-paint flash on every load. Same slice (D0). + +## Hard core (unchanged + new) + +- Behavior parity: run/poll/localStorage/transport semantics untouched. +- Control density tokens (h-7/h-8 controls, 11px data text) unchanged. +- Single `.dark` class theming; tokens only, no raw hex in chrome. +- OpenSpec `--strict` per slice; Graphite-only branch ops; stack never submitted. +- **NEW (user law, 2026-06-11): no hard-coded config overrides anywhere** — not + in app code, not in tests. A literal pipeline-config value duplicating the + recipe's source of truth is a defect to remove on sight (D4). Migration/ + round-trip fixtures that test *user data shapes* (legacy formats, preset + payloads) are not overrides of defaults and remain legitimate, but must not + mirror real default values as expectations. + +## Issues → changes + +| # | Observation (grounded) | Root cause | Change | +|---|---|---|---| +| 1 | All padding/margins "really bad" app-wide | Unlayered `*` reset beats `@layer utilities` | **D0** remove reset from index.html (Tailwind preflight already provides a layered one) | +| 2 | Light users get dark pre-paint flash | Bootstrap reads stale localStorage key | **D0** align bootstrap to `theme-preference` | +| 3 | Config nesting reads as code-indent ladder, not "slate" | depth≥2 groups are heading + `border-l` indent; no surface tiers | **D1** surface-elevation scheme: stage card → recessed group wells; cap at two surface tiers, deeper nesting by type/spacing only | +| 4 | No chunking rhythm in form (fields ≈ groups ≈ sections) | gap-0.5/1/1.5/2 ad hoc, within-group == between-group | **D1** codified form rhythm on the 4px base: 4 within-field / 8 field↔field / 12 group↔group; amend `.interface-design/system.md` | +| 5 | Two studio bars + game controls interleaved in footer | AppFooter grew accretively; no ownership boundary | **D2** split: one centered **Studio console** (status · last run · seed · reroll · auto-run · Run), one right-docked named **Game console** (live status/sync, autoplay, Run in Game + status/retry/diagnostics, save-deploy chip) | +| 6 | Studio↔game state legibility must survive the split | Relation cues exist (stale ring, Current/Stale/Previous chip) but are scattered | **D2** bridge: relation chips live on the Game console, computed against current studio state; studio dirty state stays on the Studio console (Modified + Run ring) | +| 7 | Explore bottom buttons uncategorized, zig-zag layout | View toolbar mixes camera, map-overlay, data, and list-filter controls | **D3** regroup by target: VIEW (fit, edges overlay) / DATA (render, space, era, variant, overlay); consistent label-left/control-right rows | +| 8 | Debug toggle misplaced | `showDebugLayers` filters the DATA *list*, it is not a view control | **D3** move it to the DATA section header (list filter lives with the list) | +| 9 | 330-line hand-maintained default-config duplicate | `src/ui/data/defaultConfig.ts` predates recipe artifacts; app never imports it; two test blocks pin its shape | **D4** delete file; retarget those tests at `STANDARD_RECIPE_CONFIG` (the generated artifact) | + +## D1 design (the config surface, decided) + +- **Elevation, not indentation.** Panel (popover, 11%) → stage card (card, 9%, + one step recessed — already true) → **group well**: one further recess + (`bg-background/40`-class tint over card, `border-border-subtle`, rounded, + p-2). Groups read as machined slots in the slate, matching the instrument- + bezel direction. Arrays unify onto the same well treatment. +- **Two surface tiers maximum.** Nesting deeper than stage→group does not add + surfaces; it differentiates by eyebrow heading (text-label uppercase muted) + and the group↔group rhythm. Indent ladders (`border-l` + `pl-*`) are removed. +- **Rhythm:** 4px within a field block (label/input/help), 8px between fields, + 12px between groups and between stage cards. One place owns these (the FORM + constant in rjsfTemplates). +- Field labels stay `text-foreground` medium (Pass-2); descriptions stay muted + and are NOT removed (docs are part of the instrument). + +## D2 design (consoles, decided — user proposal incorporated) + +Deliberation: accepted. The current footer already contains a latent boundary +(status/last-run + run controls are studio-runtime; live chip + autoplay + +run-in-game are Civ7-runtime). Making it structural gives the game side room to +grow (user: more live-game controls coming). One deviation from the literal +ask: **Run in Game moves to the Game console** (it commands the live game), +even though it sits in the run-controls bar today; the save-deploy status chip +follows it. Bars: centered Studio console (true centering, independent of right +content), right-pinned Game console with an eyebrow identity label ("Civ7" / +radio glyph) so the area is *named*, modular component `GameConsole` so future +controls land there. + +## D3 design (explore toolbar, decided) + +Inventory + categorization (verified in code): + +| Control | Acts on | Disposition | +|---|---|---| +| Fit to view | camera | VIEW cluster | +| Edges toggle | map overlay (`showEdgeOverlay`) | VIEW cluster | +| Render mode segment | selected data's rendering | DATA cluster | +| Space segment | selected data's coordinate space | DATA cluster | +| Era mode+slider | selected data slice | DATA cluster (conditional) | +| Variant select | selected data slice | DATA cluster (conditional) | +| Overlay select + opacity | selected data correlation | DATA cluster (conditional) | +| Debug toggle | DATA **list** filter | moves to DATA section header | + +No control is senseless; none removed. Layout: two eyebrow-labeled clusters +(VIEW / DATA) with consistent label-left/control-right rows replacing the +current left/right zig-zag. + +## Slices (stacked on design/theme-class-sync) + +1. `design/pass3-frame` — this doc, system.md amendment, OpenSpec changes. +2. `design/cascade-reset` — **D0** (root; everything downstream re-grounds on it). +3. `design/no-hardcoded-defaults` — **D4** (independent hygiene, early so later + test work inherits the law). +4. `design/config-surface` — **D1**. +5. `design/footer-consoles` — **D2**. +6. `design/explore-toolbar-2` — **D3**. + +Verification contract: per slice — OpenSpec `--strict`, tsc, vitest green, and +live visual proof on :5173; final gate — dark + light screenshots of the full +app, DOM-computed spacing spot-checks (px-3 ⇒ 12px), build + worker bundle. + +## Falsifier + +If after D0+D1 the config surface still reads cramped or hierarchy-flat, the +spacing *values* and tier tints are wrong (design problem), not the mechanism — +iterate values in D1's slice. If the Game console split confuses state instead +of clarifying it, the bridge chips are insufficient and D2 reverts to a single +bar with internal grouping (user explicitly allowed refusal). diff --git a/openspec/changes/mapgen-studio-config-surface/design.md b/openspec/changes/mapgen-studio-config-surface/design.md new file mode 100644 index 0000000000..3f12be14ba --- /dev/null +++ b/openspec/changes/mapgen-studio-config-surface/design.md @@ -0,0 +1,52 @@ +## Context + +The config form is the instrument's main working surface (Pass-2 made the dock +340px for it). Its nesting idiom predates the design system: headings + +`border-l` indents, with arrays on a divergent muted-box treatment. The user +asked for a ground-up pass: flat, "slate feeling", elevation-based grouping. +This slice lands AFTER the spacing-substrate repair, so declared paddings +actually render; values below were chosen against the restored baseline. + +## Decisions + +### 1. Recess, don't raise + +Wells tint toward the page token (`bg-background/40` over `card`), not toward +`muted` (which is *lighter* than card and reads as raised chips). The +instrument language is machined slots in a slate bezel: panel (popover 11%) → +stage card (card 9%) → group well (≈7% effective). Controls keep their own +input-background wells inside, which stay legible on the darker ground. + +### 2. Two surface tiers, hard cap + +Schema nesting is unbounded; surface nesting is not. Stage→group gets the only +two tiers. Depth≥3 groups render heading + rhythm inside the parent well. +Rationale: each recess step costs contrast budget against the 4–5% lightness +range the whole substrate lives in; three tiers would either crush text +contrast or force lighter tints that read as raised. + +### 3. One rhythm constant + +`FORM.rhythm` (names, not numbers, at call sites): `fieldGap` 4px ⇒ `gap-1`, +`siblingGap` 8px ⇒ `gap-2`, `groupGap` 12px ⇒ `gap-3`, well padding `p-2`. +The root object template and stage card content adopt `siblingGap`; group/stage +boundaries adopt `groupGap`. This makes the Gestalt chunking auditable in one +place instead of scattered `gap-*` literals. + +### 4. Heading tier inversion + +Today depth-2 headings are `text-xs font-semibold text-foreground` — brighter +than field labels were before Pass-2, and competing with them after. Inverted: +group headings become eyebrows (`text-label` uppercase, muted), the well's +geometry carries the grouping, and field labels stay the brightest scan line. +Stage titles keep `text-sm font-semibold` (the card already separates them). + +## Risks + +- Well tint over `card` must survive light mode (`background` is *lighter* + than `card` in light theme, so `bg-background/40` raises instead of + recesses there). Verify both themes; if light mode inverts, use an explicit + token pair or `dark:` variant — decided at implementation against the + running app, recorded in the slice. +- 340px minus card padding minus well padding leaves ~280px for field rows — + wider than the pre-Pass-2 panel total; acceptable. diff --git a/openspec/changes/mapgen-studio-config-surface/proposal.md b/openspec/changes/mapgen-studio-config-surface/proposal.md new file mode 100644 index 0000000000..4de4829170 --- /dev/null +++ b/openspec/changes/mapgen-studio-config-surface/proposal.md @@ -0,0 +1,52 @@ +# Config surface: elevation-based nesting + codified form rhythm + +## Why + +The user judged the config panel's padding/margins "really bad" and its +nesting/grouping representation poor — it should feel "smooth and slate", with +groups displayed flat on a proper surface-elevation scheme. Grounded diagnosis +(post spacing-substrate repair, which restores the *mechanism*): + +1. **Nesting is an indent ladder, not a surface system.** Depth≥2 groups render + as a heading plus `border-l` + `pl-2.5` — a code-editor idiom that stacks + left rules, eats horizontal room at 340px, and contradicts the instrument's + substrate-elevation language (system.md: depth is carried by lightness + tiers, not lines). +2. **No chunking rhythm.** Within-field, field↔field, and group↔group spacing + are near-identical ad-hoc gaps (`gap-0.5/1/1.5/2`), so the form has no + Gestalt grouping — everything reads as one undifferentiated column. +3. Arrays use a different nested treatment (`bg-muted/40` boxes) than object + groups, so sibling structures read as different species. + +## Target Authority Refs + +- `docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md` (issues 3–4; D1 design) +- `apps/mapgen-studio/.interface-design/system.md` (§Pass-3 amendment: form + surface elevation, form rhythm; §Foundation & depth: borders-only lightness tiers) + +## What Changes + +All in `src/features/configOverrides/rjsfTemplates.tsx` (+ the FORM constant): + +- **Group wells replace indent ladders.** Depth≥2 object groups render as one + recessed surface tier inside the stage card: tinted toward the page token + (`bg-background/40`-class), `border-border-subtle`, rounded, padded. The + `border-l` indent idiom is deleted. Deeper nesting (depth≥3) adds no further + surfaces — eyebrow headings + rhythm only (two surface tiers maximum). +- **Arrays unify onto the same well treatment** as object groups. +- **Form rhythm is codified** in the FORM constant on the 4px base: 4px within + a field block (label/input/description/help), 8px between fields, 12px + between groups and between stage cards; group-well padding 8px; stage card + padding stays `p-2.5`. +- Group headings move to the eyebrow tier (`text-label` uppercase muted) so + field labels (foreground, Pass-2) remain the brightest scan anchors inside a + card. +- `transparentPaths`, error/alert contracts, gs-comments rendering, and widget + wiring are untouched (behavior parity). + +## Impact + +- Affected specs: `mapgen-studio` +- Affected code: `apps/mapgen-studio/src/features/configOverrides/rjsfTemplates.tsx`, + `apps/mapgen-studio/test/config/rjsfFieldTemplateErrors.test.tsx` (class + assertions), `.interface-design/system.md` (already amended in the frame). diff --git a/openspec/changes/mapgen-studio-config-surface/specs/mapgen-studio/spec.md b/openspec/changes/mapgen-studio-config-surface/specs/mapgen-studio/spec.md new file mode 100644 index 0000000000..b50329eeff --- /dev/null +++ b/openspec/changes/mapgen-studio-config-surface/specs/mapgen-studio/spec.md @@ -0,0 +1,46 @@ +## ADDED Requirements + +### Requirement: Config Nesting Is Expressed By Surface Elevation + +Config-form nesting SHALL be expressed by surface tiers, not indent rules: +stage sections render as cards, groups within a stage render as one recessed +well surface (page-token tint, subtle border, rounded, padded), and nesting +deeper than stage→group adds no additional surface tiers. + +#### Scenario: Group renders as a recessed well +- **WHEN** an object group (depth ≥ 2, not transparent) renders inside a stage card +- **THEN** it renders as a single recessed surface (tinted toward the page token with a subtle border and padding) +- **AND** no `border-l` indent rule is used to mark its nesting + +#### Scenario: Deep nesting stays at two surface tiers +- **WHEN** a group contains a further nested group (depth ≥ 3) +- **THEN** the inner group differentiates by heading tier and spacing only, with no third surface tier + +#### Scenario: Arrays ride the same well treatment +- **WHEN** an array field renders +- **THEN** its container uses the same well surface as object groups + +### Requirement: The Config Form Has A Codified Spacing Rhythm + +The config form SHALL chunk by a codified rhythm on the 4px base — tighter +within a field block than between fields, and tighter between fields than +between groups/stage sections — owned by a single constant in the rjsf +templates. + +#### Scenario: Within-field is tighter than field-to-field +- **WHEN** a field renders its label, input, and description +- **THEN** the vertical gap inside the field block (4px) is smaller than the gap separating it from sibling fields (8px) + +#### Scenario: Groups chunk wider than fields +- **WHEN** sibling groups or stage sections render +- **THEN** they are separated by a wider step (12px) than field-to-field spacing + +### Requirement: Group Headings Sit On The Eyebrow Tier + +Group headings inside stage cards SHALL use the eyebrow label tier (uppercase +`text-label`, muted), keeping field labels (foreground) the brightest text +inside a card. + +#### Scenario: Field labels outrank group headings in the squint test +- **WHEN** a stage card with grouped fields renders +- **THEN** field labels render on the foreground tier while group headings render uppercase on the muted eyebrow tier diff --git a/openspec/changes/mapgen-studio-config-surface/tasks.md b/openspec/changes/mapgen-studio-config-surface/tasks.md new file mode 100644 index 0000000000..9968349cb2 --- /dev/null +++ b/openspec/changes/mapgen-studio-config-surface/tasks.md @@ -0,0 +1,20 @@ +## 1. Implementation + +- [ ] 1.1 Codify `FORM` rhythm + well surface classes in `rjsfTemplates.tsx` + (fieldGap 4 / siblingGap 8 / groupGap 12; well = page-tint + subtle + border + rounded + p-2). +- [ ] 1.2 Object template: depth≥2 groups render as wells (drop `border-l` + + `pl-*` ladder); depth≥3 adds heading + rhythm only; headings move to the + eyebrow tier. +- [ ] 1.3 Array template: unify onto the well treatment. +- [ ] 1.4 Apply rhythm to root/stage content gaps. +- [ ] 1.5 Update `rjsfFieldTemplateErrors.test.tsx` assertions if class names + shifted; add a well/no-indent assertion for a nested group. + +## 2. Verification + +- [ ] 2.1 `bun run openspec -- validate mapgen-studio-config-surface --strict` +- [ ] 2.2 tsc + mapgen-studio vitest green +- [ ] 2.3 Visual on :5173, dark AND light: stage card → group well reads as two + recess steps (squint test shows chunking); no left-rule indents; rhythm + visibly tiered (field < sibling < group). Screenshot both themes. diff --git a/openspec/changes/mapgen-studio-explore-toolbar-groups/proposal.md b/openspec/changes/mapgen-studio-explore-toolbar-groups/proposal.md new file mode 100644 index 0000000000..453789a303 --- /dev/null +++ b/openspec/changes/mapgen-studio-explore-toolbar-groups/proposal.md @@ -0,0 +1,39 @@ +# Explore toolbar: group controls by target (view vs data) + relocate debug + +## Why + +The explore panel's bottom toolbar mixes four different control targets with +no visible organization: camera (Fit), map overlay (Edges), selected-data +presentation (Render, Space, Era, Variant, Overlay), and a DATA-*list* filter +(Debug). The layout zig-zags (Fit/Edges left, Render right; Space left, Debug +right), so the user reads it as an unordered pile of icon buttons. Verified in +code: `showDebugLayers` feeds `buildStepDataTypeModel(..., { includeDebug })` — +it changes which entries the DATA list shows, not how the map renders, so it +is misplaced in a view toolbar. Inventory found no senseless controls; the +defect is categorization and presentation, not existence. + +## Target Authority Refs + +- `docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md` (issues 7–8; D3 inventory table) +- `apps/mapgen-studio/.interface-design/system.md` (§Pass-3 amendment: explore + toolbar groups; Pass-2 segmented-control pattern) + +## What Changes + +All in `src/ui/components/ExplorePanel.tsx`: + +- **Two eyebrow-labeled clusters** replace the zig-zag rows: + - **VIEW** — Fit to view (camera), Edges overlay toggle (map display). + - **DATA** — Render segment, Space segment, then the conditional + data-scoped rows (Era, Variant, Overlay + opacity) under the same cluster. +- **Consistent row anatomy**: label-left / control-right within each cluster + (no more right-aligned-label-over-control vs left-aligned variants). +- **Debug toggle relocates to the DATA section header** (beside the count), + because it filters that list; same icon, aria, and tooltip semantics. +- No control is removed; all handlers, aria labels, tooltips, and + segmented-control active treatments are preserved. + +## Impact + +- Affected specs: `mapgen-studio` +- Affected code: `apps/mapgen-studio/src/ui/components/ExplorePanel.tsx` diff --git a/openspec/changes/mapgen-studio-explore-toolbar-groups/specs/mapgen-studio/spec.md b/openspec/changes/mapgen-studio-explore-toolbar-groups/specs/mapgen-studio/spec.md new file mode 100644 index 0000000000..dcf96f2490 --- /dev/null +++ b/openspec/changes/mapgen-studio-explore-toolbar-groups/specs/mapgen-studio/spec.md @@ -0,0 +1,31 @@ +## ADDED Requirements + +### Requirement: Explore Toolbar Controls Group By Their Target + +The explore panel's bottom toolbar SHALL group controls into labeled clusters +by what they act on — a VIEW cluster for camera/map-display controls (fit, +edges overlay) and a DATA cluster for selected-data presentation controls +(render mode, space, and the conditional era/variant/overlay rows) — with a +consistent label/control row anatomy inside each cluster. + +#### Scenario: View controls cluster under a VIEW label +- **WHEN** the explore toolbar renders +- **THEN** fit-to-view and the edges overlay toggle render together under a visible VIEW eyebrow label + +#### Scenario: Data controls cluster under a DATA label +- **WHEN** the explore toolbar renders for a selected data type +- **THEN** the render-mode and space segmented controls (and era/variant/overlay rows when applicable) render together under a visible DATA eyebrow label + +### Requirement: The Debug Toggle Lives With The Data List It Filters + +The debug-layers toggle SHALL render on the DATA section header (it filters +which entries the data list shows), not in the view toolbar, with unchanged +toggle semantics and accessibility contract. + +#### Scenario: Debug toggle renders on the DATA header +- **WHEN** the explore panel renders +- **THEN** the debug toggle appears in the DATA section header row and no debug control remains in the bottom toolbar + +#### Scenario: Debug filtering behavior is unchanged +- **WHEN** the debug toggle is activated +- **THEN** debug-visibility data types appear in the DATA list exactly as before the relocation diff --git a/openspec/changes/mapgen-studio-explore-toolbar-groups/tasks.md b/openspec/changes/mapgen-studio-explore-toolbar-groups/tasks.md new file mode 100644 index 0000000000..ccd579c42f --- /dev/null +++ b/openspec/changes/mapgen-studio-explore-toolbar-groups/tasks.md @@ -0,0 +1,17 @@ +## 1. Implementation + +- [ ] 1.1 Restructure the bottom toolbar into VIEW (fit, edges) and DATA + (render, space, era, variant, overlay) clusters with eyebrow labels and + consistent label-left/control-right rows. +- [ ] 1.2 Move the debug toggle to the DATA section header (same icon/aria/ + tooltip; `aria-pressed` preserved). +- [ ] 1.3 Preserve all handlers, segmented-control treatments, and conditional + rendering (era/variant/overlay). + +## 2. Verification + +- [ ] 2.1 `bun run openspec -- validate mapgen-studio-explore-toolbar-groups --strict` +- [ ] 2.2 tsc + mapgen-studio vitest green +- [ ] 2.3 Visual on :5173: clusters labeled and aligned; debug toggle on the + DATA header still filters the list (toggle live and verify entries + change). Screenshot. diff --git a/openspec/changes/mapgen-studio-footer-consoles/proposal.md b/openspec/changes/mapgen-studio-footer-consoles/proposal.md new file mode 100644 index 0000000000..768080403d --- /dev/null +++ b/openspec/changes/mapgen-studio-footer-consoles/proposal.md @@ -0,0 +1,49 @@ +# Footer consoles: studio vs live-game separation with a state bridge + +## Why + +The footer interleaves two ownership domains with no boundary: studio-runtime +controls/info (generation status, last-run summary, seed, reroll, auto-run, +Run) and Civ7-runtime controls/info (live turn/seed chip, apply-suggestion, +autoplay start/stop, Run in Game + its status/retry/diagnostics, save-deploy +chip). The user proposed — and grounding confirms — a structural split: the +game side is expected to grow (more live-game controls coming), and today new +controls have no named home, so they accrete into the run bar. Deliberated and +accepted, with one deviation from the literal ask: **Run in Game belongs to +the game console** (it commands the live game), even though it sits in the +studio run bar today. + +## Target Authority Refs + +- `docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md` (issues 5–6; D2 design) +- `apps/mapgen-studio/.interface-design/system.md` (§Pass-3 amendment: footer = two consoles) +- `docs/projects/mapgen-studio-redesign/FRAME.md` (§3 hard core = behavior parity) + +## What Changes + +- **`AppFooter` splits into two visual consoles** (one component file may still + own both, but the game side becomes a named, modular unit — `GameConsole` — + so future live-game controls have a designated home): + - **Studio console, centered:** status dot/text (Ready/Modified/Running/ + Error), last-run summary (seed · size · players · resources), seed input, + reroll, auto-run toggle, Run. The two current studio bars merge into one. + - **Game console, right-docked:** identity eyebrow ("Civ7"), live runtime + chip (turn/seed/readiness + apply-suggestion affordance), autoplay + start/stop, Run in Game button + status chip + retry + diagnostics, and + the save-deploy status chip. +- **Centering is independent of the right dock**: the studio console stays + truly centered regardless of game-console width; narrow viewports wrap + rather than overlap. +- **Bridge (state legibility):** the relation cues that tie consoles together + are preserved and co-located — stale-vs-studio warning ring + Current/Stale/ + Previous chip on the Game console; dirty emphasis (Modified + Run ring) on + the Studio console. No relation signal is dropped. +- All callbacks, gating (`operationControlsDisabled`), tooltips, aria + contracts, and status semantics are unchanged (behavior parity). + +## Impact + +- Affected specs: `mapgen-studio` +- Affected code: `apps/mapgen-studio/src/ui/components/AppFooter.tsx` + (+ a `GameConsole` unit), `src/app/StudioShell.tsx` wiring if props + regroup, footer parity tests. diff --git a/openspec/changes/mapgen-studio-footer-consoles/specs/mapgen-studio/spec.md b/openspec/changes/mapgen-studio-footer-consoles/specs/mapgen-studio/spec.md new file mode 100644 index 0000000000..0a36328b57 --- /dev/null +++ b/openspec/changes/mapgen-studio-footer-consoles/specs/mapgen-studio/spec.md @@ -0,0 +1,41 @@ +## ADDED Requirements + +### Requirement: The Footer Separates Studio And Game Consoles + +The footer SHALL render exactly two consoles: a centered studio console +carrying studio-runtime status and run controls (status, last-run summary, +seed, reroll, auto-run, Run), and a right-docked, identity-labeled game +console carrying all live-Civ7 information and controls (live runtime chip, +apply-suggestion, autoplay, Run in Game with its status/retry/diagnostics, +save-deploy status). + +#### Scenario: Studio controls live in one centered bar +- **WHEN** the footer renders +- **THEN** status, last-run summary, seed input, reroll, auto-run, and Run render in a single centered console +- **AND** no live-game control or chip renders inside it + +#### Scenario: Game controls live in the named right console +- **WHEN** the footer renders with live runtime and run-in-game state available +- **THEN** the live chip, autoplay toggle, Run in Game button, its status chip/retry/diagnostics, and the save-deploy chip render in the right-docked console under a visible identity label + +#### Scenario: Centering is independent of the game console +- **WHEN** the game console's content width changes +- **THEN** the studio console remains horizontally centered in the viewport + +### Requirement: The Console Split Preserves Studio-Game State Legibility + +The split SHALL preserve every studio↔game relation cue: staleness emphasis +and the apply-suggestion affordance on the game console, and dirty-state +emphasis on the studio console, with unchanged semantics. + +#### Scenario: Stale live game still offers the bridge +- **WHEN** the live game is proved stale relative to the current studio state +- **THEN** the game console shows the warning emphasis and the apply-suggestion affordance exactly as before the split + +#### Scenario: Run in Game relation chip survives +- **WHEN** a recorded Run in Game operation exists +- **THEN** its Current/Stale/Previous relation chip renders beside it on the game console + +#### Scenario: Studio dirty state stays on the studio console +- **WHEN** current settings differ from the last run +- **THEN** the studio console shows the Modified status and the Run button's dirty ring diff --git a/openspec/changes/mapgen-studio-footer-consoles/tasks.md b/openspec/changes/mapgen-studio-footer-consoles/tasks.md new file mode 100644 index 0000000000..924cb93468 --- /dev/null +++ b/openspec/changes/mapgen-studio-footer-consoles/tasks.md @@ -0,0 +1,19 @@ +## 1. Implementation + +- [ ] 1.1 Extract a named `GameConsole` unit (live chip + apply-suggestion, + autoplay, Run in Game + status/retry/diagnostics, save-deploy chip) + with an identity eyebrow. +- [ ] 1.2 Merge the status/last-run bar and the run-controls bar into one + centered Studio console (status · last · seed · reroll · auto-run · Run). +- [ ] 1.3 Footer layout: studio console truly centered; game console pinned + right; wrap (not overlap) on narrow viewports. +- [ ] 1.4 Keep all gating/tooltips/aria/title diagnostics verbatim; update + footer parity tests for the new structure. + +## 2. Verification + +- [ ] 2.1 `bun run openspec -- validate mapgen-studio-footer-consoles --strict` +- [ ] 2.2 tsc + mapgen-studio vitest green +- [ ] 2.3 Visual on :5173: centered studio console independent of game-console + width; game console named and right-docked; stale ring + relation chip + + Modified/dirty ring all visible in their consoles. Screenshot. diff --git a/openspec/changes/mapgen-studio-no-hardcoded-defaults/proposal.md b/openspec/changes/mapgen-studio-no-hardcoded-defaults/proposal.md new file mode 100644 index 0000000000..b75fc38572 --- /dev/null +++ b/openspec/changes/mapgen-studio-no-hardcoded-defaults/proposal.md @@ -0,0 +1,45 @@ +# Remove hard-coded pipeline-config duplicates (no hard-coded overrides law) + +## Why + +User law (2026-06-11): no hard-coded config overrides anywhere — app or tests; +any literal duplicate of pipeline default values is a defect to remove on +sight. Sweep findings: + +1. **`src/ui/data/defaultConfig.ts`** — a 330-line hand-maintained copy of the + standard recipe's default pipeline config (plateCount, climate constants, + etc.). The app never imports it: real defaults flow from + `STANDARD_RECIPE_CONFIG` (generated recipe artifact) through + `buildDefaultConfig(...)`. Its only consumers are two blocks in + `test/config/defaultConfigSchema.test.ts` that pin the *legacy file's* key + shape — guarding a dead duplicate instead of the real artifact. Every + future pipeline-default change must be mirrored by hand or the file drifts + (it already presents `plateActivity: 0.5` et al. as authoritative-looking + values). +2. Remaining literal config fixtures in tests (persistence/clientState/ + presetStore/importFlow migration and round-trip fixtures) test *user-data + shapes* (legacy persisted formats, imported preset payloads) — they are + inputs being round-tripped, not duplicated defaults, and stay. The law is + recorded so new work cannot add default-mirroring literals. + +## Target Authority Refs + +- `docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md` (hard core: + no hard-coded config overrides; issue 9) + +## What Changes + +- **Delete `apps/mapgen-studio/src/ui/data/defaultConfig.ts`** (including its + unused `getStageOrder`/`getStageDefaults` helpers). +- **Retarget the two legacy-shape test blocks** in + `test/config/defaultConfigSchema.test.ts` to assert stage-config key shapes + against `STANDARD_RECIPE_CONFIG` (the generated artifact the app actually + uses), preserving their intent (semantic surfaces only, no raw op + envelopes, no legacy stage keys). +- No app behavior changes (the file was dead code in the app path). + +## Impact + +- Affected specs: `mapgen-studio` +- Affected code: `apps/mapgen-studio/src/ui/data/defaultConfig.ts` (deleted), + `apps/mapgen-studio/test/config/defaultConfigSchema.test.ts` diff --git a/openspec/changes/mapgen-studio-no-hardcoded-defaults/specs/mapgen-studio/spec.md b/openspec/changes/mapgen-studio-no-hardcoded-defaults/specs/mapgen-studio/spec.md new file mode 100644 index 0000000000..b0919df304 --- /dev/null +++ b/openspec/changes/mapgen-studio-no-hardcoded-defaults/specs/mapgen-studio/spec.md @@ -0,0 +1,21 @@ +## ADDED Requirements + +### Requirement: Pipeline Defaults Have A Single Source Of Truth + +The studio SHALL NOT carry hard-coded duplicates of pipeline default config +values; defaults flow only from generated recipe artifacts +(`STANDARD_RECIPE_CONFIG` et al.) through the config builders, and tests +assert default-config shape against those artifacts rather than against +hand-maintained copies. + +#### Scenario: No hand-maintained default-config module exists +- **WHEN** the studio source is searched for pipeline default values +- **THEN** no app module duplicates the recipe artifact's default config (the former `src/ui/data/defaultConfig.ts` is gone) + +#### Scenario: Shape guards target the generated artifact +- **WHEN** the default-config shape tests run +- **THEN** they assert stage-config key shapes (semantic surfaces, no raw op envelopes, no legacy stage keys) against `STANDARD_RECIPE_CONFIG` + +#### Scenario: Round-trip fixtures remain user data, not defaults +- **WHEN** persistence/preset/migration tests construct config fixtures +- **THEN** those fixtures represent user-authored or legacy persisted payloads under test, and no fixture mirrors the recipe's default values as an expected default diff --git a/openspec/changes/mapgen-studio-no-hardcoded-defaults/tasks.md b/openspec/changes/mapgen-studio-no-hardcoded-defaults/tasks.md new file mode 100644 index 0000000000..4bb1230ba6 --- /dev/null +++ b/openspec/changes/mapgen-studio-no-hardcoded-defaults/tasks.md @@ -0,0 +1,14 @@ +## 1. Implementation + +- [ ] 1.1 Delete `apps/mapgen-studio/src/ui/data/defaultConfig.ts`; confirm no + remaining importers (app or tests) besides the retargeted shape tests. +- [ ] 1.2 Rewrite the two legacy-shape blocks in + `test/config/defaultConfigSchema.test.ts` to assert against + `STANDARD_RECIPE_CONFIG`, preserving the semantic-surface guards + (expected key sets, no raw op envelopes, no legacy stage keys). + +## 2. Verification + +- [ ] 2.1 `bun run openspec -- validate mapgen-studio-no-hardcoded-defaults --strict` +- [ ] 2.2 tsc + mapgen-studio vitest green +- [ ] 2.3 `rg` sweep: no app-side literal pipeline-default duplicates remain. diff --git a/openspec/changes/mapgen-studio-spacing-substrate/proposal.md b/openspec/changes/mapgen-studio-spacing-substrate/proposal.md new file mode 100644 index 0000000000..883bc5a4a0 --- /dev/null +++ b/openspec/changes/mapgen-studio-spacing-substrate/proposal.md @@ -0,0 +1,48 @@ +# Restore the spacing substrate (cascade-layer repair) + theme bootstrap key + +## Why + +Pass-3 grounding (2026-06-11, live DOM inspection on :5173) found that **every +Tailwind padding/margin utility in the app computes to 0px**. `index.html` +ships an inline, unlayered reset — `* { margin: 0; padding: 0; box-sizing: +border-box }` — and Tailwind v4 emits all utilities inside `@layer utilities`. +Unlayered author styles take cascade priority over all layered styles +regardless of specificity, so the reset silently wins everywhere: `p-2.5` +stage cards, `px-3 py-2` list rows, button/input padding, the Pass-2 scroll +fade margins — all dead. The pre-v4 build emitted unlayered utilities +(specificity won; the reset was harmless); the P1 Tailwind v4 migration +inverted the cascade without a visible diff. This is the root of the user's +"padding and margins on everything are really bad." + +Deleting the rule in the running page restores the intended texture app-wide +(verified: stage-list rows 12px inline padding, stage cards 10px, padded +header/footer/inputs). + +Same file, second defect: the pre-paint theme script reads +`localStorage["mapgen-studio:theme"]`, but the app persists the preference +under `theme-preference` (`useTheme.ts`). The bootstrap reads a key nobody +writes, so a light-theme user gets a dark pre-paint flash on every load. + +## Target Authority Refs + +- `docs/projects/mapgen-studio-redesign/pass-3-design-fixes.md` (Pass-3 frame; issues 1–2) +- `apps/mapgen-studio/.interface-design/system.md` (§Pass-3 amendment: no unlayered author CSS) + +## What Changes + +- **`index.html` drops the unlayered `*` reset.** Tailwind preflight (in + `@layer base`, via `@import "tailwindcss"` in `index.css`) already provides + the margin/padding/box-sizing reset at the correct layer priority. The + `body` pre-paint flash guard (font, background, color) stays. +- **The pre-paint theme script reads `theme-preference`** — the key the app + actually writes — preserving the dark default when the key is absent. +- No component changes in this slice. Surfaces whose Pass-1/2 styling was + tuned against the broken (zero-padding) render are re-tuned in the slices + that own them (config surface, consoles, explore toolbar). + +## Impact + +- Affected specs: `mapgen-studio` +- Affected code: `apps/mapgen-studio/index.html` only +- Visual blast radius is app-wide (all intended padding/margins appear); + behavior parity untouched. diff --git a/openspec/changes/mapgen-studio-spacing-substrate/specs/mapgen-studio/spec.md b/openspec/changes/mapgen-studio-spacing-substrate/specs/mapgen-studio/spec.md new file mode 100644 index 0000000000..e7321bd5b9 --- /dev/null +++ b/openspec/changes/mapgen-studio-spacing-substrate/specs/mapgen-studio/spec.md @@ -0,0 +1,28 @@ +## ADDED Requirements + +### Requirement: No Unlayered Author CSS May Override The Utility Layer + +The app SHALL NOT ship unlayered author CSS that overrides Tailwind's layered +utilities; `index.html` style content is limited to the pre-paint flash guard, +and element resets come only from Tailwind preflight. + +#### Scenario: Spacing utilities render their declared values +- **WHEN** an element declares a Tailwind spacing utility (e.g. `px-3`, `p-2.5`, `my-1.5`) +- **THEN** its computed style reflects the utility (e.g. `px-3` ⇒ 12px inline padding), with no unlayered `*` reset zeroing it + +#### Scenario: Pre-paint flash guard survives +- **WHEN** the page loads before React hydrates +- **THEN** the body still paints the dark flash-guard background and font stack from `index.html` + +### Requirement: The Theme Bootstrap Reads The Persisted Preference Key + +The pre-paint theme script SHALL read the same localStorage key the app writes +(`theme-preference`), defaulting to dark when the key is absent or unreadable. + +#### Scenario: Light preference applies before first paint +- **WHEN** `localStorage["theme-preference"]` is `"light"` and the page loads +- **THEN** the bootstrap does not add the `dark` class, so no dark pre-paint flash occurs + +#### Scenario: Absent key keeps the dark default +- **WHEN** no theme preference is persisted +- **THEN** the bootstrap adds the `dark` class before first paint diff --git a/openspec/changes/mapgen-studio-spacing-substrate/tasks.md b/openspec/changes/mapgen-studio-spacing-substrate/tasks.md new file mode 100644 index 0000000000..8fc1eb10c3 --- /dev/null +++ b/openspec/changes/mapgen-studio-spacing-substrate/tasks.md @@ -0,0 +1,17 @@ +## 1. Implementation + +- [ ] 1.1 Remove the unlayered `* { margin; padding; box-sizing }` rule from + `apps/mapgen-studio/index.html`; keep the `body` flash guard. +- [ ] 1.2 Point the pre-paint theme script at `theme-preference` (the key + `useTheme.ts` writes); keep dark-by-default and the try/catch fallback. + +## 2. Verification + +- [ ] 2.1 `bun run openspec -- validate mapgen-studio-spacing-substrate --strict` +- [ ] 2.2 tsc + mapgen-studio vitest project green +- [ ] 2.3 Live DOM proof on :5173: `px-3` row computes 12px inline padding; + stage card computes 10px padding; screenshot the restored texture. +- [ ] 2.4 Theme: with `theme-preference=light` persisted, reload shows no dark + flash and the app mounts light; cleared key mounts dark. +- [ ] 2.5 Full-app visual sweep (header, docks, footer, dialogs) for surfaces + that over-inflate with restored spacing; log re-tunes to owning slices.