From 59cc6720b56b14ee480194d768d183578f6a5eb4 Mon Sep 17 00:00:00 2001 From: Edward Langley Date: Sat, 19 Sep 2026 21:35:54 -0700 Subject: [PATCH] feat(browser): the page draws the mode's hint instead of spelling keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `?` now enters `Help` — the keymap says so and this client resolves `enter-mode` — but the page has no help overlay: `src/ui/help.rs` is 600 lines of ratatui and nothing shared carries its content. Landing the key without this would have swapped the mode label to HELP over an unchanged grid, which looks broken rather than unfinished. `AppMode::hint_text()` already exists in the protocol crate and is what the TUI's footer draws, so `RenderModel::hint` relays it. Every mode now states its own keys in the browser, the page spells none of them, and the modes this client can enter but not yet draw say what they are. A real help overlay needs the content moved into the shared layer first, which is the same move design-principles §5 asks for and is filed rather than faked. `web/*` is unrun; `node --check` only. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH --- crates/improvise-wasm-client/src/render.rs | 15 +++++++++++++++ web/app.js | 4 ++++ web/index.html | 1 + web/style.css | 5 +++++ 4 files changed, 25 insertions(+) diff --git a/crates/improvise-wasm-client/src/render.rs b/crates/improvise-wasm-client/src/render.rs index 13ef10e9..6a564f43 100644 --- a/crates/improvise-wasm-client/src/render.rs +++ b/crates/improvise-wasm-client/src/render.rs @@ -56,6 +56,15 @@ pub struct RenderModel { pub categories: Vec, /// Whether the panel is open, so the page does not decide. pub category_panel_open: bool, + /// The keys this mode offers, off `AppMode::hint_text()` — the same + /// string the TUI's footer draws. + /// + /// The page shows it rather than spelling any keys of its own, which is + /// what keeps a second front end from growing its own idea of the + /// bindings (design-principles §5). It also makes the modes this client + /// can *enter* but not yet *draw* — `Help` above all — state what they + /// are instead of looking like a broken grid. + pub hint: String, /// What the user has typed so far, while a keyboard edit is open on the /// selected cell — `None` when none is. /// @@ -168,6 +177,7 @@ impl ClientState { .map(|(i, entry)| RenderCatRow::of(entry, i == self.view.cat_panel_cursor)) .collect(), category_panel_open: self.view.category_panel_open, + hint: self.view.mode.hint_text().to_string(), editing: self .view .mode @@ -392,6 +402,11 @@ mod tests { let model = client.model(); assert_eq!(model.mode, improvise_protocol::AppMode::Normal.label()); assert_eq!(model.status, "saved"); + assert!( + model.hint.contains("i:edit"), + "the page draws the mode's own hint rather than spelling keys: {}", + model.hint + ); assert_eq!(model.revision, client.revision()); } diff --git a/web/app.js b/web/app.js index e850ef7c..ffc31296 100644 --- a/web/app.js +++ b/web/app.js @@ -55,6 +55,7 @@ const el = { grid: document.getElementById("grid"), categories: document.getElementById("categories"), mode: document.getElementById("mode"), + hint: document.getElementById("hint"), status: document.getElementById("status"), viewName: document.getElementById("view-name"), }; @@ -204,6 +205,9 @@ function repaint() { painted = model.revision; el.mode.textContent = model.mode; + // The mode's own hint, off `AppMode::hint_text()`. The page spells no + // keys itself; see `RenderModel::hint`. + el.hint.textContent = model.hint; modelStatus = model.status; paintStatus(); el.viewName.textContent = model.view_name; diff --git a/web/index.html b/web/index.html index 0f5f32f9..df11f638 100644 --- a/web/index.html +++ b/web/index.html @@ -36,6 +36,7 @@

improvise

+ diff --git a/web/style.css b/web/style.css index c716d7f7..901908f1 100644 --- a/web/style.css +++ b/web/style.css @@ -89,6 +89,11 @@ td.selected { outline: 1px solid var(--accent); } +#hint { + opacity: 0.7; + font-size: 0.85em; +} + /* A keyboard edit in progress. Left-aligned even in a numeric column, because what is drawn is the text being typed rather than a value. */ td.typing {