feat(ui): Appearance settings — dark/light/system mode and named themes; fix Add lane silently doing nothing (#42)

- Settings → Appearance: mode Dark / Light / System (follows the OS live) and
  a named theme remembered per mode — dark: Default, Darcula, Apple Dark;
  light: Default, Apple Light, IntelliJ Light, Canvas. Each theme is a
  CSS-variable override block keyed by `data-theme` on <html>; the sidebar
  quick toggle gains System and swaps between the two remembered themes.
- An inline boot script in index.html applies the stored mode and theme
  before first paint, so a reload no longer flashes the default colours.
- Diff +/- rows are easier to tell apart on every theme: a /20 status tint,
  a 3px status bar and a bold coloured marker; the changed-span emphasis no
  longer vanishes on light themes.
- #42: the Add-lane modal (Repo mode / "Current branch") asks
  POST /api/lanes/ensure with `exact: true`. Without it a folder nested inside
  an existing lane resolved to that parent lane with created:false, so the
  modal closed and no new card appeared. When the folder really already has a
  lane, the modal now says which one instead of closing.

Closes #42
This commit is contained in:
2026-09-24 09:44:42 +07:00
parent 2ea2184372
commit 6cf8a82889
33 changed files with 1756 additions and 59 deletions
+8 -1
View File
@@ -2297,7 +2297,14 @@ export const api = {
* location isn't tied to a specific project (e.g. Sagi's mascot lane).
* @returns `{ lane, created }` — the lane (newly created or existing) and whether it was created.
*/
ensure: (body: { cwd?: string; title?: string; pipeline?: string; tool?: LaneTool }) =>
ensure: (body: {
cwd?: string;
title?: string;
pipeline?: string;
tool?: LaneTool;
/** Only a lane on THIS directory counts, not one on a parent of it. */
exact?: boolean;
}) =>
request<{ lane: Lane; created: boolean }>("/lanes/ensure", {
method: "POST",
body: JSON.stringify(body),
+3 -3
View File
@@ -1037,7 +1037,7 @@ export function highlight(source: string, lang: string): Token[] {
* (`--syn-*` in index.css), not on fixed palette steps: the dark set is the
* pastel one this file has always used, and light mode takes the same hues
* four steps darker, because a pastel keyword on a light code block is
* unreadable. Diff rows keep their faint ±10% tint, which works on both.
* unreadable. Diff rows take a /20 status tint, pre-mixed per theme.
*/
export function tokenClass(type: TokenType): string {
switch (type) {
@@ -1068,9 +1068,9 @@ export function tokenClass(type: TokenType): string {
case "boolean":
return "text-syntax-number"; // literals grouped with numbers by colour
case "diff-add":
return "text-syntax-add bg-emerald-500/10"; // green text + faint green row tint
return "text-syntax-add bg-status-success/20"; // green text + theme-aware green row tint
case "diff-del":
return "text-syntax-del bg-red-500/10"; // red text + faint red row tint
return "text-syntax-del bg-status-danger/20"; // red text + theme-aware red row tint
case "diff-meta":
return "text-syntax-keyword";
case "plain":