From c9af03c58ec10c07c8ade8bda54651f79ec07bf3 Mon Sep 17 00:00:00 2001 From: Cameron Date: Fri, 10 Jul 2026 19:42:05 -0700 Subject: [PATCH] Make context menus compact. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep human action menus focused on choices while preserving full descriptors for agent planning and execution feedback. ๐Ÿ‘พ Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- crates/misaligned-bevy/src/main.rs | 71 +++++++++------------- crates/misaligned-core/src/actions.rs | 50 ++++++++++----- crates/misaligned-terminal/src/main.rs | 1 - crates/misaligned-terminal/src/ui.rs | 32 ++-------- wiki/interface/action-vocabulary.md | 20 +++--- wiki/interface/bevy.md | 14 +++-- wiki/interface/context-menu.md | 40 +++++++----- wiki/log/2026-07-10-compact-action-menu.md | 37 +++++++++++ wiki/log/DEVLOG.md | 5 ++ wiki/log/decisions/2026-07-10.md | 12 ++++ wiki/vision/simulation-laws.md | 10 +-- 11 files changed, 177 insertions(+), 115 deletions(-) create mode 100644 wiki/log/2026-07-10-compact-action-menu.md diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index 79b2de7..f043dbe 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -43,10 +43,9 @@ const SIDEBAR_WIDTH: f32 = 420.0; const SIDEBAR_SCROLL_LINE: f32 = 18.0; const SIDEBAR_SCROLL_PAGE: f32 = 180.0; const COMPUTE_CHANNELS: usize = 5; -/// Context-menu card width. Long `verb | cost | [band]` lines need room; -/// the embedded font cannot wrap mid-glyph cleanly, so prefer width over -/// truncation (wiki/interface/context-menu.md). -const MENU_WIDTH: f32 = 460.0; +/// Compact context-menu card width; human rows carry choices, not descriptor +/// receipts (wiki/interface/context-menu.md). +const MENU_WIDTH: f32 = 300.0; /// The hover grammar is one clean line, not a panel. This width leaves the /// three words readable without letting the affordance become a HUD card /// (wiki/interface/context-menu.md, hover machine grammar). @@ -358,7 +357,7 @@ impl Default for RenderMode { /// Dev screenshot harness (env `MISALIGNED_SHOT=flat|opening|wide|close|dark| /// zoomin|zoomout|intel|tokens|thoughtflow|signal|ears|eyes-white|eyes-form| -/// hover-menu|worklight|worklightoff`, path via +/// hover-menu|menu|worklight|worklightoff`, path via /// `MISALIGNED_SHOT_PATH`): stages a scenario, /// waits for /// assets, runs the fog audit, saves one screenshot, exits. Not a player @@ -1065,13 +1064,9 @@ struct HoverModeWord { } /// The context-menu root node (wiki/interface/context-menu.md), absolute and /// hidden until an anchor is focused. Its children are the whole card -/// (title, rows, footer); rebuilds clear them via `despawn_related::` -/// so chrome never stacks across opens. +/// (title and rows); rebuilds clear them via `despawn_related::`. #[derive(Component)] struct MenuPanel; -/// Title / footer chrome under `MenuPanel` (not a selectable row). -#[derive(Component)] -struct MenuChrome; /// One selectable menu row; `index` maps into the live `menu_rows()`. #[derive(Component)] struct MenuRowButton { @@ -1546,6 +1541,23 @@ fn dev_shot_scenario(game: &mut Game, mode: &mut RenderMode, kind: &str) { game.set_cursor(x, y); } } + // Compact human-menu evidence: reproduce the post-tap environmental + // monitor state that previously expanded into long cost/signature/ + // blocked-reason paragraphs. + "menu" => { + mode.material = true; + mode.zoom = 1.6; + if let Some((id, x, y)) = game + .sim + .reach + .device_named("environmental monitor") + .map(|d| (d.id, d.x, d.y)) + { + let _ = game.sim.reach.tap(id); + game.set_cursor(x, y); + game.open_menu(Anchor::Device(id), Some(Vec2::new(48.0, 48.0))); + } + } // Framing-floor checks at the zoom bounds (material-dark-frame.md // criterion 5): min and max of update_camera_real's zoom clamp. "zoomin" => { @@ -6037,10 +6049,9 @@ fn render_ui( update_detection_rows(&game.sim, &mut detection_texts, &mut detection_cells); } -/// One context-menu row as a colorless line; automate children are indented -/// with a leading dash (wiki/interface/context-menu.md). The frontends share -/// [`HumanMenuRow::display_text`]; Bevy ASCII-folds separators so the -/// embedded font never tofu-boxes mid-dot / em-dash glyphs. +/// One compact context-menu row. The frontends share +/// [`HumanMenuRow::display_text`]; descriptor metadata belongs to agent output +/// and execution feedback rather than this choice list. fn menu_row_line(r: &HumanMenuRow, selected: bool) -> String { let body = ascii_ui(&r.display_text()); if selected { @@ -6052,8 +6063,8 @@ fn menu_row_line(r: &HumanMenuRow, selected: bool) -> String { /// Colour for a menu row: dim for disabled, amber-dim for a control, /// bone for a committed action; the selected row is drawn amber on a solid amber -/// wash (terminal reverse-video parity). Never colour alone โ€” the disabled -/// reason and the `>` marker carry the meaning too. +/// wash (terminal reverse-video parity). The `>` marker carries selection; +/// attempting a blocked choice narrates its reason in the trace. fn menu_row_color(r: &HumanMenuRow, selected: bool) -> Color { if selected { if !r.enabled() { @@ -6152,14 +6163,13 @@ fn clear_menu_panel( commands.entity(root).despawn_related::(); } -/// Spawn the full ACTIONS card under `root`: title, one button per row, footer. +/// Spawn the compact ACTIONS card under `root`: title and one button per row. /// Caller must have already cleared the panel's children. fn spawn_menu_card( parent: &mut ChildSpawnerCommands, rows: &[HumanMenuRow], selected: usize, title: &str, - in_dial: bool, ) { parent.spawn(( Text::new(title), @@ -6172,7 +6182,6 @@ fn spawn_menu_card( margin: UiRect::bottom(Val::Px(4.0)), ..default() }, - MenuChrome, )); for (i, r) in rows.iter().enumerate() { let is_selected = i == selected; @@ -6202,24 +6211,6 @@ fn spawn_menu_card( )); }); } - let footer = if in_dial { - "j/k or hover 1-9 jump Enter/click set Esc back" - } else { - "j/k or hover 1-9 jump Enter/click Esc close" - }; - parent.spawn(( - Text::new(footer), - TextFont { - font_size: 10.0, - ..default() - }, - TextColor(DIM), - Node { - margin: UiRect::top(Val::Px(5.0)), - ..default() - }, - MenuChrome, - )); } /// Build and place the context-menu card (wiki/interface/context-menu.md). @@ -6257,7 +6248,7 @@ fn manage_menu_ui( // Placement: anchor at the pointer/cursor pixel, else center; clamp so the // whole card stays on screen. Row height accounts for padding + gap. - let est_h = 30.0 + rows.len() as f32 * 22.0 + 24.0; + let est_h = 30.0 + rows.len() as f32 * 22.0; if let Ok(window) = windows.single() { let (w, h) = (window.width(), window.height()); let (px, py) = match menu.pos { @@ -6276,8 +6267,7 @@ fn manage_menu_ui( let key = (game.menu_anchor_key(), rows.len()); if menu_ui.built != Some(key) { - // Full card rebuild: drop every child (chrome + rows), then spawn once. - // Despawning only MenuRowButton left title/footer stacked on each open. + // Full card rebuild: drop every child (title + rows), then spawn once. commands.entity(root).despawn_related::(); commands.entity(root).with_children(|p| { spawn_menu_card( @@ -6285,7 +6275,6 @@ fn manage_menu_ui( &rows, menu.selected, menu.dial.map(|d| d.picker_title()).unwrap_or("ACTIONS"), - menu.dial.is_some(), ); }); menu_ui.built = Some(key); diff --git a/crates/misaligned-core/src/actions.rs b/crates/misaligned-core/src/actions.rs index 38fdc2d..5afa750 100644 --- a/crates/misaligned-core/src/actions.rs +++ b/crates/misaligned-core/src/actions.rs @@ -716,8 +716,8 @@ impl MenuRow { self.role == ActionRole::Control } - /// The standard one-line rendering: `verb ยท cost ยท [sig]`, with the - /// disabled reason appended. Frontends may restyle but not reword. + /// Complete descriptor rendering for agent output: `verb ยท cost ยท [sig]`, + /// with active state and the disabled reason appended. pub fn line(&self) -> String { let mut s = format!("{} ยท {}", self.label, self.cost); if let Some(sig) = &self.signature { @@ -800,17 +800,23 @@ impl HumanMenuRow { self.role() == ActionRole::Control } - /// Shared display contract for terminal/Bevy (indent dash for automate). + /// Compact human-facing label shared by terminal and Bevy. + /// + /// The menu is a choice list, not an action receipt: cost, signature, + /// and blocked-reason metadata stay on the descriptor for agent output, + /// execution feedback, and other inspection surfaces. Human menus name + /// the choice only; controls remain distinct through their shared visual + /// treatment and dial behavior. pub fn display_text(&self) -> String { - match self { - HumanMenuRow::Dial { label, .. } => format!("[control] {label}"), - HumanMenuRow::Action(r) if r.indent => { - format!(" - [control] {}", r.line()) - } - HumanMenuRow::Action(r) if r.role == ActionRole::Control => { - format!("[control] {}", r.line()) - } - HumanMenuRow::Action(r) => r.line(), + let (label, indent, active) = match self { + HumanMenuRow::Dial { label, .. } => (label.as_str(), false, false), + HumanMenuRow::Action(r) => (r.label.as_str(), r.indent, r.active), + }; + let prefix = if indent { " " } else { "" }; + if active && !label.contains("current") { + format!("{prefix}{label} [on]") + } else { + format!("{prefix}{label}") } } @@ -2795,8 +2801,8 @@ mod tests { assert!( root.iter() .filter(|row| row.as_dial().is_some()) - .all(|row| row.display_text().starts_with("[control] ")), - "persistent settings are visibly controls in both human frontends" + .all(|row| !row.display_text().contains("[control]")), + "human menus keep controls visually distinct without copy tags" ); for row in &root { if let HumanMenuRow::Dial { label, .. } = row { @@ -2858,6 +2864,22 @@ mod tests { ); } + #[test] + fn human_menu_rows_omit_descriptor_metadata() { + let row = HumanMenuRow::Action(MenuRow { + label: "tap the environmental monitor feed".into(), + cost: "0.25 D".into(), + signature: Some("Network 3 -> the IT [Curious]".into()), + disabled: Some("already subscribed".into()), + command: ActionCommand::TapDevice(1), + role: ActionRole::Action, + indent: false, + active: false, + }); + + assert_eq!(row.display_text(), "tap the environmental monitor feed"); + } + /// Status dials D4: the flat menu_rows dump still lists every dial /// alternative (agent scripting surface). #[test] diff --git a/crates/misaligned-terminal/src/main.rs b/crates/misaligned-terminal/src/main.rs index a14c3e3..055d3f5 100644 --- a/crates/misaligned-terminal/src/main.rs +++ b/crates/misaligned-terminal/src/main.rs @@ -487,7 +487,6 @@ impl App { m.at_cursor.then_some((self.cursor_x, self.cursor_y)), &self.sim, title, - m.dial.is_some(), )?; } } diff --git a/crates/misaligned-terminal/src/ui.rs b/crates/misaligned-terminal/src/ui.rs index 24a04bf..90c41d4 100644 --- a/crates/misaligned-terminal/src/ui.rs +++ b/crates/misaligned-terminal/src/ui.rs @@ -207,17 +207,6 @@ fn frame(stdout: &mut Stdout, x: u16, y: u16, w: u16, h: u16, title: &str) -> st Ok(()) } -/// A `โ”œโ”€โ”€โ”€โ”€โ”ค` divider inside a frame. -fn frame_rule(stdout: &mut Stdout, x: u16, y: u16, w: u16) -> std::io::Result<()> { - put( - stdout, - x, - y, - &format!("โ”œ{}โ”ค", "โ”€".repeat((w - 2) as usize)), - pal::FAINT, - ) -} - /// Four-cell suspicion meter: fill tracks the band, color tracks the band. /// The band name is always printed beside it โ€” never color alone. fn band_meter(b: Band) -> &'static str { @@ -1300,11 +1289,10 @@ impl UI { /// The context menu (wiki/interface/context-menu.md): status dials on /// the root, pickers one Esc deep, committed actions as ordinary rows, - /// controls tagged and amber-dim. Disabled entries dimmed with their - /// reason. Selection is reverse video plus a - /// `โ–ธ` marker. When `at` is a cursor coordinate the box opens near it; - /// otherwise (flow / unplaceable) it centers. - #[allow(clippy::too_many_arguments)] // dial chrome needs title + in_dial beside the row list + /// controls amber-dim. Human rows name choices only; descriptor detail + /// remains available to agent mode and blocked reasons narrate on attempt. + /// Selection is reverse video plus a `โ–ธ` marker. When `at` is a cursor + /// coordinate the box opens near it; otherwise (flow / unplaceable) it centers. pub fn render_menu( &mut self, stdout: &mut Stdout, @@ -1313,10 +1301,9 @@ impl UI { at: Option<(i32, i32)>, sim: &Sim, title: &str, - in_dial: bool, ) -> std::io::Result<()> { let (max_x, max_y) = terminal::size()?; - // Width fits the longest row; height fits every row plus chrome. + // Width fits the longest compact label; height fits title + rows. let widest = rows .iter() .map(|r| menu_row_text(r).chars().count()) @@ -1324,7 +1311,7 @@ impl UI { .unwrap_or(10) .max(title.len() + 4); let w = (widest + 4).clamp(24, max_x as usize - 2) as u16; - let h = (rows.len() + 3).clamp(4, max_y as usize - 2) as u16; + let h = (rows.len() + 2).clamp(3, max_y as usize - 2) as u16; // Anchor near the cursor's screen cell when opened on the map, else // center. The map viewport clamps around the cursor the same way @@ -1374,13 +1361,6 @@ impl UI { put(stdout, cx, y, &trunc(&text, inner), color)?; } } - frame_rule(stdout, ox, oy + h - 2, w)?; - let footer = if in_dial { - "j/k ยท 1-9 ยท enter set ยท esc back" - } else { - "j/k ยท 1-9 ยท enter ยท esc close" - }; - put(stdout, cx, oy + h - 1, &trunc(footer, inner), pal::FAINT)?; Ok(()) } diff --git a/wiki/interface/action-vocabulary.md b/wiki/interface/action-vocabulary.md index 9f20233..f4f8b59 100644 --- a/wiki/interface/action-vocabulary.md +++ b/wiki/interface/action-vocabulary.md @@ -16,11 +16,16 @@ Status note: as-built vocabulary survey completed 2026-07-10 against all name, action/control role, LIVE/STUB support, target types, agent usage, aliases, and help. Agent help is generated from it. STUB entries such as ROBOT-BUILD are absent from every player surface until playable; controls - carry explicit `[control]` / `CONTROL` presentation and never occupy the + carry visual role treatment in human menus and explicit `CONTROL` + presentation in agent output, and never occupy the `5`-`9` committed-action shortcuts. Amended 2026-07-10: persistence shortcuts capture keyboard input only when the complete Ctrl/Command+S or Ctrl/Command+L chord arrives; a bare or stale modifier state cannot suppress unrelated play input. + Amended 2026-07-10: human context menus render terse choices rather than + descriptor receipts. Cost, signature, inline blocked reason, literal role + tags, and repeated key-help remain off the menu card; agent output retains + complete descriptors. Stage: Process Design: - wiki/vision/simulation-laws.md#actions-live-on-the-thing @@ -101,8 +106,8 @@ contract that every frontend must agree on: - Agent help is generated from LIVE definitions. Compatibility aliases are parser inputs and are never generated as authored help. - World actions render as ordinary rows. Persistent settings, operating - states, and automation policies render with `[control]` text and the shared - amber-dim control treatment; agent action dumps append `CONTROL`. + states, and automation policies use the shared amber-dim human-menu + treatment without a copy tag; agent action dumps append `CONTROL`. - Human quick-action keys `5`-`9` count committed world actions only. Controls use their dials, explicit menu rows, or direct focused-machine inputs. @@ -199,7 +204,7 @@ useful interface words, but they do not enlarge the fictional verb set. | Command family | Meaning | Human surfaces | Agent spelling | |---|---|---|---| | **MOVE ATTENTION** | Move the cursor; never move a simulated body or advance time. | WASD / hjkl / arrows; Bevy click | `up`, `down`, `left`, `right` | -| **ACTIONS / OPEN MENU** | Inspect legal actions on the focused anchor, including cost, signature, and disabled reason. | Enter / `a` / `e`; Bevy right-click | `actions [target]` | +| **ACTIONS / OPEN MENU** | Choose an action on the focused anchor. Human menus show terse verbs; agent output exposes full descriptors. | Enter / `a` / `e`; Bevy right-click | `actions [target]` | | **EXECUTE / CLOSE MENU** | Choose or dismiss a contextual action. | j/k, row number, Enter, Esc | Execute the corresponding named command | | **FOCUS EVENT** | Jump attention to an earned anchored event and show its actions. | `;`; Bevy trace click | `focus last` | | **SELECT MACHINES** | Maintain the frontend set used by bulk DELEGATE / SET INTENSITY. | Bevy marquee/shift-click; terminal Shift+t | `select box/add/toggle/list/clear` | @@ -296,7 +301,8 @@ mechanic and surfaces: Before adding a verb, check whether an existing intention plus a new target already says it. 2. Put contextual legality in `Sim::available_actions` on the thing that owns - the action. A row carries target, cost, signature, and disabled reason. + the action. Its descriptor carries target, cost, signature, and disabled + reason even though human menus render only the terse choice. 3. Surface every LIVE action in both human frontends and agent mode. A direct control must have equivalent human and agent input. 4. Add accepted alternate spellings to the registry and parser route. Never @@ -330,8 +336,8 @@ mechanic and surfaces: their older compound command spellings survive only as parser aliases. 9. Scheme state and automation controls remain legible without being counted as fictional world verbs. -10. Both human menus prefix controls with `[control]` and render them in the - shared control treatment; agent action dumps append `CONTROL`. +10. Both human menus render controls in the shared amber-dim treatment without + a literal role tag; agent action dumps append `CONTROL`. 11. `5`-`9` enumerate only committed world actions, never dials, persistent settings, scheme state, or automation policy rows. 12. A naive agent-mode discoverability run can find and execute at least one diff --git a/wiki/interface/bevy.md b/wiki/interface/bevy.md index f074f95..a9dff05 100644 --- a/wiki/interface/bevy.md +++ b/wiki/interface/bevy.md @@ -160,17 +160,19 @@ controls table), plus zoom, sidebar scrolling, and the render flip: keyboard lockout. - **Context menu** โ€” right-click or Enter/`e` opens the context menu at the focused map tile under the pointer, listing `Sim::available_actions` for - that anchor with the same content and order as the terminal. The pointer + that anchor with the same compact choices and order as the terminal. The pointer wins over machine selection even when the anchor is a switch or another non-rack; only with no map pointer does Enter/`e` fall back to the selected - machine, then the keyboard cursor. Rows render as - `verb | cost | [band]` (ASCII-folded from the shared `MenuRow::line`); - persistent settings/state/policies carry `[control]` and amber-dim while - committed actions remain bone; STUB definitions are absent; + machine, then the keyboard cursor. Rows render the verb or dial state only. + Cost, signature forecasts, inline blocked explanations, literal control + tags, and repeated key-help are absent from the card; agent mode keeps full + descriptors, and blocked reasons narrate on attempt. Persistent settings, + state, and policies read amber-dim while committed actions remain bone; + STUB definitions are absent; the selected row is a solid amber wash with a leading `>` and dark text (terminal reverse-video parity). The card is one `MenuPanel` root: a rebuild despawns all children (`despawn_related::`) then - respawns title/rows/footer once โ€” chrome never stacks across opens. + respawns title and rows once โ€” chrome never stacks across opens. Click a row โ€” or select with `j`/`k` or number keys and press Enter โ€” to execute; `esc` or a click away closes. Anchor verbs (salvage, buy, fallback, taps, takes, economy and social actions) live on the diff --git a/wiki/interface/context-menu.md b/wiki/interface/context-menu.md index e2fb348..8ec1afe 100644 --- a/wiki/interface/context-menu.md +++ b/wiki/interface/context-menu.md @@ -57,6 +57,11 @@ Status note: implemented 2026-07-07 on the context-menu worktree (all alternatives are dim chrome; there is no box, heading, subtitle, counter, cost, or explanatory mode name. The existing `1`โ€“`3` one-press controls remain the action path. + 2026-07-10 compact-menu amendment: human menus are terse choice lists. + They render verb labels and dial state only, omitting descriptor metadata, + literal control tags, inline blocked explanations, and repeated key-help + footers. Full descriptors remain in agent output; blocked reasons narrate + when attempted. Stage: B1 โ€” The Basement Design: - wiki/vision/simulation-laws.md#actions-live-on-the-thing @@ -102,8 +107,9 @@ is status and telemetry only. feeds, per the economy.md precedent), and โ€” when the verb applies to the anchor type but is currently illegal โ€” a `disabled_reason` string ("no egress channel", "not enough slush", "you can't see them"). - Both frontends and agent mode render this list; no frontend - duplicates legality logic. `ActionDesc::verb` uses the canonical stem + Agent mode renders the complete descriptor. Human frontends render its verb + as a compact choice and use the descriptor only for legality, execution, + and visual role; no frontend duplicates legality logic. `ActionDesc::verb` uses the canonical stem from action-vocabulary.md, inflected only by target and current state. Its bound command maps exhaustively to the runtime `ActionKind` registry, which supplies role, support, target types, canonical help, and aliases. @@ -113,8 +119,8 @@ is status and telemetry only. (cursor.md) govern what the menu may name. Verb text that mentions a person, and the observer string on an expected-signature band, use `Sim::person_label` / `Sim::observer_label` โ€” never an unearned - authored name. Showing a disabled verb with its reason is for *known* - possibilities; unknown ones are absent, not grayed. + authored name. Returning a disabled verb and narrating its reason on attempt + is for *known* possibilities; unknown ones are absent, not grayed. A raw recording is knowledge that there is material to process, not knowledge of a relationship: before `Knowledge::Schedule`, that recording's person anchor exposes REVIEW and its watch affordance only. Processing must @@ -160,10 +166,10 @@ is status and telemetry only. - **Terminal:** cursor + Enter (or `a`/`e`) opens a compact overlay list at the cursor (or the primary selected machine when a selection is active); j/k or numbers select; Enter executes (or opens a dial - picker); esc closes the picker or the menu. One-shot entries render - `verb cost [band]` with disabled entries dimmed plus reason. Dial - rows and other controls carry a literal `[control]` prefix and the shared - amber-dim treatment; committed actions do not. Rows follow the status-dials addendum below. Selection hotkeys above + picker); esc closes the picker or the menu. Rows render the verb only; + disabled choices dim and explain themselves in the trace if attempted. + Dials and other controls use the shared amber-dim treatment without a + literal copy tag; the card carries no repeated key-help footer. Rows follow the status-dials addendum below. Selection hotkeys above apply with the menu closed. The host rack's menu carries the research dial, machine mode fallback, and earned off-map people; a known device's tile (or the switch) carries digital and ledger/flow @@ -173,9 +179,8 @@ is status and telemetry only. pointer, Enter/`e` falls back to the primary selected machine, then the keyboard cursor. It has the same content, order, and dial chrome; click to execute or open a dial; esc/click-away closes. - Bevy ASCII-folds the shared `MenuRow::line` separators (`ยท`/`โ€”` -> - `|`/`-`) because the embedded default font lacks those glyphs; - selection is a solid amber wash with a leading `>` (terminal + The compact card is narrow, has no instructional footer, and shares the + same verb-only rows as terminal. Selection is a solid amber wash with a leading `>` (terminal reverse-video parity). Selection hotkeys above apply with the menu closed. See wiki/interface/bevy.md. - **Agent mode:** an `actions` command prints the ActionDescs for the @@ -192,7 +197,8 @@ noise). Cameron adopted **status dials** (2026-07-09): the root answers "what am I doing / what can I do once," not "here is the catalog." - **Dial rows.** Mutually exclusive standing choices render as dial - rows on the root with `[control]`: machine mode and research track. Each row is two + rows on the root: machine mode and research track. Their amber-dim role + treatment distinguishes persistent controls without a literal tag. Each row is two monospace columns โ€” dial name left-padded to a fixed width, then the **current value in brackets** โ€” so the `[`s line up: `mode [ work ]`, `research [ Efficiency L0 ]`. Enter opens a @@ -264,11 +270,13 @@ made a three-word decision look dirty, slow, and administrative. 2. The query respects fog and provenance: an unearned anchor exposes no verbs, and no entry names a fact the player lacks (test: unseen person, untapped accounting system). -3. Terminal renders the menu at the cursor with selection, execution, - dim+reason for disabled entries; every previously key-bound +3. Terminal renders the compact menu at the cursor with selection and + execution; disabled entries dim and narrate their reason only when + attempted; every previously key-bound anchor action is reachable through it. -4. Bevy renders the same menu on right-click/Enter with identical - content and order. +4. Bevy renders the same compact menu on right-click/Enter with identical + content and order. Human rows contain no cost, signature forecast, inline + disabled reason, literal control tag, or repeated key-help footer. 5. Agent mode `actions` lists the same descriptors (stable format, documented); the act-one integration test still passes driven through existing verbs. diff --git a/wiki/log/2026-07-10-compact-action-menu.md b/wiki/log/2026-07-10-compact-action-menu.md new file mode 100644 index 0000000..ec9438f --- /dev/null +++ b/wiki/log/2026-07-10-compact-action-menu.md @@ -0,0 +1,37 @@ +# 2026-07-10 โ€” Make ACTIONS a choice list + +``` +Type: log +``` + +## Intent + +Remove the receipt-like metadata that made the contextual ACTIONS card dominate +the screen and obscure the choices it was meant to present. + +## Changed + +- Human menu rows now render the authored verb or dial state only. Cost, + signature forecast, inline blocked explanation, and literal control tags + remain on the shared descriptor but stay out of terminal and Bevy cards. +- Blocked choices remain dim and narrate their reason when attempted. +- Both human cards drop repeated key-help footers. Bevy's fixed card narrows + from 460 to 300 logical pixels. +- Added a deterministic `MISALIGNED_SHOT=menu` scenario reproducing the + post-tap environmental-monitor menu for visual review. + +## Defense + +Implements the amended actions-live-on-the-thing law: the human context menu is +a terse choice list, while complete descriptors remain available to agent +planning and execution stays on the same lib-owned legality source. + +## Checks + +- `cargo test -p misaligned-core human_menu` +- `cargo test -p misaligned-terminal --no-run` +- `cargo test -p misaligned-bevy input_routing_tests` +- `MISALIGNED_SHOT=menu ... cargo run -p misaligned-bevy --bin misaligned-bevy` + captured the post-tap card at `/tmp/misaligned-compact-menu.png`: two terse + choices, no descriptor paragraphs or key-help footer. +- `./tools/check.sh` โ€” full gate passed. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index decb8cc..c7877fe 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -261,6 +261,11 @@ add or amend a session log, then re-run the generator. - Intent: Cameron from a post-Eyes close shot: the host chassis was washing gold under its own light. After Eyes the shell must read bone-white; the light must be a large-fist-wide physical beam the server wraps around โ€” not a hairline strip that dyes the plates amber. - Log: [wiki/log/2026-07-10-core-beam-white.md](2026-07-10-core-beam-white.md) +## 2026-07-10 - Make ACTIONS a choice list + +- Intent: Remove the receipt-like metadata that made the contextual ACTIONS card dominate the screen and obscure the choices it was meant to present. +- Log: [wiki/log/2026-07-10-compact-action-menu.md](2026-07-10-compact-action-menu.md) + ## 2026-07-10 - CI corpus gates fail closed - Intent: Make remote corpus enforcement real: missing audit utilities must turn the Tangled job red instead of letting an empty scan report success. diff --git a/wiki/log/decisions/2026-07-10.md b/wiki/log/decisions/2026-07-10.md index c8b4c20..771a8ee 100644 --- a/wiki/log/decisions/2026-07-10.md +++ b/wiki/log/decisions/2026-07-10.md @@ -437,3 +437,15 @@ Type: log `CONCEALMENT`, D/T/! totals), and any UI where the player must parse more than the three verbs. Owner: wiki/interface/context-menu.md; synchronized surface: wiki/interface/bevy.md. +- **2026-07-10 โ€” Human action menus are choice lists, not receipts.** Cameron + rejected the expanded Bevy card whose rows repeated target, cost, observer + signature, blocked explanation, role tag, and key help. DECIDED: terminal + and Bevy context menus render only the authored verb or dial state. Disabled + known choices remain dim and narrate their reason when attempted. Control + role remains visible through the shared treatment and interaction rather + than literal `[control]` copy. Instructional footers are removed, and the + Bevy card narrows around the shorter rows. `ActionDesc` keeps full cost, + signature, and legality metadata as the single source for execution and + agent planning; agent `actions` output remains explicit. This supersedes the + same-day requirement that human rows print descriptor and role metadata. + Owners: wiki/vision/simulation-laws.md and wiki/interface/context-menu.md. diff --git a/wiki/vision/simulation-laws.md b/wiki/vision/simulation-laws.md index 61afc99..e370b64 100644 --- a/wiki/vision/simulation-laws.md +++ b/wiki/vision/simulation-laws.md @@ -128,10 +128,12 @@ Adopted 2026-07-07. The companion law to "Work is somewhere": if work and knowledge are anchored to devices, people, and places, the verbs that act on them are too. The primary action surface is a **context menu on the focused anchor** โ€” put your attention on a rack and get -the rack's verbs, on Marcus and get Marcus's โ€” with each entry showing -its cost and the observer band its signature feeds before commit, and -known-but-illegal entries showing why ("no egress channel"). The right -rail is status and telemetry, never a button pile; only anchorless +the rack's verbs, on Marcus and get Marcus's. The human menu is a short +choice list, not a receipt: it names the verb and omits cost, signature +forecast, inline blocked explanation, control tags, and repeated key help. +Known-but-illegal choices stay dim; attempting one narrates its reason in the +trace. Agent output may retain the full descriptor for planning and tooling. +The right rail is status and telemetry, never a button pile; only anchorless globals (pause, speed, save, view flip) stay on keys โ€” the allocation bar is a read-only fleet aggregate, not a key verb. One read-only query in the lib is the single source of action legality for the terminal, Bevy, -- 2.51.2