ADR-0046 status -> Accepted + implemented (8 commits 9f5f76b..22bec61); README index updated; the two draft-divergent decisions recorded inline (App.relationships not SchemaCache; nav overlay partial-clear + gutter). Handoff 63 covers the full UI build across Phases A/B/C; issues #20/#21/#23 closed on Gitea.
29 KiB
ADR-0046: Schema sidebar focus/navigation mode and responsive input & hint layout (UI #20 / #21 / #23)
Status
Accepted (2026-06-10); implemented 2026-06-10, phased A → B → C
(see Decision — phasing) across commits 9f5f76b (DA1/DA2) · e0b9470
(DA3) · 41bae99 (DA4) · 386627a (DB1) · 94825d0 (DB2/DB4) ·
c9da6ff (DC1/DC2/DC4) · 22bec61 (DC3 + DC2 refinement). Closes Gitea
issues #20 (hint-panel height jumpiness), #21 (database-structure
/ left-column improvements), and #23 (long command input). Issue
#23's own note ("handle after #21 is decided") is honoured: the input
work is split so the part that depends on the sidebar's width budget
lands with it.
Two decisions landed differently from the original draft and are
recorded inline: the relationship data lives on App, not
SchemaCache (DB2), and the navigation overlay clears only the
sidebar strip + a one-column gutter (panels stay visible behind),
not the whole area (DC2).
Builds on and honours: ADR-0003 (the persistent Simple/Advanced
mode model — navigation mode is not a third input mode, see DC1),
ADR-0027 (the input validity indicator's reserved 6 right columns —
horizontal scroll and 2-line display preserve that reserve),
ADR-0044 (relationship visualization — the relationships panel
renders the same RelationshipSchema data the show relationship
diagram already consumes), ADR-0013 / ADR-0043 (the
RelationshipSchema model: name, parent/child tables, list-based
compound columns, referential actions), ADR-0015 (project file
format — sidebar visibility is session-only, so the format is
untouched), and ADR-0002 (no engine name in user-facing strings).
Preserves the pure-render-from-App-state invariant (CLAUDE.md):
visual changes here are driven either by new App state fields
(mutated in update()) or by pure render-time functions of the frame
geometry (see State section); update() stays pure-sync.
Requirements & issues touched (verified against requirements.md).
Evolves S1 (the always-present three-region layout — the left items
region becomes width-optional, DB1). Overrides S2, which planned
additional element kinds as nested items in the tables list;
relationships get their own panel instead (DB2/DB4 — see Genuine forks
§11). Corrects S4: its "keyboard-toggleable hint area" was never
implemented (no toggle keybinding exists in the code) and is not wanted
— the hint panel became indispensable once completion moved into it
(ADR-0022) — so the toggle phrase is struck from requirements.md and
no toggle is added here. Extends I1a (single-line cursor editing →
horizontal scroll, DA3) and honours S6 / ADR-0027 (the 6-column
validity-indicator reserve, DA3/DA4). The PageUp/PageDown context-rebind
(DC3) does not regress V4's output scroll, which stays live in
input mode. Adjacent but separate: Gitea #22 (an in-app
overlay/annotation layer for casts and guided lessons — its own ADR)
shares the overlay-render and screencast context with DC2's Clear
overlay; the two are meant to coexist, not merge.
Context
Three UI issues were raised together because they are coupled through the terminal's width and height budget; treating them as one decision avoids three conflicting partial fixes.
Current layout (verified in src/ui.rs). render() splits
vertically into Min(8) main / Length(1) project label / Length(1)
status. The main area splits horizontally into a fixed
Length(28) left column (render_items_panel, a "Tables" list with
indented index names) and a Min(20) right column. The left column's
block has Borders::ALL, so its usable inner width is 26 columns
(28 − 2 borders). The right column splits vertically into output
(Min(5)), input (Length(3)), and hint (Length(hint_content)).
#20 — hint jumpiness. hint_content is recomputed every frame
as clamp(wrapped_lines, 1, MAX_HINT_ROWS=3) + 2, i.e. 3–5 rows. As the
user types and hint strings appear, grow, and vanish, the hint panel
resizes and shoves the input and output panels, producing the
flicker visible in screencasts. The root cause is that height tracks
content rather than terminal geometry.
The hint catalog (src/friendly/strings/en-US.yaml) was measured: the
two longest strings are value_literal_slot (106 chars) and
create_table_element (102); four more are 50–57; the rest ≤ 50. The
wrapping consequence is sharp: at a right-column inner width ≥ ~54
columns the worst string needs at most 2 lines; a 3rd line is
only ever required when the right column is narrower than ~54 (a
sub-~83-column terminal with the sidebar shown, or a sub-~55-column
terminal). On the project's screencasts (90 columns wide, sidebar
hidden — see DB1) two lines are provably sufficient.
#21 — the left column. A persistent 26-column Tables list is rarely filled even half-way by a teaching database, yet it permanently costs horizontal space the output and input panels want — acutely so on the 90-column screencasts. The pedagogical value of an always-visible schema overview is real (CLAUDE.md "pedagogy wins ties"), so the column is kept but made optional and more useful, not deleted.
#23 — long input. The command input is a single logical String
rendered by a Paragraph with no wrap and no horizontal scroll
(render_input_panel); text past the panel width clips silently.
The cursor is a byte offset on a char boundary; Up/Down drive history.
The fix needs the width the sidebar's removal frees, hence the coupling.
Keybinding space (verified). Taken: Tab/Shift-Tab (completion), Enter (submit), Up/Down (history), Left/Right/Home/End (cursor), PageUp/PageDown (output scroll), Backspace/Delete, Esc (completion-undo / modal cancel), Ctrl-C (quit). Reserved-but-deferred (I1b readline): Ctrl-A/E/W/K/U. Printable keys all route to the input. Terminal-hijacked and therefore unusable: Ctrl-S/Q (flow control), Ctrl-Z (suspend), Ctrl-H (backspace), Ctrl-I/M (Tab/Enter), Ctrl-G (BEL). This leaves a narrow band of safe combinations for new controls.
Decision — phasing
The work ships in three phases so the screencasts benefit from the least-controversial part first and the riskiest part (the focus/scroll model) is isolated:
- Phase A — input & hint (DA1–DA4): self-contained, no sidebar dependency; fixes #20 and the baseline of #23.
- Phase B — optional, richer sidebar (DB1–DB3): visibility model + relationships panel + schema-cache enrichment.
- Phase C — navigation mode (DC1–DC4): the Ctrl-O focus/scroll/ expand model that makes the sidebar browsable.
Each phase is independently shippable and independently green.
Decision — Phase A: responsive input & hint heights
DA1 — Hint height is a function of terminal geometry, fixed between resizes
The hint panel's height is decoupled from hint content. It is
computed from the terminal's width and height once per resize and
held constant as the user types. Because the panel no longer resizes on
every keystroke, it never shoves the input/output panels — the #20 jump
is eliminated at the source, not damped. Content that exceeds the fixed
height is ellipsized (the existing clamp_wrapped truncation), which is
now a rare, width-driven event rather than a per-keystroke one.
DA2 — Responsive height buckets
Heights are chosen by terminal height (rows), with the hint's optional 3rd line gated on right-column width (per the Context measurement):
| Terminal height | Input content rows | Hint content rows |
|---|---|---|
Compact (H < 40 — covers the 25-row screencasts) |
1 (+ horizontal scroll, DA3) | 2 |
Comfortable (H ≥ 40 — fullscreen terminals) |
2 (soft-wrap, DA4) | 2 (→ 3 only if right-column inner < ~54) |
A safety degradation protects tiny terminals: the output panel's
Min(5) is honoured first; if rows are insufficient, the hint shrinks
to 1, then the input to 1. The 40-row threshold is a tunable constant.
DA3 — Input horizontal scroll (single logical line)
The input keeps its single-String model (no embedded newlines —
this is explicitly not multi-line input, see Out of scope). A new
App field input_scroll_offset: usize tracks the first visible
column; the renderer shows a window of the line and keeps the cursor in
view, mirroring the candidate-line horizontal-scroll markers already in
render_candidate_line. The ADR-0027 6-column indicator reserve is
preserved (the scroll window is the text area = inner.width − 6, not
the full inner width). Because update() does not know the panel width,
the renderer feeds it back via a note_input_viewport(text_width) call
(the analogue of the existing note_output_viewport), against which the
offset is clamped to keep the cursor visible. input_scroll_offset
resets to 0 whenever the buffer is replaced wholesale — on submit,
on history navigation (Up/Down), and on any clear. This is the baseline
#23 fix and is sufficient on its own for the compact (1-row) layout.
DA4 — Two-line input display when tall (H ≥ 40)
On comfortable terminals the input renders across 2 visual rows by
soft-wrapping the single logical line, with the cursor mapped to a
(row, col) within the two rows. Content longer than two rows scrolls
the two-row window horizontally (DA3) so the cursor stays visible. The
ADR-0027 [ERR]/[WRN] indicator stays anchored to the right edge
of the first row (its 6-column reserve applies to row 1; the soft-
wrap on row 1 stops 6 columns short, row 2 uses the full text width) —
S6 is preserved.
This is display-only over the same single-String model — distinct
from the deferred true multi-line-input feature (I1, which adds
multiple logical lines with Enter-inserts-newline). Forward-compat
note: I1, when built, should reuse DA4's row-rendering and cursor
(row, col) mapping rather than introduce a parallel one — DA4 is the
substrate, not a competitor.
Decision — Phase B: optional, richer sidebar
DB1 — Width-derived visibility plus transient peek (session-only)
Sidebar visibility is derived, not stored: the sidebar is visible
iff the terminal width > 90 or navigation mode is currently
focused on a sidebar panel (the Ctrl-O peek, DC1). It is recomputed
every frame from terminal width and NavFocus; nothing persists to
project.yaml (ADR-0015 untouched), so it is session-only by
construction — and there is no stored visibility field to keep in sync.
At ≤ 90 columns the sidebar is hidden by default — so the 90-column
screencasts never show it and the output panel gets the full width it
needs there — but Ctrl-O temporarily reveals it for the duration of a
browse and re-hides it on exit (DC1).
No persistent show/hide toggle (resolved 2026-06-10, user). Issue
#21's original wording asked for "a keystroke to show and hide it"; the
Ctrl-O peek covers that need, so no separate toggle and no
force-shown/force-hidden override is added. Visibility stays a pure
function of (terminal width, NavFocus) — the simplest model that
satisfies the requirement. Should pinning ever prove necessary, a
persistent override is an additive follow-up (see Out of scope).
DB2 — Add a relationships panel; enrich the schema cache
The left column gains a second panel below Tables: a list of the project's relationships. This is a deliberate override of S2, whose note proposed additional element kinds (relations, views) as nested items inside the existing tables list. Relationships are cross-table, not per-table, so nesting them under a single table reads wrong; a sibling panel is the honest shape (user-confirmed 2026-06-10). S2's "without restructuring" intent is still met — the items column simply holds two stacked panels (DB4) instead of one.
The panel needs the full RelationshipSchema (name, parent/child
tables, list-based columns, on-delete/on-update actions) that the show relationship path already fetches.
Data home — App, not SchemaCache (revised at implementation,
2026-06-10). The design first proposed an additive
SchemaCache.relationship_details: Vec<RelationshipSchema> field.
Implementation revised this to a parallel App.relationships: Vec<RelationshipSchema> field for two reasons: (1) SchemaCache is
walker/completion-facing — it needs only relationship names
(unchanged in SchemaCache.relationships, still borrowed as
&Vec<String> by IdentSource::Relationships); the full records are
UI-only, so App is the architecturally correct home, mirroring
app.tables (which the items panel already reads alongside the cache).
(2) Adding a field to SchemaCache would force edits to ~23 full
struct literals across the test suite, whereas App gains one field.
The /runda guard it answered — don't break completion by retyping
relationships — is fully honoured either way. Delivery: a worker
Request::ReadAllRelationships (→ Database::read_all_relationships,
returning Vec<RelationshipSchema> via the existing
read_all_relationships(conn)); the runtime's refresh_schema_cache
posts a new AppEvent::RelationshipsRefreshed alongside
SchemaCacheRefreshed, and the App stores it. No behavioural
difference from the original design.
The panel has two display states keyed off focus (DC2):
-
Unfocused (26-col) — an ambient glance. Per relationship: the name (ellipsized past the inner width), and the endpoints broken at the arrow to fit a narrow column:
Customers_Orders Customers.id -> Orders.customer_id -
Focused + expanded (40–50 col, DC2) — a browse view. At the wider width the endpoints fit on one line (
Customers.id -> Orders.customer_id); the arrow-break is used only when even the expanded width cannot hold a (possibly compound) endpoint pair. The wider width minimises horizontal truncation so the panel needs mainly vertical scrolling (DC3).
DB3 — Sidebar width unchanged when unfocused
The unfocused sidebar keeps Length(28) / 26 inner columns. Widening
happens only on focus (DC2), as an overlay, so the unfocused layout and
the right-column reflow are unchanged from today.
DB4 — Vertical split of the two left-column panels
The items column stacks Tables (top) and Relationships (bottom). The Relationships panel's height is content-driven within bounds, so it stays small when there is little to show and never dominates the column (user-chosen 2026-06-10):
- No relationships: fixed at 5 rows (3 content + 2 border),
rendering a single
Noneline. This is the floor. - With relationships: grows with content (
content_rows + 2, where the unfocused format is ~3 rows per relationship) up to a cap of 50 % of the column height; beyond the cap the panel scrolls (DC3). Formallyrel_h = clamp(content_rows + 2, 5, ⌊col_h / 2⌋). - Tables takes the remainder (
col_h − rel_h) and scrolls if it overflows (it, too, is a focusable, scrollable panel — DC3). - Degradation: on a column too short to honour the 5-row floor plus
a usable Tables panel (
col_h < ~10), the floor yields first so Tables keeps at least its border + one row; both panels stay renderable. The50 %cap and5-row floor are tunable constants.
Heights are a pure render-time function of the column height and the cached relationship count, so they are unit-testable without a terminal (see Testing).
Decision — Phase C: navigation mode
DC1 — Ctrl-O navigation mode: a focus cycle, not an input mode
Ctrl-O enters a navigation mode that is orthogonal to the
Simple/Advanced input mode (ADR-0003) — it changes where keystrokes
go, not how commands parse. It drives a focus cycle:
- Press 1 → focus the Tables panel (revealing the sidebar if it is currently hidden — a temporary peek).
- Press 2 → focus the Relationships panel.
- Press 3 → leave navigation mode: restore the sidebar width, re-hide it if the peek revealed it, and return focus to the command input.
Esc exits navigation mode directly from any focused panel (a
short-cut for step 3); Esc is otherwise only completion-undo, which
does not apply while browsing.
Why Ctrl-O and not Ctrl-B. Ctrl-B is the default tmux prefix
and Ctrl-A is screen's — a multiplexer eats them before the app
sees them, so either would make navigation mode unreachable for the many
students who run inside tmux/screen. Ctrl-O is not a multiplexer
prefix; in the raw mode the TUI sets, its legacy line-discipline meaning
(discard-output) is disabled, so it reaches the app. It is free in the
app today (the main key handler's catch-all, app.rs:1001). The
mnemonic is weak ("Outline"); reachability won over mnemonic.
Routing. Navigation mode is handled inside the main key handler,
which runs only when no modal is open (app.rs:919 gates on
self.modal.is_some()). So Ctrl-O and the nav keys are inert while
a modal dialog is active — modals keep full keyboard ownership. Within
the main handler, a NavFocus != Input branch precedes the normal
input-editing arms and routes keys per DC3/DC4.
DC2 — Expand-on-focus as an overlay
A focused sidebar panel widens to a 45-column overlay
(NAV_EXPANDED_WIDTH): the renderer Clears the strip the expanded
panel occupies plus a one-column gutter (NAV_OVERLAY_GUTTER) and
paints the wide panel on top. The output/input/hint panels underneath
keep their exact layout — unused and unchanging while browsing,
still visible to the right of the overlay (just partially occluded
on the left) — and are restored fully by the next frame on exit. The
gutter keeps them from butting against the expanded panel's border so
the overlay edge reads cleanly. This is cheap because the renderer is a
pure function of App state: focus state selects the width and the
overlay path. (The input underneath is inactive in navigation mode.)
Implementation note (2026-06-10): a full-area clear (hiding the base panels entirely during browse) was tried first and rejected — leaving the base visible is truer to "underneath keep their layout," and the one-column gutter resolves the only wrinkle (the panels' left edges being cut by the overlay reading harshly without separation).
DC3 — Scroll the focused panel; focus highlight
While a sidebar panel is focused it scrolls, reusing the output panel's
proven mechanism (a usize offset clamped against a renderer-reported
viewport via a note_*_viewport call):
- Up / Down — line-by-line scroll (the lazygit
j/kfeel; user-chosen 2026-06-10). - PageUp / PageDown — page scroll.
This is a context-sensitive rebind: Up/Down drive history and
PageUp/PageDown scroll the output in input mode, whereas in navigation
mode they scroll the focused sidebar panel. The two contexts never
apply simultaneously (NavFocus selects which). The focused panel shows
an accent border so it is obvious where keys are going (lazygit
convention).
DC4 — Other keys are inert in navigation mode
The command input is visibly occluded by the overlay while browsing, so
keys that have no navigation meaning are inert rather than acting on
the hidden input. Specifically, only Ctrl-O (advance focus),
Up/Down + PageUp/PageDown (scroll, DC3), and Esc (exit) are live;
printable characters, Enter, Tab, Backspace/Delete, Left/Right, and
Home/End all do nothing until navigation mode is exited. The occlusion
signals "not typing," so swallowing these is clearer than letting them
silently edit an invisible buffer.
State, keybindings, and cross-cutting wiring
Stored App state (mutated in update(), read by the renderer):
input_scroll_offset: usize(DA3) — reset on submit / history-nav / clear.NavFocus { Input, SidebarTables, SidebarRelationships }(DC1) — the navigation-mode focus cursor;Input≙ not in navigation mode.- Per-panel scroll offsets for the Tables and Relationships panels, each
clamped against a renderer-reported viewport (DC3), mirroring
output_scroll/note_output_viewport. App.relationships: Vec<RelationshipSchema>(DB2) — the full relationship records for the sidebar panel, delivered byAppEvent::RelationshipsRefreshedfrom the runtime's schema refresh.SchemaCache.relationships: Vec<String>(names, for completion) is unchanged. (See DB2 for why this lives onApp, notSchemaCache.)
Render-time derived (pure functions of frame.area() + cached
counts — not stored fields; this keeps the pure-render invariant and
makes the geometry logic unit-testable without a terminal):
- Sidebar visibility —
(width > 90) || NavFocus is a sidebar panel(DB1). - Input/hint row counts — a pure helper
panel_heights(area) -> (input_rows, hint_rows)(DA1/DA2), the same helper the renderer and the Tier-1 tests call. - Left-column split
rel_h = clamp(content_rows + 2, 5, ⌊col_h/2⌋)(DB4). - Input width fed back to
update()vianote_input_viewport(DA3), sinceupdate()cannot readframe.area().
Keybindings introduced/affected:
| Key | Input mode | Navigation mode |
|---|---|---|
Ctrl-O |
enter nav mode, focus Tables (peek-reveal) | advance focus (Tables → Relationships → exit) |
Up / Down |
history (unchanged) | line-scroll focused panel |
PageUp / PageDown |
scroll output (unchanged) | page-scroll focused panel |
Esc |
completion-undo (unchanged) | exit nav mode directly |
| printable / Enter / Tab / Backspace / Left / Right / Home / End | edit/submit input (unchanged) | inert |
All nav keys are inert while a modal is open (the main handler is gated
on !modal.is_some(), app.rs:919).
Renderer changes (src/ui.rs): geometry-driven hint/input height
(DA1/DA2), input window + cursor windowing (DA3) and 2-row soft-wrap
with row-1 indicator (DA4), the relationships panel + two-panel split
(DB2/DB4), the focus accent border and expand-on-focus Clear overlay
(DC2/DC3); note_input_viewport feedback added alongside the existing
note_output_viewport.
Genuine forks (escalated, resolved 2026-06-10)
- Left column fate — remove entirely vs narrow vs keep + make optional and richer (chosen, user). → DB1/DB2.
- Focus/scroll model — a navigation mode (chosen, user) vs modeless modifier-key scroll vs deferring scroll. → DC1.
- Navigation shortcut —
Ctrl-O(chosen, user);Ctrl-Brejected on review (it is the default tmux prefix → unreachable inside tmux); Ctrl-T also viable; terminal-hijacked combos excluded. → DC1. - Expand-on-focus rendering — overlay with
Clear(chosen, keeps the right panels unchanging) vs re-splitting the layout (would reflow output). → DC2. - Navigation-mode printables — ignore (chosen, user) vs drop-to-input-and-type. → DC4.
- Hint anti-jump — fix height to terminal geometry (chosen) vs damping/hysteresis vs always-reserve-max. → DA1.
- Height thresholds —
H < 40compact /H ≥ 40comfortable, with 1/2 and 2/2 splits (chosen, user). → DA2. - Visibility persistence — session-only (chosen, user) vs
per-project in
project.yaml. → DB1. - Persistent show/hide toggle — deferred (chosen, user): the Ctrl-O peek covers #21's "keystroke to show and hide", so visibility stays width-derived with no override. → DB1.
- Nav-mode Up/Down — line-scroll the focused panel (chosen, user) vs leaving scroll to PageUp/PageDown only. → DC3.
- Relationships placement — a separate sibling panel (chosen, user — overrides S2) vs nesting relations inside the tables list per S2's documented extension model. → DB2/DB4.
- Hint-area toggle (S4) — no toggle (chosen, user): the hint
panel is indispensable since completion moved into it; S4's stale
"keyboard-toggleable" claim (never implemented) is struck from
requirements.md. → Status (Requirements & issues touched).
Testing
Tier-1 (app.rs pure update() unit tests), Tier-2 (insta
snapshots, src/snapshots/) for the visual surfaces — this change is
heavily render-side, so the geometry/format/overlay assertions belong in
snapshots, not only behavioural tests — and Tier-3 integration.
Test-first per CLAUDE.md. The geometry helpers (panel_heights, the
DB4 split, visibility) are pure functions exercised directly in
Tier-1 without a terminal.
Phase A:
- Hint anti-jump:
panel_heights(area)is invariant under changing hint content at a fixed terminal size (assert it does not change asapp.hintvaries); it does change across theH < 40/H ≥ 40boundary and the width-< 54 boundary. - Height buckets: compact → input 1 row / hint 2; comfortable →
input 2 / hint 2 (3 only when right-column inner < ~54); tiny-terminal
degradation honours output
Min(5). - Input horizontal scroll: a line longer than the panel keeps the
cursor visible while moving Left/Right/Home/End; ADR-0027's 6-column
reserve is intact; no characters are lost (buffer = full string);
input_scroll_offsetresets on submit / history-nav / clear. - Two-line input: at
H ≥ 40a line wrapping to two rows renders both rows with correct cursor (row, col), the[ERR]/[WRN]indicator on row 1's right edge (Tier-2 snapshot); a longer line scrolls.
Phase B:
- Relationship data path:
Database::read_all_relationshipsreturns full records through the worker thread (integration test, real DB via an m:n junction);AppEvent::RelationshipsRefreshedpopulatesApp.relationships;SchemaCache.relationshipsnames are undisturbed (completion still resolves them). - Relationships panel render (Tier-2): empty → a single
Noneline at the 5-row floor; the unfocused narrow format (name + arrow-break, ellipsis past inner width); a compound endpoint pair arrow-breaks correctly. - Two-panel split (DB4):
rel_h = clamp(content_rows + 2, 5, ⌊col_h/2⌋)— 5 when empty; grows with content; capped at 50 %; Tables takes the remainder; degrades sanely atcol_h < 10. - Width-derived visibility: width ≤ 90 hides, > 90 shows, recomputed on resize (the peek interaction is covered under Phase C).
Phase C:
- Focus cycle:
Ctrl-Ocycles Input → Tables → Relationships → Input;Escexits directly; a peek-revealed sidebar re-hides on exit; a width-shown (> 90) sidebar stays shown on exit;Ctrl-Ois inert while a modal is open. - Expand overlay (Tier-2): focusing widens to the expanded width; the underlying output/input/hint state is unchanged across enter/exit (no reflow); the focus accent border marks the focused panel.
- Scroll rebind: in nav mode Up/Down line-scroll and PageUp/PageDown page-scroll the focused panel (clamped to its viewport); in input mode Up/Down still drive history and PageUp/PageDown still scroll output (no V4 regression); inert keys (printable/Enter/Tab/Backspace) do nothing in nav mode.
All tiers green, zero skips; clippy clean (nursery).
Out of scope
- True multi-line input (I1) — Enter-inserts-newline / Ctrl-Enter- submits over a multi-logical-line buffer. DA3/DA4 keep a single logical line; this remains a separate, deferred feature.
- Readline shortcuts (I1b) — Ctrl-A/E/W/K/U stay reserved-deferred; not touched here.
- Cross-session sidebar persistence — visibility is session-only (DB1); persisting it would amend ADR-0015.
- The output panel as a third navigation focus target — navigation mode cycles the two sidebar panels only; output keeps its input-mode PageUp/PageDown scroll.
- Relationship search / filtering within the panel — the panel is a scrollable list; no query box.
- Relationship rename / edit from the panel — it is read-only; mutation stays with the DSL/SQL commands.
- A persistent show/hide toggle / force-shown override (DB1, resolved deferred) — visibility is width-derived + Ctrl-O peek; a pin/force override is an additive follow-up if ever needed.
- A hint-area toggle (old S4 wording) — not implemented today and not wanted (the hint panel is indispensable since completion moved in; fork §12). The stale "keyboard-toggleable" phrase is removed from S4.
- In-app overlay / keystroke-annotation layer (Gitea #22) — a
separate feature with its own ADR; DC2's
Clearoverlay is built to coexist with it, not to provide it.
Accepted consequences
- Width-threshold discontinuity. Because
Autovisibility flips at width 90 and the sidebar costs 28 columns, widening a terminal across the boundary (89 → 91) makes the output narrower (≈ 89 → 63 inner) as the sidebar appears. This is inherent to any width-gated auto-hide and is accepted: 90 is the screencast width, real terminals sit well to one side of it, andCtrl-Opeek covers the in-between case. The90threshold is a tunable constant.