- Visual Continuity: The three vertical zones (Top Bar, History Workspace, Input Bar) are always present; modes never reflow or hide them.
- Minimalism: No persistent side panels. The message history is the singular central surface.
- Keyboard First: Entire UI usable without mouse. Mouse affordances are optional, never required.
- Low Distraction: No flashing, no layout jumps, no gratuitous borders. Motion is purposeful (e.g. focus scroll).
- Progressive Disclosure: Secondary surfaces (topics palette, context preview, help) are transient overlays; they never permanently occupy layout space.
- Consistency > Ornament: Mode changes alter behavior & status label only; they do not recolor or restyle zones.
| Zone | ID | Purpose | Persistent Elements |
|---|---|---|---|
| Top Bar | #topBar |
Command entry (COMMAND mode) and application, message count badge, menu access. | Command input field, error span, message count badge, menu badge |
| History Workspace | #historyPane |
Scrollable chronological list of message pairs (decomposed into parts). Only this middle zone scrolls; top and bottom bars remain fixed. | Message parts (navigable) + non-focusable meta rows. |
| Input Bar | #inputBar |
User prompt entry (INPUT mode). Two-row structure: row 1 full-width prompt input; row 2 left cluster (mode label, model, topic) + right-aligned Send button. | Prompt input, mode label, model, topic, send button. |
Decision: The command input lives in the Top Bar; the bottom Input Bar is exclusively for composing user messages. Mode label lives ONLY in the Input Bar (second row, left cluster) and never appears in the Top Bar.
Modes: VIEW, INPUT, COMMAND.
| Mode | Primary Focus Target | Navigation / Actions | Text Entry | Esc Behavior | Enter Behavior | Direct Shortcuts |
|---|---|---|---|---|---|---|
| VIEW | focused part in history | j/k, Arrows, g/G, Ctrl-T, r, o/Shift-o 1-2-3, a, Space | None | To COMMAND | To INPUT | Ctrl+v (stay) |
| INPUT | Bottom input field | (nav limited), Ctrl-T, Ctrl-M | User prompt | To VIEW | Send message (stay INPUT) | Ctrl+i |
| COMMAND | Top command field | (edit + history unaffected) | Command filter | Clear filter (stay COMMAND) | Apply filter -> VIEW | Ctrl+d |
Mode Transition Model (cyclical via Enter/Escape): VIEW --Enter--> INPUT --Esc--> VIEW --Esc--> COMMAND --Enter--> VIEW
Notes:
- In COMMAND, Esc clears filter & keeps mode; Enter applies filter then returns to VIEW. (also Ctrl-W, Ctrl-U - are used)
- In INPUT, Enter sends message and remains; Esc returns to VIEW.
- In VIEW, Enter enters INPUT; Esc enters COMMAND.
- Direct overrides: Ctrl+i / Ctrl+d / Ctrl+v jump modes irrespective of cycle.
Visual Mode Indicator: A textual label (e.g. [VIEW], [INPUT], [COMMAND]) is rendered in the Input Bar, bottom-left, just beneath/flush with the input field baseline. It NEVER migrates to the Top Bar (fixed decision). No zone border change.
A Message Pair renders as an ordered sequence of Message Parts:
- User Request Parts (1..N)
- Metadata Line (always exactly one line; badges + inline controls)
- Assistant Response Parts (1..M)
"Message Part": A logically navigable fragment (splitting occurs only when needed for readability / navigation of long texts).
No folding/collapsing: The history behaves like an immutable paper roll—parts are linear; we only scroll, never fold or toggle visibility.
<div class="pair" data-id="PAIR_ID">
<div class="part user" data-part-index="0">…user text (or first chunk)…</div>
<!-- additional user parts (optional) -->
<div class="meta" role="group">
<span class="model">gpt-4</span>
<span class="stars">★★</span>
<span class="topic" data-topic-id="TID">topic-name</span>
<span class="flags">[in]</span>
<span class="timestamp" data-ts="…">2025‑08‑23 12:34</span>
</div>
<div class="part assistant" data-part-index="U+1">…assistant text…</div>
<!-- additional assistant parts (optional) -->
</div>
Authoritative token set (:root in src/styles/variables.css):
--bg: #0c0c0c; /* primary middle zone */
--bg-alt: #242424; /* top/bottom bars & overlays */
--border: #232323;
--border-active: #454545;
--accent: #5fa8ff; /* accent (reserved) */
--focus-ring: #2b4f80; /* active part border */
--text: #e6e6e6;
--text-dim: #7a7a7a;
--danger: #f66;
--size-base: 13px; /* base font size */
--gutter: 16px; /* unified left alignment */
Typography:
- Global UI font stack (VS Code / Copilot Chat style):
-apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", system-ui, Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif. - Monospace stack retained separately for future code blocks; current messages use the UI stack.
- Uniform base size across command input, prompt input, message parts, overlay text (no bold except where semantically required—currently none).
Layout & Spacing:
- Unified gutter ensures left edges of command placeholder, message text, mode label, etc. align vertically.
- History pane vertical bounds computed dynamically (JS
layoutHistoryPane) from actual bar heights to avoid clipping first/last messages. - Send button moved to second row right; first row remains pure input field.
- No horizontal separators between message pairs; visual separation via spacing only.
- Spacing tokens are CSS variables:
--gap-outer,--gap-meta,--gap-intra,--gap-between, and--part-padding. JS writes them to:rootvia settings.
Active Part Styling:
- Single
.part.activeat a time. - 1px border using
--focus-ring, subtle tinted backgroundrgba(40,80,120,0.12). - Internal horizontal padding (default 15px) adjustable (Settings overlay; apply to commit, no live preview).
- Gap between parts (default 6px) adjustable.
- Uniform part padding (all sides) adjustable (Settings overlay; implemented via inner wrapper
.part-innerso left text gutter remains fixed). - Granular vertical gaps adjustable (Settings overlay):
- Outer Gap (top/bottom of scroll content) –
gapOuterPx - Meta Gap (user→meta & meta→assistant) –
gapMetaPx - Intra-role Gap (user→user, assistant→assistant) –
gapIntraPx - Between Messages Gap (pair→pair) –
gapBetweenPx
- Outer Gap (top/bottom of scroll content) –
- Settings overlay (Ctrl+,) manages rare adjustments (fraction, reading position, padding, gap, zone heights) applied only after selecting "Apply" (no live preview).
- Spacing updates are applied via CSS variables on
:root(no injected<style>). Anchor for active part is re-applied post-update to maintain reading position. - Meta row visible but NEVER focusable (excluded from navigation sequence).
Rules (normative):
- Zones have no decorative borders; only subtle color shift (
--bgvs--bg-alt). - Mode switches do not recolor zones.
- Exactly one active part visualized via focus ring (no thick outlines).
- Metadata line background stays transparent; never inherits user part blue.
- Scrolling may be animated (default on) but must settle deterministically at the anchor; micro-corrections within a small dead-band are suppressed.
- No horizontal scrolling at any viewport width; layout must wrap or truncate content to avoid horizontal overflow. The history pane hides horizontal overflow.
- Only middle zone scrolls; document overflow hidden.
- Overlays share
--bg-altbackground and regular-weight text. - Inputs: prompt input and command input are borderless (background
--bg-alt);
j/k(primary) andArrowDown/ArrowUp(secondary) move focused (a.k.a active) part.g/G: First / last part.o/Shift+O: Jump to first in-context (included) pair and center it (one-shot; does not toggle Typewriter Regime).n: First part of last message (clears new-message badge; re-anchors even if already there).Enter/Esc: Mode cycle as defined above.Ctrl+i/Ctrl+d/Ctrl+v: Direct mode activation.*: Cycle star rating (0→1→2→3→0) for active pair.123: Directly set star to 1/2/3;Spacesets 0.a: Toggle color flag (blue ↔ grey).
See docs/keyboard_reference.md for the authoritative, continually updated key mapping. This UI layout spec retains only high‑level rationale.
Extension Interference:
Browser extensions (e.g. Vimium) may capture plain j/k. Users should exclude the app origin. Arrow keys are a fallback, not a first-class replacement. (Note: Ctrl+K is now bound to the API Keys overlay.)
Anchoring & Scrolling: Default Ensure‑Visible; optional Typewriter Regime recenters on j/k. See docs/ui_view_reading_behaviour.md and docs/scroll_positioning_spec.md.
Lifecycle specifics: See docs/new_message_workflow.md for send/reply focus and alignment sequences.
New Message Badge: Top bar right corner (as part of message counter); appears when reply arrives and user navigated away or reply filtered out; cleared by n, G, or badge activation.
Decision Clarifications:
- Active restore A1: After partition changes (resize or fraction change) we attempt to keep user near same textual spot approximately (same pair, nearest line) rather than precise hash mapping.
- Filtered reply B2: Using
nwhen new reply hidden permanently adjusts filter (simple clear) so reply remains visible; no temporary flash. - Pending send D: No extra spinner; cues are cleared input, placeholder assistant entry, and Send button label change; Enter does nothing until reply.
- Command field (Top Bar) only interprets command/filter language. (leadging ':' maybe used to introduce specific commands in the future versions)
- Input field (Bottom) only composes user prompts; a leading
:there does not switch semantics (avoiding dual meaning). This removes ambiguity.
- Command parse errors: inline in Top Bar error span (red, truncated if overly long) left of the message counter.
- No modal dialogs for routine errors.
Invoked by shortcuts; they never reflow base layout.
Implemented:
| Overlay | Purpose | Trigger | Dismiss |
|---|---|---|---|
| Topic Quick Picker | Reassign active pair (VIEW) / set pending topic (INPUT) | Ctrl+T | Esc / Enter |
| Topic Editor | Full topic CRUD, mark/paste, search | Ctrl+E | Esc |
| Model Selector | Choose model for pending message | Ctrl+M (INPUT) | Esc / Enter |
| Model Editor | Edit model catalogue and parameters, CRU | Shift+Ctrl+M | Esc / Enter |
| Settings / Preferences | Adjust part size fraction, reading position, padding, gap, top/bottom zone heights (Apply/Cancel) | Ctrl+, | Esc / Apply |
| API Keys Overlay | View / edit stored API keys (local only) | Ctrl+K / auto-open on missing key | Esc / buttons |
| Daily Stats | Message count by day | ||
| Help | Key & command summary |
Placement & Styling:
- Center or slight top-center; low-opacity scrim (no blur).
- Focus trap isolation; Esc unwinds current overlay.
- Deterministic partitioning (viewport fraction → whole-line parts) with persistent active part across re-renders.
- Anchored navigation (see
docs/ui_view_reading_behaviour.mdfor modes/defaults) with clamp-based stabilization (outer-gap padding) and calm edges. - Meta row non-focusable; all metadata actions target pair from any part.
- Keyboard metadata control (numeric stars, include toggle) immediate visual feedback.
- Topic reassignment via picker from any active part.
- Full region rebuild until perf dictates virtualization.
- Active part state external to DOM for deterministic re-render.
- Partition engine: off-screen measurement + caching; stable IDs when text/settings unchanged; major resize (≥10% height) triggers recompute & active remap.
- Meta rows excluded from parts array.
- Anchoring: compute desired alignment and apply clamped scroll; calm edges (no jitter on boundary navigation).
- Settings overlay (Ctrl+,) manages rare adjustments (fraction, reading position, padding, gap, zone heights) applied only on Apply (no live preview).
- Edge anchoring mode persisted (adaptive|strict); re-anchoring logic re-runs on change without forcing repartition.
- Tests: partition determinism, resize threshold behavior, new message indicator accumulation, active mapping after repartition.
Layout & spacing:
- partPadding, gapOuterPx, gapMetaPx, gapIntraPx, gapBetweenPx — affect measurements and visual spacing only. Outer gap is pane padding and stays fixed.
Reading & anchoring:
- Legacy anchorMode / edgeAnchoringMode removed. All alignments originate from explicit one‑shot alignTo calls or Ensure‑Visible logic; results clamp to [0, maxScroll].
Fading & visibility:
- fadeMode, fadeHiddenOpacity, fadeInMs, fadeOutMs, fadeTransitionMs — control per‑part opacity transitions. Aligned targets are placed outside the fade zone; a ≤1 px tolerance avoids boundary flicker.
Scroll animation:
- scrollAnimMs, scrollAnimEasing, scrollAnimDynamic, scrollAnimMinMs, scrollAnimMaxMs — used when alignTo/ensureVisible is invoked with animate=true. Default one‑shots prefer animate=false for deterministic sequencing.
Partitioning:
- partFraction — influences partition size; partitioning ensures parts fit the usable height (no tall text parts). Actions use measured positions.
Edge overlays:
- Height = gapOuterPx; CSS‑only treatment on
#historyPane::before/::afterthat fades content within the outer gap; pointer‑events: none.
Unrelated to scrolling:
- charsPerToken, userRequestAllowance, maxTrimAttempts, assumedUserTokens, showTrimNotice — compose/budgeting settings; unaffected by scroll logic.