Skip to content

refactor(shell): express panel hierarchy with surfaces and add the design-token guard - #46

Merged
mrsibe merged 4 commits into
mainfrom
design/semantic-tokens-and-shell
Sep 24, 2026
Merged

mrsibe merged 4 commits into
mainfrom
design/semantic-tokens-and-shell

Conversation

@mrsibe

@mrsibe mrsibe commented Sep 24, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Moves the app shell (three-panel workspace, sidebar, settings dialog, list rows)
and every remaining screen onto the token layer introduced in the base PR, and
adds npm run check:design, a guard that mechanically enforces the part of
DESIGN.md that can be checked from source text.

Stacked on #45. Merge that one first; this PR only contains the shell, page,
guard and CI work.

Why?

#45 established the vocabulary (surface ladder, three radius values, two
elevation tokens, three text levels) but deliberately stopped at the base
primitives. Until the shell and the pages use it, the ladder does not actually
express anything: panels still floated on shadow-md, list selection still used
bg-primary/10, and the settings dialog put its nav rail and its content panel
on the same surface so the nav had no container.

Fixes #43 (part 2).

What changed?

Shell — SourcePanel / NotePanel / ProcessPanel drop the
rounded-xl border-0 shadow-md trio for rounded-lg border border-border bg-surface-raised; the settings dialog becomes a sunken dialog with a
transparent nav rail and a raised content panel; PanelHeader is 44px; the tab
bar and drag handles lose their shadows and accent hovers; document / note /
generated-item rows share one selected expression (bg-surface-selected,
hover:bg-surface-hover) and their meta lines move to the tertiary text level.

Pages — library, home, reader, chat, quiz, flashcards, mind map and settings:

  • the chat user bubble becomes neutral (bg-muted) instead of a solid accent
    fill; accent is now limited to the primary action, focus, selection, links and
    progress fills;
  • quiz correctness uses a new --success token plus --destructive, with icons
    and labels, instead of blue/green/red literals that only worked in one theme;
  • flashcard type chips become outline badges with a chart-coloured dot, because
    --chart-* sits at one lightness in both themes and a text-bearing fill had no
    readable text colour;
  • the home title drops from 48px/bold to 20px/medium and the quiz score from 72px
    to 36px — both were marketing-page scale inside a desktop app;
  • modal scrims move from bg-black/80 to a --scrim token, the last raw colour;
  • markdown/editor inline code loses its accent colour and gains the control
    radius (it was a fourth radius value in raw CSS).

At this point bg-card / bg-popover / bg-accent / border-input,
shadow-sm|md|lg|xl|2xl, rounded-sm|xl|2xl|[inherit], rounded bare and the
raw Tailwind palette are all gone from src/renderer.

Guard — scripts/check-design-tokens.mjs (npm run check:design) reads the
renderer sources with no build and no dependencies and fails on: a radius
utility outside md/lg/full, an elevation utility other than
shadow-elevation / shadow-control / shadow-none, a palette or arbitrary
colour, alpha stacked on a text level, and a raw CSS border-radius outside the
scale. --list prints the rules and allowlists. It runs in Verify before the
packaging matrix.

Latent bugs fixed on the way

  • the sidebar menu button's outline variant wrapped an oklch() token in
    hsl() — invalid CSS, so the 1px outline never rendered;
  • markdown.css used color: var(--primary) / 80, also invalid, so the link
    hover colour never applied;
  • TopNavigationBar cast a KeyboardEvent through
    as unknown as React.MouseEvent to satisfy a handler that only calls
    stopPropagation(); the parameter is now typed SyntheticEvent and the cast
    is gone;
  • quiz.questionsData was reached through as any as QuizQuestion[] in three
    places; the schema column now carries .$type<QuizQuestion[]>(), which removes
    all three casts (its metadata neighbours move from Record<string, any> to
    Record<string, unknown>, matching the shared DTOs);
  • dead tokens removed: --foreground-dark, --background-dark, and text-h1
    (undefined, so two settings headings silently rendered at body size).

Related issue

Fixes #43

How was this tested?

  • npm run check:design — no violations. Verified negatively too: injecting
    rounded-xl, shadow-md, bg-slate-100, text-gray-500 and
    text-muted-foreground/70 produces all five rule IDs and exit code 1.
  • npm run lint — 0 errors; warnings 118 → 112 (the reductions come from the
    removed casts and dead tokens).
  • npm run typecheck — passes (node + web + test configs).
  • npm test — 11 pass, 0 fail.
  • npm run build (electron-vite) — passes; confirmed .bg-scrim and
    .text-success are generated from the new tokens.

Not verified: the visual result. My environment has no display (no Xvfb), so I
could not launch Electron or capture screenshots. npm run smoke:packaged also
needs a display and was not run locally; CI covers it with xvfb-run.

Screenshots / recordings

Could not be captured (no display in the environment this was built in). The
changes a reviewer should look at:

  • settings dialog now reads as three levels (sunken dialog / transparent rail /
    raised content) and its selected nav item is neutral, not a solid blue chip;
  • chat: both bubbles are neutral, so nothing competes with the answer text;
  • quiz result and question screens are sized like an app, not a landing page;
  • panels are separated by a hairline and a surface step only.
Before After

Pre-existing issues found, not touched

Both are on main and unrelated to this change; I left them alone rather than
widening the diff.

  1. src/main/db/index.ts:275 — getSqlite() has an inferred return type that
    references BetterSqlite3.Database, so TypeScript cannot name it in a
    declaration emit (TS4058). It does not appear in npm run typecheck (no
    declaration emit there). The file is byte-identical to main, so this predates
    the branch; the fix is a one-line explicit return type if you want it.
  2. .github/workflows/verify.yml — actions/checkout@v4 and
    actions/setup-node@v4 are not pinned to SHAs (zizmor: unpinned-uses). This
    PR adds a run: step only and does not touch those lines.

Checklist

  • I have reviewed my own changes.
  • npm run typecheck passes.
  • npm run build passes.
  • I have tested the affected user workflow. Cannot be done here — no display.
  • I have not included unrelated changes.
  • I have updated documentation when necessary.

Desktop / build changes

  • Not applicable — this PR adds a CI step to verify.yml; npm run build:unpack
    and npm run smoke:packaged were not run locally (no display), CI covers both.

@mrsibe
mrsibe changed the base branch from design/tokens-and-primitives to main September 24, 2026 07:39
…dows

The three workspace panels (source / ask / notes), the settings dialog and
the sidebar now sit on the ladder: sunken window chrome, base canvas between
panels, raised content panels with a hairline, overlay only for floating
layers. The panel trio of rounded-xl + border-0 + shadow-md is gone, as are
the shadow-sm on the tab bar and the settings content panel.

Settings gains a real hierarchy: the dialog is sunken, the nav rail is
transparent on it, and the content panel is raised. Previously the rail and
the panel were the same surface-base value, so the nav had no visual
container.

List rows (documents, notes, generated items) share one selected expression,
bg-surface-selected with hover:bg-surface-hover, and their meta lines move to
the tertiary text level. Row padding drops to px-2 py-2, the delete
affordance also reveals on focus-visible, and timestamp/meta text stops
competing with titles.

Two latent bugs fixed on the way:
- the sidebar menu button's outline variant wrapped an oklch() token in
  hsl(), which is invalid CSS, so the 1px outline never rendered.
- TopNavigationBar cast a KeyboardEvent through 'as unknown as MouseEvent'
  to satisfy a handler that only calls stopPropagation(); the parameter is
  now typed SyntheticEvent and the cast is gone.

Also makes bg-muted a recessed fill in dark mode (0.275, below surface-raised
at 0.325), so nested containers read as inset in both colour schemes instead
of flipping direction between them.
Library, home, reader, chat, quiz, flashcards, mind map and settings pages
now use the surface ladder, the three text levels and the two elevation
tokens. This is the last of the legacy usage: bg-card/bg-popover/bg-accent,
shadow-sm/md/lg, rounded-sm/xl/2xl and the raw blue/green/red/purple palette
are gone from src/renderer.

Notable decisions:
- the chat user bubble is neutral (bg-muted) instead of a solid accent fill;
  accent is now limited to the primary action, focus, selection, links and
  progress fills, as DESIGN.md states.
- quiz correctness uses a new --success token plus --destructive, with icons
  and labels, instead of blue/green/red literals that only worked in one
  theme.
- the flashcard type chips became outline badges with a chart-coloured dot:
  --chart-* sits at one lightness in both themes, so a text-bearing fill had
  no readable text colour.
- the home hero drops from 48px/bold to 20px/medium, and the quiz score from
  72px to 36px: both were marketing-page scale inside a desktop app.
- markdown/editor inline code loses its accent colour and gains the control
  radius (it was a fourth radius value in raw CSS).

Two latent bugs fixed on the way:
- markdown.css used 'color: var(--primary) / 80', which is not valid CSS, so
  the link hover colour never applied.
- quiz questionsData was typed through 'as any as QuizQuestion[]' in three
  places. The schema column now carries .$type<QuizQuestion[]>(), which
  removes all three casts; the neighbouring metadata columns move from
  Record<string, any> to Record<string, unknown>.
scripts/check-design-tokens.mjs reads the renderer sources and fails on a
radius utility outside md/lg/full, an elevation utility other than
shadow-elevation/shadow-control/shadow-none, a raw Tailwind-palette or
arbitrary colour, alpha stacked on a text level, and a raw CSS
border-radius outside the scale. It needs no build and no dependencies, so
Verify runs it before the packaging matrix. 'npm run check:design -- --list'
prints the rules.

The three violations it found on the first run are fixed here: a bare
'rounded' on the close-tab affordance, the sidebar's floating variant using a
bare 'shadow', and a comment that contained an example of the removed
arbitrary shadow value.

Modal scrims move from bg-black/80 to a --scrim token, which was the last raw
colour in the tree. DESIGN.md's enforcement section now states exactly what
the guard covers and what it deliberately leaves to review.
--foreground-dark was never defined in theme.css, so the class generated
nothing. The base text-foreground already covers both themes.
@mrsibe
mrsibe force-pushed the design/semantic-tokens-and-shell branch from 9755e35 to 4632643 Compare September 24, 2026 07:41
@mrsibe
mrsibe merged commit 86d8349 into main Sep 24, 2026
3 checks passed
@mrsibe
mrsibe deleted the design/semantic-tokens-and-shell branch September 24, 2026 07:42
@mrsibe mrsibe added the skip-changelog Exclude from generated release notes label Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Exclude from generated release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: 建立 KnowNote/DESIGN.md 与语义化 design tokens,收敛散落的 UI 约定

1 participant