feat(browser): the page draws the mode's hint instead of spelling keys
`?` 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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EoW5BB87ZZdm96SvBTWawH
This commit is contained in:
@@ -56,6 +56,15 @@ pub struct RenderModel {
|
||||
pub categories: Vec<RenderCatRow>,
|
||||
/// 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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<h1>improvise</h1>
|
||||
<span id="view-name"></span>
|
||||
<span id="mode"></span>
|
||||
<span id="hint"></span>
|
||||
<span id="status" role="status"></span>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user