Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions apps/mapgen-studio/.interface-design/system.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ Decisions (see `docs/projects/mapgen-studio-redesign/pass-4-design-fixes.md`):
Console glyphs: autoplay start `FastForward` / stop `Square`; Run in Game
`SquareArrowOutUpRight` (launches the external app); Explore `Binoculars`
(tile visibility). `Eye` stays reserved for layer-visibility toggles.
*(Glyph assignments superseded by the Z-wave registry v3 below; the
icon-only contract itself is unchanged.)*
- **Config objects collapse:** every config object renders a per-object header
row (chevron + title + trailing action zone — future home of object-local
Reset/Show-JSON); collapsed by default, manual expand; optional sticky
Expand Down Expand Up @@ -318,3 +320,34 @@ Decisions (see `openspec/changes/mapgen-studio-dag-tab/design.md`):
the options it specifies + its player options; ungoverned keys never count
as drift. The game-setup dropdowns may never silently supersede a selected
saved config.

## Z-wave amendment (2026-06-12, user-grounded): Game bar v3 — one status, one popup, launch CTA

Rearranges the Game bar (supersedes the Pass-4/5 console glyph registry and
the trailing-disclosure placement; keeps the icon-only contract + zoning):

- **Bar order:** `Gamepad2` Game · │ · Config select + Modified pill +
**gear** (`Settings`, icon-only Button) · │ · signal chip · autoplay ·
explore · **Play** CTA. The game-setup disclosure rides the CONFIG cluster
(the setup it opens is exactly what drifts a saved config); open state =
`ring-1 ring-ring border-primary text-primary` (the footer auto-run idiom).
- **ONE status, held by the signal chip.** The chip's dot folds live runtime
+ Run in Game + save/deploy into one color (any failure → destructive;
in-flight or stale → warning; live ok → success); while an operation runs
the chip text narrates its phase, then settles back to `Turn N · Seed S`.
No secondary status pills ride the bar — the run-relation pill
(Current/Stale/Previous), request ids, errors, and recovery hints live in
the **status hang-off**: a popup panel docked under the chip (`popover`
tier, chevron-disclosed, outside-click + Escape dismissal — popup
semantics keep it from ever stacking against the setup row). Secondary
per-operation affordances live inside it: Apply-live-to-Studio, refresh
status (`RotateCw`), copy diagnostics (**`Bug`** — it's the debug action).
- **Console glyph registry (v3):** autoplay idle = composed glyph (one
`Play` triangle inside the clockwise `RotateCw` loop — "keep playing
forward"); stop `Square` unchanged. Explore = **`ScanEye`** (reveal/vision
without the toy binoculars; bare `Eye` stays reserved for layer-visibility
toggles). Run in Game = labeled **`Play`** CTA, filled primary, SAME
Button size as the World console's Run (the two bars mirror: World "Run"
authors the map, Game "Play" launches it) with a **`Rocket`** glyph —
launch-the-external-app semantics, replacing `SquareArrowOutUpRight`.
`SlidersHorizontal` retired from the header (gear owns setup).
40 changes: 21 additions & 19 deletions apps/mapgen-studio/src/ui/components/AppHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from 'react';
import { ChevronDown, Gamepad2, SlidersHorizontal } from 'lucide-react';
import { Gamepad2, Settings } from 'lucide-react';
import { AppBrand } from './AppBrand';
import { ViewControls } from './ViewControls';
import { OptionSelect } from './OptionSelect';
import { Button, Tooltip, TooltipContent, TooltipTrigger } from '../../components/ui';
import {
getLocalPlayerSetup,
updateStudioSetupGameOption,
Expand Down Expand Up @@ -61,8 +62,6 @@ export const AppHeader: React.FC<AppHeaderProps> = ({
const textSecondary = 'text-muted-foreground';
const textMuted = 'text-muted-foreground/70';
const dividerColor = 'bg-border';
const setupButtonClassName =
'flex h-7 shrink-0 items-center gap-1.5 rounded border border-input bg-input-background px-2 text-data font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring';
const localPlayerSetup = getLocalPlayerSetup(setupConfig);
const updateLeader = (value: string) => {
onSetupConfigChange(updateStudioSetupPlayerOption(setupConfig, 'PlayerLeader', value || undefined));
Expand Down Expand Up @@ -138,6 +137,25 @@ export const AppHeader: React.FC<AppHeaderProps> = ({
Modified
</button>
) : null}
{/* Game-setup disclosure: the gear rides the config cluster (the
setup it opens is what drifts a saved config), icon-only per
the console contract. */}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="outline"
size="icon"
aria-expanded={setupOpen}
aria-controls="app-header-setup-panel"
aria-label="Game setup"
title="Game setup"
onClick={() => setSetupOpen((open) => !open)}
className={`shrink-0 ${setupOpen ? 'ring-1 ring-ring border-primary text-primary' : ''}`}>
<Settings className="w-3.5 h-3.5" />
</Button>
</TooltipTrigger>
<TooltipContent>Game setup</TooltipContent>
</Tooltip>
</div>

{gameConsole ? (
Expand All @@ -146,22 +164,6 @@ export const AppHeader: React.FC<AppHeaderProps> = ({
{gameConsole}
</>
) : null}

<div className={`w-px h-5 shrink-0 ${dividerColor}`} />

{/* Game-setup disclosure: icon-only (the dropdown row IS the
label), pinned last in the bar per the Pass-5 spec. */}
<button
type="button"
className={setupButtonClassName}
aria-expanded={setupOpen}
aria-controls="app-header-setup-panel"
aria-label="Game setup"
title="Game setup"
onClick={() => setSetupOpen((open) => !open)}>
<SlidersHorizontal className="w-3.5 h-3.5" />
<ChevronDown className={`w-3.5 h-3.5 transition-transform ${setupOpen ? 'rotate-180' : ''}`} />
</button>
</div>

{setupOpen ? (
Expand Down
Loading