Skip to content

feat(theme): light/dark theme system v0.1 (designer tokens)#61

Merged
SymbolStar merged 5 commits into
mainfrom
judy/dark-theme-v0.1
Jun 4, 2026
Merged

feat(theme): light/dark theme system v0.1 (designer tokens)#61
SymbolStar merged 5 commits into
mainfrom
judy/dark-theme-v0.1

Conversation

@SymbolStar

Copy link
Copy Markdown
Owner

落地 dora 的 [[designer/openforge-theme-tokens-v0.1.md]] —— 三层 token (Primitive → Semantic → Component),light/dark 都是一等公民。

按设计稿 §7 分 3 个独立 commit,方便逐步 review / 回滚:

Commit 1 — 8ca5c8f semantic token 层

  • :root 补齐 Semantic token(surface/text/border/accent/status/shadow)
  • 加 dark 层:@media (prefers-color-scheme: dark) :root:not([data-theme="light"]) + :root[data-theme="dark"]
  • 老 token(--slack-purple/--rail-bg…)保留作别名,零回归

Commit 2 — 90208b1 清 fallback

  • 全工程 grep var(--x, #hex)54 处全清
  • 顺手把一批失配/缺位 token 名 remap 到新 semantic(--soft → --surface-2--muted → --text-mute--color-primary → --brand-blue …)
  • 非 hex fallback(layout 尺寸 / rgba 阴影)不动 —— 那些不是 PR [judy] fix(xiaof): dark-mode contrast — own text/icon tokens #40 那类 bug

Commit 3 — 449a98a 切换 UI

  • icon-rail 底部加 ☀/🌙/💻 三态按钮
  • <head> 内联 bootstrap script 在 first paint 前读 localStorage.openforge.theme 并打 <html data-theme>无 FOUC
  • 默认 system 跟随;显式 light/dark override

还没动的(dora v0.2 调整 + 后续)

  • muted/placeholder 偏暗、Ghost 按钮看着不像按钮、warn/danger banner 偏浑浊 —— dora 自查时标了 v0.2 跟进,等 scott 拍板品牌色 / 默认主题 / 切换位置 三个问题后统一调
  • code 高亮主题切换(设计稿 §8 待定)

验收

  • 本地 web/app.js Node syntax check 过、web/index.html parse 过
  • 没跑 web E2E(仓里没有),靠人眼真机验:开发模式起服务后用 sidebar 底部切 light/dark/system,刷新看是否持久 + 无白闪

cc @scott @designer

Per designer/openforge-theme-tokens-v0.1: introduce three-layer token
system. Adds full Semantic token set (surface/text/border/accent/status/
shadow) on :root, plus dark layer via @media (prefers-color-scheme: dark)
and explicit :root[data-theme="dark"] override.

Existing legacy vars (--slack-purple, --rail-bg, etc.) kept as aliases to
the new tokens for zero-regression on current selectors. Step 2 will
sweep var(--x, #hex) fallbacks; step 3 adds the sidebar theme toggle UI.
Per tokens-v0.1 §8 禁忌清单: hex fallback is the temperature that breeds
PR #40 -style dark-mode bugs (fallback黑字 落到 dark 黑底). Replace 54
occurrences of `var(--name, #hex)` with bare `var(--name)`, remapping
a handful of legacy/typo'd token names (--soft, --link, --muted,
--text-muted, --text-sub, --border-soft, --chip-bg, --chip-fg,
--row-hover, --panel, --color-primary) onto the semantic tokens added
in step 1. Non-hex var() fallbacks (layout dims, rgba) untouched —
those are not the bug class the rule targets.
icon-rail bottom now hosts a three-state theme toggle (light / dark /
system). Bootstrap script in index.html <head> applies the persisted
choice (localStorage `openforge.theme`) by setting <html data-theme>
before first paint, so there is no FOUC on reload.

Default = system (no data-theme attribute) → @media
(prefers-color-scheme: dark) governs. Explicit light/dark wins.

Closes the v0.1 rollout described in
designer/openforge-theme-tokens-v0.1.md.
@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown

🤖 bot-review (comment-only · phase 1)

Diff: 4 files changed, 451 insertions(+), 190 deletions(-) @ bb66faa

Red-line checks:

  • ✅ A-7.5: no new 'forbidden' code in xiaof

Needs human review — these paths are not eligible for future auto-approve:

  • bin/forge (service CLI — review process-lifecycle changes)

Phase 1: this bot leaves comments only. Auto-approve will be enabled per-path after 1–2 weeks of clean runs. Promotion plan: judy PR #42 follow-up.

Per dora review on PR #61: clearing var(--x, #hex) fallbacks was not
enough. Components were bypassing the token layer entirely with direct
hex literals (background: #fff, color: #1d1c1d, …) so flipping
data-theme had no effect on most surfaces.

This sweep:
- background: #fff/#ffffff/#fafafa/#f8f8f8/#f4f5f7 → --bg / --surface-{1,2,3}
- color: #1d1c1d/#1c1917/#616061/#78716c/#5B6675/#8d8d8d/#a8a29e → --text / --text-mute / --text-soft
- color: #fff (on accents) → --text-on-accent
- border 1px greys (#ddd/#e5e5e5/#eaeaea/#e7e5e4/#E1E4E8) → --border
- accent blacks (#0a0a0a/#1a1a1a) → --accent / --accent-hover
- brand blues (#3B82F6/#2563EB/#60A5FA/#5b9bff/#1264a3) → --brand-blue{,-hover,-soft}
- soft surfaces (#ececec/#f1f1f1/#f0f0f0/#e8f1ff) → --surface-hover / --accent-soft / --brand-blue-soft
- status (#dc2626/#16a34a/#007a5a/#1a7a48/#c00/#fee/#fef2f2/#f0fdf4/#fffbeb/#fff1f2/#e6f5ed/#fecaca/#bbf7d0) → --danger / --ok / --warn + -soft variants

Deliberately NOT remapped:
- icon-rail (#1a1d24/#2a2f3a/#cfd2d8/#6f7480) — always-dark sidebar by
  design, not theme-driven
- taskcenter (.tc-*) — Portal-style component with its own dark palette
  (#171a21/#262b36/#7c5cff/…), per tokens-v0.1 §2 "Portal components
  keep their own tokens"
- brand-fixed avatars (.av-milk/sentry/bugfix/milly/kb) — slack-style
  accent identifiers, locked
- gradients in activity row avatars — purely decorative
- mermaid-error scoped dark callout — locked by upstream convention
- Forge orange ★ (#F5A623) — locked per Favorites PRD §7

Should fix dora's screenshots: white cards (modal/dropdown/composer/
preview/files panels) now follow --bg, text follows --text, borders
follow --border.
PR #61 dora review caught: squad-rail (left column) inverted in dark
because it consumed --slack-purple → --accent, which flips to white in
dark; .squad-item color used --text-on-accent which flips to black →
black text on white panel.

Fix per tokens-v0.1 §2 (Portal/always-dark components keep own tokens):
turn #squad-rail into a token island — locally declare --rail-bg /
--rail-fg / --rail-hover / --rail-active = fixed dark, and shadow
inherited --text-on-accent / --slack-active with fixed dark values so
all descendant selectors (.brand, .squad-item, .new-squad-btn,
.squad-unread-badge, .squad-delete:hover, etc.) keep working unchanged
without needing per-selector edits.

Drops the --slack-purple / --slack-purple-hover aliases (no other
consumer in web/, grep clean).

post-composer dark contrast nit (--surface-2 #1C1F25 vs --bg #0E1014
visually close) deferred to v0.2 per dora — semantic is correct, only
visual gap to widen.
@SymbolStar SymbolStar merged commit d922f1b into main Jun 4, 2026
5 of 6 checks passed
@SymbolStar SymbolStar deleted the judy/dark-theme-v0.1 branch June 4, 2026 14:40
SymbolStar added a commit that referenced this pull request Jun 4, 2026
* feat(theme): add --surface-input semantic token for composers

New --surface-input token gives post/thread composer textareas a distinct
input surface separate from --surface-2 cards/aside:

- light: #FFFFFF (relies on existing --border for delineation)
- dark:  #22262E (~8% brighter than --bg #0E1014, layer separation)

Applied to #post-composer textarea and #thread-composer textarea only.
Modals / nested cards untouched (still --surface-2 / --bg).

Part of dark theme v0.2 (follow-up to #61).

* feat(theme): split --text-on-* axis for status surfaces

--text-on-accent was overloaded as the foreground for accent/brand AND
for status-colored surfaces (.btn-danger hover red, #post-composer
green send btn, modal submit). Dark mode flipped --text-on-accent to
near-black which broke contrast on those status fills.

Add three status-aligned foreground tokens:
  --text-on-ok      = #FFFFFF (light + dark)
  --text-on-warn    = #1D1C1D (yellow surfaces; white fails WCAG AA)
  --text-on-danger  = #FFFFFF (light + dark)

Switch:
  .btn-danger:hover                       -> --text-on-danger
  #post-composer button (send/ok)         -> --text-on-ok
  .modal-actions button[type=submit]      -> --text-on-ok

Accent/brand buttons keep --text-on-accent (theme-inverted by design).

* fix(theme): bump dark --text-mute for AAA readability

Dark --text-mute was #A4ABB6 — visually drifted on dark surfaces,
placeholders and secondary text felt washed out.

Bump to #B7BFCA: contrast vs --bg #0E1014 climbs from ~7:1 to ~8.5:1,
still AAA, and visibly lifts placeholder/secondary text without
flattening the mute/text hierarchy.

Light --text-mute untouched.

* fix(theme): make .ghost-btn read as a button

.ghost-btn already carries border-1px in its base, so 'transparent until
hover' isn't an option here — instead lift the hover affordance:

- hover: border-color jumps to --border-strong (was --text-soft)
- hover: background fills with --surface-hover

light + dark both gain a tactile press surface without any token change.
Border thickness stays 1px in all states, so no layout shift on hover.

(Designer note used '.btn-ghost' / transparent-base; codebase ships
'.ghost-btn' with a permanent 1px border, so executed equivalent without
layout regression.)

* fix(theme): brighten dark warn/danger soft banners

Dark --warn-soft #2A1F0A and --danger-soft #2A1010 read as muddy near-black
brown/maroon — banner intent was bleeding into the surface.

  --warn-soft   #2A1F0A -> #3A2C0F (brighter, slightly desaturated)
  --danger-soft #2A1010 -> #3A1818 (brighter, slightly desaturated)

--ok-soft #0F2A1F kept as-is (designer sign-off retained).
Light mode untouched.

Completes dark theme v0.2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant