diff --git a/DESIGN.md b/DESIGN.md
new file mode 100644
index 0000000..8b397e1
--- /dev/null
+++ b/DESIGN.md
@@ -0,0 +1,340 @@
+# KnowNote Design System
+
+KnowNote is a desktop reading, note-taking and RAG tool. The interface is mostly
+chrome around content that people read for a long time: document text, notes,
+chat answers, settings.
+
+This document is the **single source of truth for UI decisions in this
+repository**. It is written to be executable: every rule names a token or a
+Tailwind class. If you are an agent adding a page or a component, follow the
+recipes in [Component recipes](#component-recipes) instead of inventing values.
+If a rule here is wrong or missing, change this file in the same PR as the code.
+
+Non-goals: this document does not define product behaviour, information
+architecture, branding (logo, hue of the primary colour) or i18n.
+
+## Design principles
+
+1. **Content is the subject.** Chrome is neutral and quiet. Panels are separated
+ by a 1px hairline and a surface step, not by shadows or colours.
+2. **Neutral first, accent rarely.** The accent colour marks focus and the
+ primary action of a view. It is never decoration. Same-screen accent elements
+ should be countable on one hand.
+3. **Hairline over shadow.** Shadows are reserved for things that float above the
+ document (dialogs, popovers, menus, toasts). Panels never float.
+4. **Density over airiness.** 32–36px rows, 44px panel headers, 14px UI text.
+ Density is what makes a knowledge tool feel fast.
+5. **Four states, always.** Every interactive surface has a defined `hover`,
+ `selected`, `focus-visible` and `disabled` expression. Missing states are
+ bugs, not omissions.
+
+Direction reference: Notion's content feel, Linear's information density,
+Raycast's desktop chrome (near-flat dark surfaces, hairlines, almost no shadow).
+
+## Surfaces
+
+The app is a stack of opaque surfaces plus two translucent state fills. Higher
+in the stack means further from the window background, and **lighter** in both
+colour schemes.
+
+| Token | Tailwind | Light | Dark | Use for |
+| -------------------- | --------------------- | ------------------- | ------------------------------- | ----------------------------------------------------------------------- |
+| `--surface-sunken` | `bg-surface-sunken` | `oklch(0.9551 0 0)` | `oklch(0.24 0.0127 258.3724)` | Window chrome that sits _behind_ content: sidebar, left rail, title bar |
+| `--surface-base` | `bg-surface-base` | `oklch(0.9851 0 0)` | `oklch(0.2925 0.0157 264.2965)` | App canvas, page background, the gap between panels |
+| `--surface-raised` | `bg-surface-raised` | `oklch(1 0 0)` | `oklch(0.325 0.011 260)` | Content panels: document list, reader, chat, editor, cards |
+| `--surface-overlay` | `bg-surface-overlay` | `oklch(1 0 0)` | `oklch(0.365 0.011 260)` | Floating layers: dialog, sheet, popover, menu, select, tooltip, toast |
+| `--surface-hover` | `bg-surface-hover` | `foreground @ 5%` | `neutral 0.9 @ 6%` | Translucent hover fill for rows, menu items, ghost buttons |
+| `--surface-selected` | `bg-surface-selected` | `foreground @ 9%` | `neutral 0.9 @ 10%` | Translucent selected/active fill for rows and toggles |
+
+Rules:
+
+- **Never invent a background colour.** `bg-white`, `bg-black`, `bg-gray-*`,
+ `bg-slate-*`, `bg-[…]` and raw `oklch()`/`hsl()` in a component are all
+ violations. If the surface you need is not in the table, the table is wrong —
+ extend it here.
+- `surface-hover` and `surface-selected` are translucent so that they adapt to
+ whatever they are drawn on. Do not add alpha to them again (`bg-surface-hover/50`
+ is a bug).
+- The ladder is monotonic: `sunken < base < raised < overlay` in lightness, in
+ **both** colour schemes. Ordering surfaces the other way (a panel darker than
+ the canvas in dark mode) is the bug this ladder was introduced to fix.
+- Adjacent surfaces must differ in lightness; two neighbours at the same value
+ mean you are expressing hierarchy through a shadow, which is not allowed.
+- Inside a panel, nested containers (a bubble, an inline code block, an icon
+ tile) use `bg-muted` — a quiet opaque fill — not another surface step. Do not
+ nest `surface-raised` inside `surface-raised`.
+
+Legacy aliases kept for compatibility while pages migrate:
+`--background`, `--card`, `--popover`, `--sidebar`, `--accent`,
+`--sidebar-accent` are defined in terms of the tokens above. New code uses
+`surface-*`.
+
+## Borders and elevation
+
+Two border weights exist, and only two:
+
+- **Hairline divider**: `border-b border-border` / `border-r border-border` —
+ panel seams, panel headers, list separators.
+- **Control outline**: `border border-border` on `Input`, `Select` trigger,
+ `outline` buttons and popover/menu containers.
+
+An element gets **both** a border and a shadow only when it floats. Rules:
+
+| Layer | Border | Shadow |
+| ------------------------------------------------------- | ------------------------------------ | ------------------ |
+| Panel (`surface-raised`) | hairline dividers only, `rounded-lg` | **none** |
+| Card (`surface-raised`) | optional `border border-border` | **none** |
+| Overlay (`surface-overlay`) | `border border-border` | `shadow-elevation` |
+| Small controls (switch knob, slider thumb, drag handle) | — | `shadow-control` |
+| Buttons, inputs, badges, tabs, list rows | — | **none** |
+
+`shadow-elevation` and `shadow-control` are the only shadow tokens permitted in
+component code. The legacy `shadow-sm/md/lg/xl/2xl` utilities are aliases of
+`shadow-elevation` and exist only so that un-migrated lines degrade to a
+consistent value instead of a wild one; the design guard fails a build that
+introduces them.
+
+`ring` shadows (`ring-2`, `focus-visible:ring-ring`) are focus rings, not
+elevation, and are always allowed.
+
+## Radius
+
+Three values, no exceptions:
+
+| Value | Class | Use for |
+| ------ | -------------- | -------------------------------------------------------------------------------------- |
+| 6px | `rounded-md` | Controls: buttons, inputs, selects, menu items, list rows, tabs, badges with a label |
+| 8px | `rounded-lg` | Containers: panels, cards, dialogs, popovers, empty states, icon tiles, avatars-in-box |
+| 9999px | `rounded-full` | Pills, avatars, knobs, progress bars, counters |
+
+`--radius: 0.5rem` in `theme.css` derives both scale values, so this is a single
+knob for the whole app. **Forbidden:** `rounded-sm`, `rounded-xl`,
+`rounded-2xl`, `rounded-3xl`, `rounded-[…]`. `rounded-none` is not in the list
+either: a full-bleed element inside a padded container should get its parent
+`p-1` + `rounded-md` items instead of a square child.
+
+Do not round one corner differently (`rounded-t-lg` is allowed only on a header
+that is visually part of a rounded container).
+
+## Spacing and density
+
+Base rhythm is 4 / 8 / 12 / 16 (`gap-1,2,3,4`; `p-2,3,4,6`). Half steps
+(`px-2.5`, `py-1.5`, `gap-1.5`) are tolerated only when aligning text against a
+16/20px icon; do not introduce them anywhere else.
+
+| Element | Height | Padding |
+| ------------------------------------------ | -------- | -------------------------------------- |
+| Panel header | `h-11` | `px-3`, content `gap-2` |
+| Toolbar icon button (`Button size="icon"`) | `size-8` | — |
+| Button (`default`) | `h-9` | `px-4` |
+| Button (`sm`) | `h-8` | `px-3` |
+| Button (`lg`) | `h-10` | `px-5` |
+| Input / Select trigger | `h-9` | `px-3` |
+| List row, compact | `h-8` | `px-2` |
+| List row, comfortable | `h-9` | `px-3` |
+| Sidebar nav item | `h-8` | `px-2` |
+| Card / panel body padding | — | `p-4` |
+| Dialog padding | — | `p-5` |
+| Section gap inside a panel | — | `space-y-3` / `divide-y divide-border` |
+| Page gutter | — | `px-6 py-4` |
+
+Rows are separated with `divide-y divide-border` or a `border-b` hairline, never
+with margin gaps.
+
+## Type scale and text hierarchy
+
+Fonts (bound in `theme.css`, loaded by `index.html`):
+`--font-sans: Inter` for UI and prose, `--font-mono: Fira Code` for code, IDs
+and keycaps. `--font-serif: Lora` is reserved for long-form article titles; it is
+not currently used and must not be introduced without a stated reason.
+
+Three text levels, and only three:
+
+| Level | Class | Use for |
+| -------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------- |
+| T1 — primary | `text-foreground` | Titles, panel headers, list primary line, body of documents and answers, form values |
+| T2 — secondary | `text-muted-foreground` | Descriptions, list secondary line, help text, placeholders, inactive icons |
+| T3 — tertiary | `text-subtle-foreground` | Timestamps, counters, keyboard hints, disabled labels — anything that must be readable but must not attract the eye |
+
+Do not express hierarchy by inventing a new grey (`text-gray-500`), and do not
+use opacity on top of a level (`text-muted-foreground/70`).
+
+Sizes:
+
+| Size | Class | Use for |
+| ---- | ------------- | ------------------------------------------------------------------------------------- |
+| 11px | `text-[11px]` | Keycaps, tiny counters (rare; prefer `text-xs`) |
+| 12px | `text-xs` | Meta line, timestamps, badges, table headers, code |
+| 14px | `text-sm` | **Default UI size**: buttons, labels, list rows, inputs, prose in chat and the editor |
+| 16px | `text-base` | Dialog titles, empty-state body (only where 14px reads cramped) |
+| 18px | `text-lg` | Page titles, empty-state titles |
+| 20px | `text-xl` | Home hero only |
+
+Weights: `font-normal` for body, `font-medium` for interactive labels, panel
+headers, section titles and the selected state of anything. `font-semibold` and
+above are reserved for page titles and the home hero. Never use weight alone to
+express selection — pair it with `bg-surface-selected`.
+
+Line height: UI text uses Tailwind defaults. Long-form reading surfaces
+(`markdown.css`, `noteEditor.css`) use 14px / `line-height: 1.75`.
+
+## Accent and states
+
+`--primary` is the only accent. It is allowed in exactly five places:
+
+1. The primary action of a view (`Button` variant `default`).
+2. Focus indication (`focus-visible:ring-2 focus-visible:ring-ring`).
+3. The selected indicator of a nav/list row: a 2px bar
+ (`before:` / `absolute inset-y-1 left-0 w-0.5 rounded-full bg-primary`) or a
+ filled `bg-primary text-primary-foreground` icon chip.
+4. Inline links: the `Button` variant `link`, and prose links inside long-form
+ content (`.markdown-body`, `.note-editor`).
+5. Progress and slider fill (`bg-primary`), where the colour _is_ the data.
+
+Everywhere else — toolbars, icon buttons, headers, cards, badges, tab labels,
+progress — the UI is neutral. A blue icon in a neutral toolbar is a violation.
+`--destructive` is allowed only on a destructive action and its confirmation
+dialog. `--chart-*` are for charts, graph nodes and mind-map topic colouring.
+
+State expressions (use these literal forms, they are the contract):
+
+| State | Expression |
+| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| hover | `hover:bg-surface-hover` (rows, menu items, ghost/outline buttons) |
+| selected | `bg-surface-selected` + `text-foreground` + `font-medium`; optional 2px primary indicator |
+| focus-visible | `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background` (inputs use `ring-1` with `border-ring`) |
+| disabled | `disabled:pointer-events-none disabled:opacity-50` |
+| transition | `transition-colors` for fills; `transition-[transform,opacity]` for popovers. No bouncy or long animations on chrome. |
+
+A row that is both hovered and selected keeps the selected fill; do not stack
+`hover:bg-surface-hover` on `bg-surface-selected` elements.
+
+A **segmented control** (`Tabs`) is the one place where selection is expressed as
+a surface step rather than a translucent fill, because the thumb must cover the
+track: track `bg-surface-sunken`, selected segment `bg-surface-raised`, no shadow.
+Both steps are monotonic in dark mode, which `bg-muted` + `bg-background` was not.
+
+Small atoms keep the control radius even though it is proportionally rounder:
+a 16px checkbox is `rounded-md`, not a new radius value.
+
+## Dark mode
+
+Dark mode is a first-class theme, not a filter. Requirements:
+
+- Every surface, border and text token has a dark value defined in `theme.css`;
+ a component must never branch on the theme itself.
+- The surface ladder is monotonic in dark mode too (see [Surfaces](#surfaces)).
+- Text levels keep their contrast ordering: T1 > T2 > T3. Do not ship a dark
+ value that reads below 3:1 against its surface.
+- Colour is never the only carrier of state (selection, error, warning) — pair it
+ with weight, an icon, or a border.
+- Both themes must be usable on every page; a page that "looks off in dark" is a
+ blocker, not a follow-up.
+
+## Do / Don't
+
+Do:
+
+- Separate panels with a hairline and one surface step.
+- Use `bg-surface-hover` for hover, `bg-surface-selected` for selection.
+- Keep a screen to one accent action.
+- Put reading content on `surface-raised` with T1 text and generous line height.
+- Reach for `text-sm` before anything else; go smaller only for meta.
+
+Don't:
+
+- `bg-white`, `bg-black`, `bg-slate-*`, `text-gray-*`, `border-[#…]`, raw
+ `oklch(...)` in a component, `bg-card shadow-md` panels, `rounded-xl` cards,
+ `shadow-sm` buttons or inputs, `rounded-sm` controls.
+- Gradients, glassmorphism (`backdrop-blur` except on a floating toolbar over
+ scrolling content), glow effects.
+- Colour icons in neutral chrome; colour-coded sections chosen from `--chart-*`
+ outside chart/graph surfaces.
+- Express hierarchy with `scale-*` transforms on chrome (allowed only inside
+ mind-map / graph editors, where zoom is part of the interaction).
+- `transition-all` on rows (`transition-colors` is enough and avoids animating
+ layout properties).
+
+## Component recipes
+
+Copy these shapes. `cn()` (`@/lib/utils`) is used for merging class overrides.
+
+### Panel
+
+```tsx
+