feat(ui): give the workspace a surface ladder and make Home a starting desk - #175
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
theme.css, shell, DESIGN.md)DocumentList, newdropdown-menu)ProcessPanel,MessageList,NotebookHeader)components/home,NotebookCard,get-workspace-overview)What changed?
Surfaces —
theme.css,NotebookLayout,SourcePanel,NotePanel,NotebookListPage,OnboardingPage,App,AppErrorBoundaryp-2gutter 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.#111110/#171716/#1e1e1d) and T1 goes from0.7621to0.93, so content stops competing with chrome.4.29:1 → 5.28:1, light T32.88:1 → 3.57:1.Library —
DocumentList, newui/dropdown-menu.tsx, newcommon/sourceIcon.ts, newlib/sourceKind.ts···menu. Every row used to carry two permanent stacked icon buttons.PDF · 202 chunks), from a testedsourceKindOf(mime type → path → URL host) shared with Home. An unknown file isfile, not a guessedtext.@radix-ui/react-dropdown-menu(devDependency, bundled like the other Radix wrappers).Chat —
ProcessPanel,MessageList, newchat/NotebookHeader.tsx,stickToBottom84px → 56px), and no longer carries abackdrop-blurwith nothing behind it.COMPOSER_RESERVE_FALLBACKre-derived from the new geometry (152 → 120).top-14 → top-11).Typography —
MessageList,NoteEditor,markdown.css,noteEditor.css--reading-measure(72ch) and centred, shared by the transcript and the note editor.del, the editor placeholder) replaced with a text level.theme.cssand 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, newget-workspace-overviewlib/recentlyOpened.tsrecord, not byupdatedAt— which answers "what changed" rather than "where was I". Times are not stored, only ids, so a rename or a delete is reflected immediately.get-workspace-overviewcall (source counts, newest sources across all notebooks, last active conversation) rather than one IPC per notebook.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 violationsnpm run build— passesnpm run build:unpack— passesnpm run smoke:packaged— passes, 29 checksnpx prettier --checkon touched files — cleantest/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.theme.cssand 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:
#171716) separate from the floor (#111110) enough at 1.05:1···control against the title in a 320px library panel-translate-y-px+shadow-control) — too much or too littleChecklist
npm run typecheckpasses.npm run buildpasses.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
npm run build:unpackpasses.npm run smoke:packagedpasses.