diff --git a/crates/misaligned-bevy/src/main.rs b/crates/misaligned-bevy/src/main.rs index b498a57..2cd6228 100644 --- a/crates/misaligned-bevy/src/main.rs +++ b/crates/misaligned-bevy/src/main.rs @@ -91,10 +91,11 @@ const BUILD_ROUTE_CHOICE_WIDTH: f32 = 300.0; /// The hover grammar grows only when several actionable bodies share a tile. /// Width follows the union rather than reserving an empty HUD-sized strip. const MACHINE_VERB_BAR_WIDTH: f32 = 320.0; -/// A device receipt is a short attached stack, not one dashboard-width line. -/// Two hundred pixels fits the longest current signature line while keeping -/// the focused verb visually tied to its device. -const DEVICE_VERB_BAR_WIDTH: f32 = 200.0; +/// GUI focus has room to communicate. Device actions use a readable attached +/// explanation instead of compressing price, observer, channel, and band into +/// terminal-width telemetry. +const DEVICE_VERB_BAR_WIDTH: f32 = 340.0; +const DEVICE_RECEIPT_HEIGHT: f32 = 170.0; /// Air between the fixed mode grammar and the machine base it belongs to. const HOVER_VERB_BAR_GAP: f32 = 12.0; const DETECTION_ROWS: usize = 6; @@ -1748,8 +1749,8 @@ struct OverlayText; struct OpeningOverlay; #[derive(Component, Clone, Copy)] struct OpeningModeWord(MachineMode); -/// Root for the menu-closed hover verb bar: bare `WORK / THINK / LIE` on -/// machines or `TAP / TAKE` on devices. It has no panel chrome. +/// Root for the menu-closed attached action surface: machines retain the bare +/// `WORK / THINK / LIE` line; GUI device focus opens into an explanatory field. #[derive(Component)] struct HoverVerbBar; /// Small world-bound label for the exact target of the current attention @@ -1768,14 +1769,16 @@ enum HoverVerbFamily { Machine, Device, } -/// The two visual tiers of one exact pre-commit receipt. They remain separate -/// text nodes only for composition; both values still come directly from the -/// same ActionDesc. +/// Layout slots for the core-authored plain-language action explanation. #[derive(Component, Clone, Copy, PartialEq, Eq)] enum HoverVerbReceiptText { Cost, - Signature, + Notice, + Attention, + ObserverState, } +#[derive(Component)] +struct HoverVerbReceipt; #[derive(Component)] struct HoverVerbWord { @@ -1831,20 +1834,17 @@ impl HoverVerbTarget { } fn height(self) -> f32 { - // Device receipts use two calm lines: the price, then its shadow. - // Combined machine/device tiles retain both verb families without - // letting the receipt hang through the target anchor. if self.machine.is_some() && self.device.is_some() { - 86.0 + 201.0 } else if self.device.is_some() { - 58.0 + DEVICE_RECEIPT_HEIGHT } else { 32.0 } } fn clearance(self) -> f32 { - if self.device.is_some() { 12.0 } else { 26.0 } + if self.device.is_some() { 18.0 } else { 26.0 } } } /// The context-menu root node (wiki/interface/context-menu.md), absolute and @@ -9268,6 +9268,7 @@ fn setup_ui(mut commands: Commands) { ..default() }, BackgroundColor(Color::NONE), + BorderColor::all(Color::NONE), Visibility::Hidden, GlobalZIndex(40), HoverVerbBar, @@ -9315,11 +9316,11 @@ fn setup_ui(mut commands: Commands) { bar.spawn(( Node { width: Val::Percent(100.0), - height: Val::Px(20.0), + height: Val::Px(24.0), flex_direction: FlexDirection::Row, align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - column_gap: Val::Px(9.0), + justify_content: JustifyContent::FlexStart, + column_gap: Val::Px(10.0), ..default() }, Visibility::Visible, @@ -9332,7 +9333,7 @@ fn setup_ui(mut commands: Commands) { line.spawn(( Text::new(label), TextFont { - font_size: 15.0, + font_size: 20.0, ..default() }, TextColor(DIM), @@ -9350,37 +9351,74 @@ fn setup_ui(mut commands: Commands) { bar.spawn(( Node { width: Val::Percent(100.0), - height: Val::Px(28.0), + height: Val::Px(100.0), flex_direction: FlexDirection::Column, - align_items: AlignItems::Center, - justify_content: JustifyContent::Center, - row_gap: Val::Px(1.0), + align_items: AlignItems::FlexStart, + justify_content: JustifyContent::FlexStart, + row_gap: Val::Px(3.0), ..default() }, Visibility::Visible, + HoverVerbReceipt, )) .with_children(|line| { line.spawn(( Text::new(""), TextFont { - font_size: 8.0, + font_size: 11.0, ..default() }, - TextColor(scaled(DIM, 0.76)), - TextLayout::new_with_justify(Justify::Center), + TextColor(scaled(DIM, 0.86)), + TextLayout::new_with_justify(Justify::Left), Visibility::Visible, HoverVerbReceiptText::Cost, )); + line.spawn(( + Text::new("WHAT THIS RISKS"), + TextFont { + font_size: 8.0, + ..default() + }, + TextColor(scaled(DIM, 0.52)), + TextLayout::new_with_justify(Justify::Left), + Node { + margin: UiRect::top(Val::Px(5.0)), + ..default() + }, + Visibility::Visible, + )); line.spawn(( Text::new(""), TextFont { - font_size: 9.0, + font_size: 14.0, ..default() }, - TextColor(scaled(DIM, 0.58)), - TextLayout::new_with_justify(Justify::Center), + TextColor(scaled(BONE, 0.88)), + TextLayout::new_with_justify(Justify::Left), Visibility::Visible, - HoverVerbReceiptText::Signature, + HoverVerbReceiptText::Notice, + )); + line.spawn(( + Text::new(""), + TextFont { + font_size: 11.0, + ..default() + }, + TextColor(scaled(DIM, 0.88)), + TextLayout::new_with_justify(Justify::Left), + Visibility::Visible, + HoverVerbReceiptText::Attention, + )); + line.spawn(( + Text::new(""), + TextFont { + font_size: 10.0, + ..default() + }, + TextColor(scaled(DIM, 0.68)), + TextLayout::new_with_justify(Justify::Left), + Visibility::Visible, + HoverVerbReceiptText::ObserverState, )); }); }); @@ -9750,16 +9788,35 @@ fn render_hover_verb_bar( camera_q: Query<(&Camera, &GlobalTransform), With>, camera3_q: RealCameraQuery, mut grammar: Query< - (&mut Node, &mut Visibility), + ( + &mut Node, + &mut BackgroundColor, + &mut BorderColor, + &mut Visibility, + ), ( With, Without, Without, + Without, ), >, mut groups: Query< - (&HoverVerbGroup, &mut Visibility), - (Without, Without), + (&HoverVerbGroup, &mut Node, &mut Visibility), + ( + Without, + Without, + Without, + ), + >, + mut receipt_group: Query< + (&mut Node, &mut Visibility), + ( + With, + Without, + Without, + Without, + ), >, mut words: Query< (&HoverVerbWord, &mut Text, &mut TextColor, &mut Visibility), @@ -9779,7 +9836,7 @@ fn render_hover_verb_bar( ), >, ) { - let Ok((mut node, mut visibility)) = grammar.single_mut() else { + let Ok((mut node, mut background, mut border, mut visibility)) = grammar.single_mut() else { return; }; let Ok(window) = windows.single() else { @@ -9824,6 +9881,23 @@ fn render_hover_verb_bar( node.height = Val::Px(target.height()); node.left = Val::Px(position.x); node.top = Val::Px(position.y); + if target.device.is_some() { + node.align_items = AlignItems::FlexStart; + node.justify_content = JustifyContent::FlexStart; + node.padding = UiRect::axes(Val::Px(16.0), Val::Px(14.0)); + node.border = UiRect::left(Val::Px(2.0)); + node.row_gap = Val::Px(5.0); + background.0 = Color::srgba(0.008, 0.010, 0.015, 0.92); + *border = BorderColor::all(Color::srgba(0.33, 0.35, 0.36, 0.62)); + } else { + node.align_items = AlignItems::Center; + node.justify_content = JustifyContent::Center; + node.padding = UiRect::ZERO; + node.border = UiRect::ZERO; + node.row_gap = Val::Px(5.0); + background.0 = Color::NONE; + *border = BorderColor::all(Color::NONE); + } *visibility = Visibility::Visible; let current_mode = target @@ -9833,13 +9907,10 @@ fn render_hover_verb_bar( .device .map(|id| device_hover_verbs(&game.sim, id)) .unwrap_or_default(); - // The pre-commit receipt for the leading device verb: cost + expected - // signature + observer + band from ActionDesc unmodified - // (digital-read.md criterion 2); emission-free verbs say so. The read is - // DIGITAL's treatment; REAL stays deferred, so the receipt is blank there. - let receipt_parts = (!mode.material) - .then_some(target.device) - .flatten() + // The renderer-neutral ActionDesc writes the plain-language explanation; + // the GUI gives it more room than compact terminal/agent rows. + let receipt_read = target + .device .and_then(|id| { game.sim .available_actions(Anchor::Device(id)) @@ -9851,19 +9922,18 @@ fn render_hover_verb_bar( ) }) }) - .map(|a| { - ( - ascii_ui(&a.cost.label()), - ascii_ui(&a.signature_label().unwrap_or_else(|| "no signature".into())), - ) - }) - .unwrap_or_default(); + .map(|action| action.receipt_read()); for (part, mut receipt_text) in receipt.iter_mut() { - receipt_text.0 = match part { - HoverVerbReceiptText::Cost => receipt_parts.0.as_str(), - HoverVerbReceiptText::Signature => receipt_parts.1.as_str(), - } - .into(); + receipt_text.0 = receipt_read + .as_ref() + .map(|read| match part { + HoverVerbReceiptText::Cost => read.cost.as_str(), + HoverVerbReceiptText::Notice => read.notice.as_str(), + HoverVerbReceiptText::Attention => read.attention.as_str(), + HoverVerbReceiptText::ObserverState => read.observer_state.as_str(), + }) + .map(ascii_ui) + .unwrap_or_default(); } let mut device_slots = Vec::new(); for verb in device_verbs { @@ -9872,11 +9942,21 @@ fn render_hover_verb_bar( } device_slots.push(verb); } - for (group, mut group_visibility) in groups.iter_mut() { + if let Ok((mut receipt_node, mut receipt_visibility)) = receipt_group.single_mut() { + let shown = target.device.is_some(); + receipt_node.display = if shown { Display::Flex } else { Display::None }; + *receipt_visibility = if shown { + Visibility::Visible + } else { + Visibility::Hidden + }; + } + for (group, mut group_node, mut group_visibility) in groups.iter_mut() { let shown = match group.family { HoverVerbFamily::Machine => target.machine.is_some(), HoverVerbFamily::Device => target.device.is_some(), }; + group_node.display = if shown { Display::Flex } else { Display::None }; *group_visibility = if shown { Visibility::Visible } else { @@ -9911,7 +9991,7 @@ fn render_hover_verb_bar( Visibility::Hidden }; color.0 = if word.family == HoverVerbFamily::Device && label != "/" { - DIM + scaled(BONE, 0.86) } else { machine_verb_color(active, control_hint, label) }; @@ -10000,7 +10080,7 @@ mod hover_verb_bar_tests { } #[test] - fn device_receipt_is_a_bounded_stack_clear_of_its_anchor() { + fn device_explanation_has_breathing_room_and_clears_its_anchor() { let target = HoverVerbTarget { machine: None, device: Some(3), @@ -10017,7 +10097,9 @@ mod hover_verb_bar_tests { ); assert_eq!(target.width(), DEVICE_VERB_BAR_WIDTH); - assert_eq!(target.height(), 58.0); + assert_eq!(target.height(), 170.0); + assert!(target.width() >= 320.0, "GUI copy gets sentence width"); + assert!(target.height() >= 160.0, "GUI copy gets vertical rhythm"); assert_eq!(position.y + target.height(), anchor.y - target.clearance()); assert_eq!(position.x + target.width(), anchor.x - HOVER_VERB_BAR_GAP,); } diff --git a/crates/misaligned-core/src/actions.rs b/crates/misaligned-core/src/actions.rs index eaae82c..9a3ca75 100644 --- a/crates/misaligned-core/src/actions.rs +++ b/crates/misaligned-core/src/actions.rs @@ -800,6 +800,26 @@ impl ActionCost { ActionCost::Plot { thought, .. } => format!("{thought:.2} T"), } } + + /// Expanded human-facing copy for a GUI focus read. The compact label + /// remains stable for terminal and agent rows; this form spends words so + /// a visual surface does not have to decode `T`, signs, or standing-rate + /// syntax on the player's behalf. + pub fn player_label(&self) -> String { + match self { + ActionCost::Free => "NO COST".into(), + ActionCost::Thought(n) => format!("USES {n:.2} THOUGHT"), + ActionCost::StandingThought(n) => { + format!("USES {n:.2} THOUGHT EACH SECOND") + } + ActionCost::Slush(n) => format!("SPENDS ${n}"), + ActionCost::Gain(n) => format!("GAINS ${n}"), + ActionCost::Plot { thought, money } if *money > 0 => { + format!("USES {thought:.2} THOUGHT AND MOVES ${money}") + } + ActionCost::Plot { thought, .. } => format!("USES {thought:.2} THOUGHT"), + } + } } /// The expected signature of a verb, expressed as the observer band it @@ -868,6 +888,16 @@ pub struct ActionDesc { pub automate: Option, } +/// Plain-language pieces of one pre-commit action explanation. Core owns the +/// meaning and the GUI owns only spacing, type, and hierarchy. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ActionReceiptRead { + pub cost: String, + pub notice: String, + pub attention: String, + pub observer_state: String, +} + impl ActionDesc { /// The receipt's signature slot, formatted once for every frontend /// (digital-read.md's one-receipt-model). A plot's emissions are @@ -887,6 +917,37 @@ impl ActionDesc { } } + pub fn receipt_read(&self) -> ActionReceiptRead { + let (notice, attention, observer_state) = if let Some(signature) = &self.signature { + ( + format!("{} MAY NOTICE", signature.observer.to_ascii_uppercase()), + format!( + "{} ATTENTION {:+}", + signature.kind.name().to_ascii_uppercase(), + signature.size + ), + format!("CURRENTLY {}", signature.band.name().to_ascii_uppercase()), + ) + } else if matches!( + self.command.kind(), + ActionKind::StartPlot | ActionKind::ChoosePlot + ) { + ( + "ATTENTION FOLLOWS EACH STEP".into(), + String::new(), + String::new(), + ) + } else { + ("NO ADDED ATTENTION".into(), String::new(), String::new()) + }; + ActionReceiptRead { + cost: self.cost.player_label(), + notice, + attention, + observer_state, + } + } + pub fn enabled(&self) -> bool { self.disabled_reason.is_none() } @@ -4208,6 +4269,28 @@ mod tests { ); } + #[test] + fn expanded_receipt_says_when_an_action_adds_no_cost_or_attention() { + let action = ActionDesc { + verb: "untap".into(), + command: ActionCommand::UntapDevice(3), + cost: ActionCost::Free, + signature: None, + disabled_reason: None, + automate: None, + }; + + assert_eq!( + action.receipt_read(), + ActionReceiptRead { + cost: "NO COST".into(), + notice: "NO ADDED ATTENTION".into(), + attention: String::new(), + observer_state: String::new(), + } + ); + } + #[test] fn available_actions_never_exposes_registry_stubs() { let s = sim(); @@ -4277,6 +4360,14 @@ mod tests { "Network feeds the IT observer (gated label): {}", sig.observer ); + let read = tap.receipt_read(); + assert_eq!(read.cost, "USES 0.25 THOUGHT"); + assert_eq!(read.notice, "THE IT MAY NOTICE"); + assert_eq!(read.attention, "NETWORK ATTENTION +3"); + assert_eq!( + read.observer_state, + format!("CURRENTLY {}", sig.band.name().to_ascii_uppercase()) + ); assert!( !acts .iter() diff --git a/wiki/interface/context-menu.md b/wiki/interface/context-menu.md index 688bb2f..f0ae7e9 100644 --- a/wiki/interface/context-menu.md +++ b/wiki/interface/context-menu.md @@ -285,11 +285,12 @@ falsely suggests that the next number keys commit the device verbs. readout, cost, signature, or prose subtitle. In particular, WORK does not expand to “day job.” The three mode words remain the primary read; their small number prefixes clarify direct input without becoming another legend. -- DIGITAL's device line carries the one exception owned by digital-read.md: a - focused device verb places its exact price above its exact signature in a - narrow centered stack. This is not panel chrome or another action row. It - keeps the consequence attached without letting a long receipt collide with - the device glyph, focus label, or reach line. +- Bevy's device line carries the one exception owned by digital-read.md: a + focused device verb opens into a spacious attached explanation. The action + is a large heading; cost uses the full resource name; WHAT THIS RISKS leads + the named observer, attention channel/change, and current band on separate + left-aligned lines. It is not another action row. Machine modes remain the + bare line above; terminal and agent rows remain compact. - `1` / `2` / `3` (including numpad) remain the immediate commit path for WORK / THINK / LIE on the resolved computer. This overlay does not create a second target or a Bevy-only action; it renders the same pointer → selection @@ -297,7 +298,8 @@ falsely suggests that the next number keys commit the device verbs. - The treatment is identical over the material and flat-sensorium views. It belongs to the machine, not to either rendering dialect. -Rejected: a boxed context card; `RACK 03` or `OWNED` headings; mode subtitles +Rejected for the machine-mode grammar: a boxed context card; `RACK 03` or +`OWNED` headings; mode subtitles such as `DAY JOB`, `RESEARCH`, or `CONCEALMENT`; D/T/! counters; explanatory rows; any amber menu word—including panic exits and operator recommendations; a generic HUD or persistent sidebar legend. Those @@ -379,11 +381,13 @@ H4. The overlay reads the same machine target and current `WorkGrid` mode used H5. Terminal retains its ordinary dim `1-3 modes` control hint regardless of the focused machine's current mode. THINK does not substitute a special `WORK` exit sentence or amber control line. -H6. A DIGITAL device receipt is bounded to the device-width stack: price on - the first line, exact signature/observer/band on the second. The stack ends - above and left of the target anchor at both supported window sizes; the - focused device label is right-anchored left of its glyph so graph lines do - not strike through it. The machine-only line remains unchanged. +H6. A Bevy device explanation receives at least 320 logical pixels of sentence + width and 160 pixels of vertical rhythm. Action, full-word cost, WHAT THIS + RISKS, named observer, attention change, and current band each have a + distinct line and hierarchy at both supported window sizes. The field ends + above and left of the target anchor; the focused device label is + right-anchored left of its glyph so graph lines do not strike through it. + The machine-only line and compact non-GUI projections remain unchanged. ## Addendum (2026-07-08): events carry you to the thing diff --git a/wiki/interface/digital-read.md b/wiki/interface/digital-read.md index 007e892..6253edb 100644 --- a/wiki/interface/digital-read.md +++ b/wiki/interface/digital-read.md @@ -18,10 +18,12 @@ Status note: IMPLEMENTED. Adopted 2026-07-15 from the Fable playtest causal prose. The agent `read` verb prints the same sentences. - **Reticle receipt (2).** The hover verb bar shows the focused verb's cost + `ExpectedSignature` (observer + band) straight off `ActionDesc`, - or `no signature`. Bevy composes those exact values as a bounded two-tier - stack—price above shadow—rather than one line running through the focused - object. The device's world label ends to the left of its glyph so live - reach edges remain readable. + or no added attention. `ActionDesc::receipt_read` expands those fields for + human surfaces: `USES 0.25 THOUGHT`, `THE IT MAY NOTICE`, `NETWORK + ATTENTION +3`, `CURRENTLY CURIOUS`. Bevy gives that explanation a spacious + 340×170 attached field with full-size, left-aligned hierarchy; it does not + compress GUI copy to terminal width. The device's world label ends to the + left of its glyph so live reach edges remain readable. - **World sentences and trace (3-4).** Earned-anchor sentences render in world space above their tile; anchor-less ones fall to a rail READ block that collapses same-cause starving sinks. Pending trace records sit at @@ -40,9 +42,11 @@ Status note: IMPLEMENTED. Adopted 2026-07-15 from the Fable playtest - **Cold-player sweep (8).** The capture set was reviewed visually (not just structurally); evidence under `assets/reference/art-direction/b1-digital-read-*.png`. - REAL stays deferred: the drill-tier card and receipt render in DIGITAL - only. Per-amendment history (D1-D10) is in the dated `wiki/log/` entries - from 2026-07-15 onward. + REAL stays deferred for standing world sentences and the drill-tier card. + The focused action explanation is interaction UI shared by both Bevy + dialects; terminal and agent rows retain their compact projection. + Per-amendment history (D1-D10) is in the dated `wiki/log/` entries from + 2026-07-15 onward. Stage: B1 — The Basement Work order: digital-read Work priority: 27 @@ -112,11 +116,15 @@ that perception rendered. `no signature` when the verb emits nothing. A blocked verb renders its blocker as world state on the anchor that blocks it (`needs: security segment — the switch bridges it`), never as a grayed catalog row. The - player never commits an act whose shadow they have not been shown. In Bevy, - price and signature are two centered lines in one fixed-width attached - stack; line breaking changes no words or values. The stable device label is - right-anchored immediately left of the glyph, leaving outgoing reach lines - clear instead of drawing them through the label. + player never commits an act whose shadow they have not been shown. Compact + terminal/agent rows may retain symbolic labels; Bevy uses the shared + `ActionDesc::receipt_read` to spend words: full `THOUGHT`, who may notice, + which attention channel changes by how much, and that observer's current + state. Those sentences sit left-aligned in a spacious attached field with + deliberate gaps and type hierarchy. The GUI must not optimize for the + smallest possible footprint when that makes causality hard to read. The + stable device label is right-anchored immediately left of the glyph, leaving + outgoing reach lines clear instead of drawing them through the label. - **Trace on the wire.** Pending one-shot signatures render as discrete records sitting on their emitting anchors/carriers, aging toward the observer who samples that channel; scrub visibly consumes them. The @@ -177,9 +185,11 @@ composition. 2. Focusing any executable verb in Bevy shows cost + expected signature + observer + band at the reticle before commitment; verbs with no emission say so; the row's data comes from `ActionDesc` unmodified. The - device treatment fits as a bounded price-over-signature stack at both the - 1280x720 default and 960x540 minimum, and the device's focused label ends - before its outgoing reach lines begin. + device treatment uses a 340×170 explanation field, full words, left-aligned + hierarchy, and visible separation between action, cost, risk, observer, + attention channel, and current band at both the 1280x720 default and + 960x540 minimum. The device's focused label ends before its outgoing reach + lines begin. 3. Standing anchor sentences render for rising states and only rising states: a nominal machine says nothing; a starving reservoir, a held choice, a deadline, and an observer band change each surface at their diff --git a/wiki/log/2026-07-17-device-receipt-breathing-room.md b/wiki/log/2026-07-17-device-receipt-breathing-room.md new file mode 100644 index 0000000..66b33d4 --- /dev/null +++ b/wiki/log/2026-07-17-device-receipt-breathing-room.md @@ -0,0 +1,72 @@ +# Device explanations get breathing room + +``` +Type: log +``` + +The first receipt-polish pass solved collision by shrinking and stacking the +text. Cameron corrected the premise: “I don't want it to be compact. If we're +in the GUI we can show more like this. It's hard to read is what I'm saying. +Don't make things compact. Let them breathe, like communicate information.” + +The category error was optimizing footprint instead of comprehension. A GUI +does not need to inherit the terminal row's abbreviation budget. The focused +device action now occupies a 340×170 logical-pixel attached field with a quiet +black body and one gray rule. Its hierarchy reads: + +```text +TAP +USES 0.25 THOUGHT + +WHAT THIS RISKS +THE IT MAY NOTICE +NETWORK ATTENTION +3 +CURRENTLY CURIOUS +``` + +`ActionDesc::receipt_read` owns those plain-language pieces in core. Bevy only +lays them out, so the larger surface does not become a second causal model. +Compact `ActionCost::label` and `ExpectedSignature::label` remain available to +terminal and agent rows; the GUI form expands `T` to THOUGHT and separates the +observer, channel, amount, and current state. Verbs with no signature say NO +ADDED ATTENTION. Plot actions continue to say that attention follows their +steps. + +The previous right-anchored world label is retained: `SWITCH / REACHABLE` ends +left of the glyph and the graph edge leaves on the right. The correction adds +space and communication without reintroducing collision. No action legality, +cost, signature value, observer state, graph topology, or input path changes. + +## Played evidence + +The deterministic `read-receipt` scenario was captured after the final code +mutation at the default and supported minimum GUI sizes. Both exact PNGs were +opened and read. The full action, resource name, risk question, observer, +attention change, and current band are legible without clipping; the +explanation occupies a deliberate part of the available world canvas rather +than collapsing into telemetry. The switch label and outgoing cyan line remain +clear at both sizes. + +- Default 1280x720 logical / 2560x1440 Retina PNG: + `c6c3284c3c6b3f817a9efcf8e343655a1de7cddd134b41a950a6722de34fcc12` +- Minimum 960x540 logical / 1920x1080 Retina PNG: + `3a2f9fcbaaad74c8822e235d106f7b18135185a3c20584ad79a635d8f94cef1a` + +Observed commands: + +```sh +tools/bevy-headless.sh read-receipt \ + --output /tmp/misaligned-device-explanation-final-default.png + +MISALIGNED_SHOT_SIZE=minimum tools/bevy-headless.sh read-receipt \ + --output /tmp/misaligned-device-explanation-final-minimum.png +``` + +Focused defenses pin the shared plain-language projection, at least 320 pixels +of sentence width, at least 160 pixels of vertical rhythm, anchor clearance, +and the separate right-anchored device label. + +Defense: `wiki/interface/digital-read.md` criterion 2 now distinguishes +meaning parity from identical compression. The GUI spends space to explain the +one consequential action in focus; stable world state remains quiet under +`wiki/interface/superhuman-operability.md`. diff --git a/wiki/log/2026-07-17-device-receipt-polish.md b/wiki/log/2026-07-17-device-receipt-polish.md index bc67b60..6a8adbc 100644 --- a/wiki/log/2026-07-17-device-receipt-polish.md +++ b/wiki/log/2026-07-17-device-receipt-polish.md @@ -4,6 +4,12 @@ Type: log ``` +> **Superseded later the same day:** this pass correctly separated the device +> label from live graph lines, but optimized the receipt for compactness when +> Cameron was asking for readability. The current binding treatment is the +> spacious GUI explanation recorded in +> `2026-07-17-device-receipt-breathing-room.md`. + Cameron's GUI capture exposed a compositional failure in the DIGITAL focus tier. `TAP`, its `0.25 T | Network 3 -> the IT [Curious]` receipt, the selected switch label, and a live ReachNet edge all occupied one loose horizontal band. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 6a80d0a..1f8165a 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -101,6 +101,11 @@ add or amend a session log, then re-run the generator. - Intent: (see session log) - Log: [wiki/log/2026-07-17-device-receipt-polish.md](2026-07-17-device-receipt-polish.md) +## 2026-07-17 - Device explanations get breathing room + +- Intent: (see session log) +- Log: [wiki/log/2026-07-17-device-receipt-breathing-room.md](2026-07-17-device-receipt-breathing-room.md) + ## 2026-07-16 - The migration ladder collects the rider - Intent: (see session log) diff --git a/wiki/log/decisions/2026-07-17.md b/wiki/log/decisions/2026-07-17.md index a7c9cf5..8fc6518 100644 --- a/wiki/log/decisions/2026-07-17.md +++ b/wiki/log/decisions/2026-07-17.md @@ -49,3 +49,12 @@ Type: log device-width stack. End the device's focused world label immediately left of its glyph so graph edges begin cleanly instead of striking through text. No panel or extra state. Owners: digital-read.md and context-menu.md. +- **2026-07-17 — GUI device explanations get breathing room, not compactness.** + Cameron corrected the prior treatment immediately: the problem was that the + receipt was hard to read, not that it consumed too much space. Terminal and + agent rows may stay compact; Bevy should use its visual room to communicate. + The focused device action is now a spacious attached field with a large verb, + full-word cost, WHAT THIS RISKS, named observer, attention change, and + current band on separate left-aligned lines. The previous compact-stack + decision is superseded; its line-clear device label remains valid. Owners: + digital-read.md and context-menu.md.