Skip to content

feat(ui): give the workspace a surface ladder and make Home a starting desk - #175

Merged
mrsibe merged 1 commit into
mainfrom
feat/workspace-visual-hierarchy
Sep 28, 2026
Merged

mrsibe merged 1 commit into
mainfrom
feat/workspace-visual-hierarchy

Conversation

@mrsibe

@mrsibe mrsibe commented Sep 28, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Gives the interface the visual hierarchy it was missing, and rebuilds Home so it is a starting desk rather than a dashboard.

Concretely: a named three-tier surface ladder (floor / chrome / content) wired into the workspace shell, a warm neutral ramp with measured contrast, a Library that groups sources and keeps row actions in a menu, a composer that reads as an input, a notebook title that is a document heading instead of a 14px label, a reading measure for long-form content, and a Home with four sections that are allowed to be empty.

Why?

The app read as flat. Panels were cards on a near-identical plane, the notebook's name was the same size as a list row, and long-form content had no line-length cap — on a wide window a chat answer set a line at roughly 130 characters. Two documented contrast values were also wrong when measured: light T2 was 4.29:1 (below AA for body text) and light T3 was 2.88:1 (below 3:1).

Home had the opposite problem: everything was one big grid of equal cards, with no answer to "where was I".

Related issue

There is no issue for this. Flagging it explicitly, because CONTRIBUTING asks for an issue before anything larger than a small improvement and this is a broad UI change. Happy to split it into several PRs if you would rather review it that way — the areas are:

  1. surface ladder + neutral ramp (theme.css, shell, DESIGN.md)
  2. library list (DocumentList, new dropdown-menu)
  3. chat composer + notebook header (ProcessPanel, MessageList, NotebookHeader)
  4. reading measure + prose weights (typography)
  5. Home (components/home, NotebookCard, get-workspace-overview)

What changed?

Surfaces — theme.css, NotebookLayout, SourcePanel, NotePanel, NotebookListPage, OnboardingPage, App, AppErrorBoundary

  • Three named tiers, one job each: floor (window background, the p-2 gutter between panels), chrome (rails, left/right panels, title bars), content (chat, cards, dialogs). The rails are a step above the floor, so the separation is carried by surfaces rather than by shadows.
  • The neutral ramp is warm (hue ~91) in both schemes. Dark is deeper (#111110 / #171716 / #1e1e1d) and T1 goes from 0.7621 to 0.93, so content stops competing with chrome.
  • Measured contrast replaces estimated values, and two real defects fall out: light T2 4.29:1 → 5.28:1, light T3 2.88:1 → 3.57:1.

Library — DocumentList, new ui/dropdown-menu.tsx, new common/sourceIcon.ts, new lib/sourceKind.ts

  • Grouped by kind under T3 headings, rendered only when more than one group exists.
  • Retry, re-index, open and delete move into one hover-revealed ··· menu. Every row used to carry two permanent stacked icon buttons.
  • A row leads with what the source is (PDF · 202 chunks), from a tested sourceKindOf (mime type → path → URL host) shared with Home. An unknown file is file, not a guessed text.
  • Added @radix-ui/react-dropdown-menu (devDependency, bundled like the other Radix wrappers).

Chat — ProcessPanel, MessageList, new chat/NotebookHeader.tsx, stickToBottom

  • The composer is opaque, one line tall when empty (84px → 56px), and no longer carries a backdrop-blur with nothing behind it.
  • COMPOSER_RESERVE_FALLBACK re-derived from the new geometry (152 → 120).
  • The notebook title moved out of the panel header into a 30px document header that scrolls with the answer. The panel header keeps its toggles plus an empty centre that preserves the drag surface, and the tab strip still carries the name, so nothing is lost.
  • The 12px dead band under the panel header is gone (top-14 → top-11).

Typography — MessageList, NoteEditor, markdown.css, noteEditor.css

  • Long-form content is capped at --reading-measure (72ch) and centred, shared by the transcript and the note editor.
  • Prose headings are 600 rather than 700.
  • Alpha-on-text (del, the editor placeholder) replaced with a text level.
  • The source reader is excluded on purpose, and the reason is recorded in theme.css and DESIGN.md: its highlight rects are measured once from the laid-out range, so a column that re-centres on resize would strand the highlight.

Home — components/home/*, NotebookCard, NotebookListPage, new get-workspace-overview

  • Greeting, search entry, recent notebooks, Continue, recently added. Every section may render nothing instead of showing an empty box with a heading.
  • One grammar per kind of thing: notebooks are cards, everything else is a row, creating is a button.
  • "Continue" is ordered by the user's own activity via a new lib/recentlyOpened.ts record, not by updatedAt — which answers "what changed" rather than "where was I". Times are not stored, only ids, so a rename or a delete is reflected immediately.
  • Home's data arrives in one get-workspace-overview call (source counts, newest sources across all notebooks, last active conversation) rather than one IPC per notebook.
  • No dead affordances: there is no Library page, so the shelf expands in place instead of linking to a "View all" that does not exist.
  • Home's search filters notebook and source names in the renderer, and the placeholder says exactly that.

Documentation — DESIGN.md updated in the same commit: the surface table, a Rail list recipe, the Composer recipe, the Notebook header recipe, the reading measure and prose-weight rule, a Home section, and the one card-hover shadow exception.

How was this tested?

  • npm run typecheck — passes (node, web, test)
  • npm test — 446 pass, 0 fail (426 before, +20 new)
  • npm run check:design — 0 violations
  • npm run build — passes
  • npm run build:unpack — passes
  • npm run smoke:packaged — passes, 29 checks
  • npx prettier --check on touched files — clean
  • New tests: test/sourceKind.test.ts (10) covers a null mime type, missing mime type with Windows and POSIX paths, a directory dot that is not an extension, a query string that is not an extension, YouTube lookalike hosts, and unparseable URLs. test/recentlyOpened.test.ts (10) covers corrupt JSON, per-entry rejection so one bad row does not discard the rest, NaN/Infinity timestamps, untrusted stored order, duplicate ids, the cap, and that the recorder does not mutate its input.
  • Contrast values in theme.css and DESIGN.md were computed from the oklch tokens with an OKLCH→sRGB + WCAG script rather than estimated.

Not tested: I could not run a visual pass. No screenshot tooling (playwright/puppeteer/scrot/grim) is available in this environment, so nothing here has been looked at on screen. That is why the screenshots section is empty rather than filled.

Screenshots / recordings

No screenshots available from the environment this was developed in. Worth a look on screen before merging:

  • whether the dark rails (#171716) separate from the floor (#111110) enough at 1.05:1
  • the 30px notebook title wrapping in a narrow centre panel
  • the ··· control against the title in a 320px library panel
  • the Home card hover (-translate-y-px + shadow-control) — too much or too little
  • the search dropdown alignment at the window's minimum width

Checklist

  • I have reviewed my own changes.
  • npm run typecheck passes.
  • npm run build passes.
  • I have tested the affected user workflow.
  • I have not included unrelated changes.
  • I have updated documentation when necessary.

The workflow checkbox is deliberately unchecked: this branch has never been run in the app window from this environment, only typechecked, unit-tested and packaged-smoke-tested. See "Not tested" above.

Desktop / build changes

  • Not applicable
  • npm run build:unpack passes.
  • npm run smoke:packaged passes.

…g desk

The interface read as flat: one plane of near-identical greys, panels that were
cards, and a 14px label where a document title should be. This pass gives it the
hierarchy it was missing and rebuilds Home around what a returning user actually
does.

Surfaces (DESIGN.md "Surfaces", theme.css)
- Three named tiers with one job each: floor (window + gutter), chrome (rails,
  left/right panels, title bars), content (chat, cards, dialogs). The workspace
  gutter is the floor and the rails sit a step above it, so the hierarchy is
  carried by surfaces rather than by shadows.
- The neutral ramp is warm (hue ~91) in both schemes. Dark is deeper and its T1
  is much brighter (0.7621 -> 0.93), so content stops competing with chrome.
- Measured WCAG contrast replaces the previous estimates. Two real defects are
  fixed on the way: light T2 was 4.29:1 (below AA for body text, now 5.28:1) and
  light T3 was 2.88:1 (below 3:1, now 3.57:1).

Library (DocumentList)
- Sources are grouped by kind under T3 headings, shown only when more than one
  group exists.
- Retry, re-index, open and delete move into one hover-revealed menu, so a row
  stops carrying a permanent column of buttons. The row button and the menu
  trigger are siblings: a button inside a button is the defect the tab strip
  already fixed.
- A row leads with what the source is: `PDF · 202 chunks`, from a tested
  `sourceKindOf` (mime type -> path -> URL host) shared with Home.

Chat (ProcessPanel, MessageList, NotebookHeader)
- The composer is an opaque floating layer, one line tall when empty, and no
  longer takes a backdrop blur that had nothing to blur. Its reserve fallback is
  re-derived from the new geometry.
- The notebook title leaves the panel header and becomes a 30px document header
  that scrolls with the answer. The panel header keeps its toggles and an empty
  centre that preserves the drag surface; the tab strip still carries the name.
- The 12px dead band under the panel header is gone (top-14 -> top-11).

Typography
- Long-form content is capped at --reading-measure (72ch) and centred, shared by
  the transcript and the note editor. Uncapped, a wide panel set a line at ~130
  characters. The source reader is deliberately excluded, with the reason
  recorded: its highlight rects are measured once, so a re-centring column would
  strand them.
- Prose headings are 600, not 700: an h3 inside an answer must not out-weigh the
  panel header above it. Alpha-on-text (del, editor placeholder) is replaced with
  a text level.

Home (components/home, NotebookListPage)
- A starting desk, not a dashboard: greeting, search entry, recent notebooks,
  Continue, recently added. Every section may render nothing.
- One grammar per kind of thing - notebooks are cards, everything else is a row -
  so the page does not read as a wall of identical boxes.
- "Continue" is ordered by the user's own activity (a new localStorage record),
  not by updatedAt, which answers "what changed" rather than "where was I".
- No dead affordances: there is no Library page, so the shelf expands in place
  instead of linking to a "View all" that does not exist.
- Home's data arrives in one get-workspace-overview call (source counts, newest
  sources across all notebooks, last active conversation) rather than one IPC per
  notebook on a page that is opened constantly.
- Home's search filters notebook and source names in the renderer, and says so.
  Cross-notebook full-text search needs a notebook-free retrieval path; a box
  labelled "ask your knowledge" that matched only titles would be worse.

Verification
- npm run typecheck, npm test (446 pass, +20 new), npm run check:design,
  npx electron-vite build, npm run build:unpack, npm run smoke:packaged (29
  checks) all pass.
- New tests: test/sourceKind.test.ts (null mime, Windows paths, lookalike hosts),
  test/recentlyOpened.test.ts (corrupt storage, per-entry rejection, NaN/Infinity,
  untrusted order, cap).
- Not verified: pixels. No screenshot tooling is available in this environment,
  so no visual pass was performed; DESIGN.md records the intent instead.
@github-actions github-actions Bot added the enhancement New feature or request label Sep 28, 2026
@mrsibe
mrsibe merged commit de39d9a into main Sep 28, 2026
4 checks passed
@mrsibe
mrsibe deleted the feat/workspace-visual-hierarchy branch September 28, 2026 14:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant