feat: the page bar becomes a control, and [/] reach the modes about the document
Three tickets on one subject: paging.
improvise-rw6l — clicking a page chip opens a menu of that axis's items, with
a down-arrow saying so. Almost nothing had to be derived: `PageAxis::items` was
already projected and `ModelCommand::SetPageSelection` was already on the wire.
The one gap was that `RenderPageAxis` dropped the names, keeping the position
and count improvise-p1ym needed for `[`/`]`. `items` is relayed INSIDE `cycle`
rather than beside it, so there is one predicate and not two: a Filter axis has
no cycle, therefore no arrow, no menu, no items, and composes nothing if a click
reaches it — all four off `PageAxis::cycle`, the one home of gotcha 27's split.
improvise-771e — the browser tile bar said "Quarter Pag". `short_label` is three
characters so the TUI aligns in a fixed-width terminal (gotcha 26); a browser has
no column to fit. `render.rs` asks `plain_label` instead; `short_label` is
untouched. Note what pinned the truncation was `harness/tests/drill.spec.js`,
not `tests/grid_frame_agreement.rs`, which does not mention the tile bar.
improvise-wiew — `[`/`]` move from Normal onto a new `document_keymap` between
the global and the modes whose subject IS the document (Normal, the three
panels, TileSelect; RecordsNormal through Normal). Help and the import wizard
take the global directly. Per the user's ruling: an overlay has a subject of its
own, and these are the first shared keys that MUTATE the document — under the
help overlay that is an invisible change to the grid it is covering.
Boundary crossings to flag, both outside the brief's stated write scope and
neither a wire change:
- `resolve.rs` gains `Click::PageItem` and `lib.rs` a `click_page_item`
binding. Composing the command anywhere else would put a derivation in the
page, which design-principles §5 forbids; `Command`/`ModelCommand` are
untouched.
- `improvise-command/src/command/keymap.rs` gains three tests. The two
existing guards were the mechanism that forced the wiew argument, so they
had to be answered where they live.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
This commit is contained in:
+54
-1
@@ -88,7 +88,8 @@ export function frameCostOf(model) {
|
||||
/** The page bar: one entry per category fixing which records are visible. The
|
||||
* Page/Filter split is `cycle`, decided in Rust — gotcha 27, clients.md. */
|
||||
export function pageEntries(model) {
|
||||
return model.page_coords.map((axis) => ({
|
||||
return model.page_coords.map((axis, index) => ({
|
||||
index,
|
||||
// The TUI's own spelling (`src/ui/grid.rs`); only the spelling is local,
|
||||
// exactly as `indicator`'s is.
|
||||
text: `${axis.cat} = ${axis.selected}`,
|
||||
@@ -96,9 +97,61 @@ export function pageEntries(model) {
|
||||
// navigable: there is no position to show and no key that would move it.
|
||||
position: axis.cycle ? `${axis.cycle.at + 1}/${axis.cycle.count}` : "",
|
||||
navigable: Boolean(axis.cycle),
|
||||
// The items a menu offers, RELAYED from `cycle` — the one home of the
|
||||
// Page/Filter split, so a Filter axis offers none (improvise-rw6l).
|
||||
items: axis.cycle
|
||||
? axis.cycle.items.map((label, item) => ({
|
||||
item,
|
||||
label,
|
||||
selected: item === axis.cycle.at,
|
||||
}))
|
||||
: [],
|
||||
}));
|
||||
}
|
||||
|
||||
/** The down-arrow. Present exactly where there is a menu to open, which is
|
||||
* `navigable` and never "is this on the page bar" (gotcha 27). */
|
||||
export const MENU_ARROW = "▾";
|
||||
|
||||
/** What a delegated click on the page bar or its menu MEANS. `null` where the
|
||||
* node names neither, which is every gap between the chips. */
|
||||
export function pageItemAction(dataset) {
|
||||
const axis = whole(dataset.axis);
|
||||
if (axis === null) return null;
|
||||
const item = whole(dataset.item);
|
||||
// A chip carries an axis and no item: it OPENS the menu. A menu row carries
|
||||
// both: it picks. One attribute apart, so nothing infers from structure.
|
||||
return item === null ? { kind: "open", axis } : { kind: "pick", axis, item };
|
||||
}
|
||||
|
||||
/** What a key does while the page menu is open. The menu owns the keyboard
|
||||
* exactly as the inline editor does — `context/code/clients.md`. */
|
||||
export function menuKey(key, { count, active }) {
|
||||
if (count === 0) return { kind: "close" };
|
||||
switch (key) {
|
||||
case "Escape":
|
||||
case "Tab":
|
||||
return { kind: "close" };
|
||||
case "Enter":
|
||||
case " ":
|
||||
return { kind: "pick", item: active };
|
||||
case "ArrowDown":
|
||||
case "j":
|
||||
return { kind: "move", item: (active + 1) % count };
|
||||
case "ArrowUp":
|
||||
case "k":
|
||||
return { kind: "move", item: (active - 1 + count) % count };
|
||||
case "Home":
|
||||
return { kind: "move", item: 0 };
|
||||
case "End":
|
||||
return { kind: "move", item: count - 1 };
|
||||
// Everything else is SWALLOWED rather than passed on: a menu that let
|
||||
// `hjkl` reach the grid would scroll the thing it is anchored to.
|
||||
default:
|
||||
return { kind: "ignore" };
|
||||
}
|
||||
}
|
||||
|
||||
/** The grid, as a description: every decision made, nothing created. The page
|
||||
* decides nothing here and neither does this — which labels repeat, how many
|
||||
* header levels there are and which columns share a group all arrive
|
||||
|
||||
Reference in New Issue
Block a user