diff --git a/context/code/core.md b/context/code/core.md index 2b2023e6..3ea1b81e 100644 --- a/context/code/core.md +++ b/context/code/core.md @@ -1790,11 +1790,31 @@ beside the key and never in its place: - The browser's render model keeps its field names and carries the display in them (`RenderPageAxis.cat`, `RenderCatRow.label`), so `web/` is unchanged. -Still keys: `GridLayout::row_label`/`col_label` (commands and the status line -match on them), `edit_text`, the active-view marker, and everything status -messages and the view title say (improvise-cq1q.13). A display change alone does -not move `layout_fingerprint`, so it resets no cursor; no production path changes -one yet. +Still keys: `GridLayout::row_label`/`col_label` (commands match on them), +`edit_text` and the active-view marker. A display change alone does not move +`layout_fingerprint`, so it resets no cursor; no production path changes one yet. + +**What the third slice covers (improvise-cq1q.13):** the view title and every +message that names a thing which exists. The title is the active view's display: +the TUI reads `app.view().display()`, the browser `ViewListCache::display_of` +(key when the list lacks the view). The statuses are the axis change (panel and +tile bar), `Filter:`, `Deleted category/item`, the formula-result and non-item +refusals, and the item-add prompt. Both ends say them through the same +`viewmodel` builders, so a wording is not spelled twice. The host reads +`Model::category_display`/`item_display`; the client reads +`CategoryTreeCache::category_display`/`item_display`. Each falls back to the key. + +**A message that names something which does not exist says what was typed.** +`Unknown category 'x'`, `No view named "x"`, `View 'x' not found` and the +`workbook.rs` errors are lookup misses: there is no display to find, and the typed +text is what the person needs to correct. `Added category "x"` is optimistic and +names what was just typed, whose display equals its key at creation. A stray +coordinate in `non_item_refusal` is a key for the same reason; only the category +beside it is a display. **A display must never reach a lookup**: the item-add +mode keeps the category key (`AppMode::item_add_shown(key, shown)`) and the prompt +alone carries the display. A formula target has no display path yet +(`set_item_display` needs an explicit `_Measure` item), so `derived_refusal` reads +`item_display("_Measure", target)` and in practice says the key. `AxisFrame::cat_displays` is projected and validated, but nothing paints it yet (the corner label has no consumer); that is a gap to close when one does, not a diff --git a/context/test-evidence/core.md b/context/test-evidence/core.md index 38d50101..4fb6c30c 100644 --- a/context/test-evidence/core.md +++ b/context/test-evidence/core.md @@ -2143,3 +2143,75 @@ The display "Eastern Seaboard" against the key "E" gives 17, not the 4 minimum. Rival: `col_groups` labels a run with `g.name`. The browser test for group displays covered row groups only; this one folds a column frame whose group "Q1" is shown as "First quarter" and fails on the key. + +### draw::content_tests::the_grid_title_names_the_views_display_not_its_key + +improvise-cq1q.13, TUI half. The view is displayed "Quarterly overview of regional +sales" under the key "Default", and the whole frame is drawn through `draw`. Rival: +`draw_content` passes `app.view_state.active_view` to the widget; the title reads +`View: Default` and this test fails on both assertions. + +### render::tests::the_title_names_the_active_views_display_not_its_key + +The browser half. Rival: `render_model` takes `view_name` from +`state.view.active_view`; the test fails. The second half is the control: a view +the list does not hold falls back to its key rather than to an empty title. + +### resolve::display_tests::a_panel_status_names_what_a_person_sees_not_the_keys + +The browser resolver's panel statuses: axis cycle, filter, delete category, delete +item. Displays are 36 to 38 characters against keys of 4 to 6. Rivals, each run: +the cycle status passes the key; the filter composes `SetPageSelection` with the +item's display; the item removal names the display. Each fails here, the last two +on the `Region/North` and `RemoveItem` assertions that pin the lookup to keys. + +### resolve::display_tests::a_tile_status_names_the_display + +Rival: the tile route passes `tile.category`; fails on the key. + +### resolve::display_tests::the_item_prompt_names_the_display_and_the_mode_keeps_the_key + +Rival: the mode is built with the display as its category +(`item_add_shown(shown, ..)`); the prompt reads right and the commit would add to +a category that does not exist. The test fails on `category == "Region"`. + +### resolve::display_tests::a_refused_cell_names_the_displays_it_shows + +Rival: the non-item refusal gets the category key. The derived arm is covered by +the item display for `_Measure`; the stray coordinate stays a key because it names +no item and has no display. + +### command::cmd::panel::tests::a_panel_status_names_what_a_person_sees_not_the_keys + +The host half of the panel statuses and the item-add prompt. Rivals, each run: the +axis status passes `cat_name` (fails); the filter status passes the item key +(fails); the item-add mode is built from the display (fails on the +`category: "Type"` assertion). + +### command::cmd::tile::tests::a_tile_status_names_the_display_not_the_key + +Rival: the status formats `name`. It fails on both the present display and the +absent key. + +### command::cmd::cell::tests::a_refusal_names_the_formulas_display_not_its_key + +A formula target has no display path today: `set_item_display` needs an explicit +`_Measure` item, so the fixture adds one named like the target. Rival: +`derived_refusal` skips the lookup; the test fails on "Operating profit...". + +### ui::app::non_item_key_tests::a_non_item_refusal_names_the_categorys_display + +Rival, run once per site: the refusal in `EditOrDrill` (the `i` key) and the one in +`CmdContext::value_cell_key` (the `p` key) each pass the key. Each fails here. + +### ui::app::non_item_key_tests::a_commit_on_a_non_item_cell_names_the_categorys_display + +The third site, `uneditable` in `cmd/core.rs`, which only a commit reaches once an +editor is open (`:enter-mode editing`). Rival: it passes the key; the two tests +above stay green and this one fails. + +### ui::app::non_item_key_tests::a_refused_wire_write_names_the_displays_it_shows + +The wire's own refusals in `Grids::unwritable`: the non-item arm and the formula +arm (once a third copy of the derived wording, now `derived_refusal`). Rivals: the +category key; a model with no display for the formula. Each fails. diff --git a/crates/improvise-command/src/ui/app.rs b/crates/improvise-command/src/ui/app.rs index 60fd36f1..d655b163 100644 --- a/crates/improvise-command/src/ui/app.rs +++ b/crates/improvise-command/src/ui/app.rs @@ -5721,6 +5721,21 @@ mod non_item_key_tests { assert!(status.ends_with(&refusal("Mars")), "{status}"); } + #[test] + fn a_commit_on_a_non_item_cell_names_the_categorys_display() { + const REGION: &str = "Sales territory of the whole company"; + let mut app = paged_app("Mars"); + let region = app.model_state.workbook.model.category_mut("Region"); + region.unwrap().set_display(REGION); + press(&mut app, ":enter-mode editing\n"); + press(&mut app, "5\n"); + let said = &app.view_state.status_msg; + assert!( + said.ends_with(&improvise_protocol::non_item_refusal(REGION, "Mars")), + "{said}" + ); + } + /// `uneditable` declines a non-item cell, so an advance re-opens no editor /// on one (#265 meets #277). Evidence: `context/test-evidence/command.md`. #[test] diff --git a/crates/improvise-protocol/src/render.rs b/crates/improvise-protocol/src/render.rs index fd1c7084..8072f005 100644 --- a/crates/improvise-protocol/src/render.rs +++ b/crates/improvise-protocol/src/render.rs @@ -68,10 +68,8 @@ pub enum CellId { NonItem { category: String, item: String }, } -/// Why a cell whose coordinate names no item takes no write. One wording for the -/// projection's decline and the host's refusal (improvise-gk51). -/// `category` is what a person sees for it; `item` is the stray coordinate as -/// stored, which has no display because it names no item. +/// Why a cell naming no item takes no write (improvise-gk51). `category` is a +/// display; `item` is the stray key, which has none (`core.md` §21). pub fn non_item_refusal(category: &str, item: &str) -> String { format!("{item} is no item of {category}, so there is no one cell to write") } diff --git a/crates/improvise-protocol/src/viewmodel.rs b/crates/improvise-protocol/src/viewmodel.rs index 2d456775..bbb66012 100644 --- a/crates/improvise-protocol/src/viewmodel.rs +++ b/crates/improvise-protocol/src/viewmodel.rs @@ -37,9 +37,8 @@ impl CatTreeEntry { } } -/// What a moved axis says on the status line. One home: the tile bar and the -/// category panel are two routes to one operation — improvise-25bf. Every -/// name here is a display: the status is read by a person, never matched on. +/// What a moved axis says on the status line: one home for the tile bar and the +/// panel (improvise-25bf). Takes displays; `core.md` §21. pub fn axis_change_status(category: &str, axis: Axis) -> String { format!("{category} → {}", axis.plain_label()) }