Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions apps/admin/src/api/drafts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import type {

import { request } from '~/utils/request'

export type DraftSortOrder = 'asc' | 'desc'

export interface GetDraftsParams {
page?: number
size?: number
refType?: DraftRefType
hasRef?: boolean
sortBy?: string
sortOrder?: 1 | -1
sort_by?: string
sort_order?: DraftSortOrder
}

export interface CreateDraftData {
Expand Down
15 changes: 13 additions & 2 deletions apps/admin/src/api/notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,22 @@ import type { NoteModel } from '~/models/note'

import { request } from '~/utils/request'

export type NoteSortKey =
| 'title'
| 'createdAt'
| 'modifiedAt'
| 'weather'
| 'mood'
export type SortOrder = 'asc' | 'desc'

export interface GetNotesParams {
page?: number
size?: number
sortBy?: string
sortOrder?: number
sort_by?: NoteSortKey
sort_order?: SortOrder
/**
* @deprecated backend dropped db_query in v12.10.x pager refactor; param is silently ignored
*/
db_query?: Record<string, boolean>
}

Expand Down
7 changes: 5 additions & 2 deletions apps/admin/src/api/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import type { PostModel } from '~/models/post'

import { request } from '~/utils/request'

export type PostSortKey = 'createdAt' | 'modifiedAt' | 'pinAt'
export type SortOrder = 'asc' | 'desc'

export interface GetPostsParams {
page?: number
size?: number
sortBy?: string
sortOrder?: number
sort_by?: PostSortKey
sort_order?: SortOrder
categoryIds?: string[]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ function createAdminTransport(providerId: string): TransportAdapter {
return async (messages, tools, model, signal) => {
const response = await fetch(`${API_URL}/ai/agent/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: {
'Content-Type': 'application/json',
'x-skip-translation': '1',
},
credentials: 'include',
body: JSON.stringify({ model, messages, tools, providerId }),
signal,
Expand Down
1 change: 1 addition & 0 deletions apps/admin/src/utils/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const $api = ofetch.create({
onRequest({ options }) {
const headers = new Headers(options.headers)
headers.set('x-uuid', _uuid)
headers.set('x-skip-translation', '1')

// GET 请求添加时间戳防缓存
if (options.method?.toUpperCase() === 'GET') {
Expand Down
8 changes: 7 additions & 1 deletion apps/admin/src/views/extra-features/webhook/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,13 @@ export default defineComponent({
data: submitData,
})
} else {
createMutation.mutate(data as any)
// Server requires `secret` as a string; admin form leaves it optional.
// Default to "" when the user did not provide one so creation succeeds.
const createPayload: Partial<WebhookModel> = {
...data,
secret: data.secret ?? '',
}
createMutation.mutate(createPayload as any)
}
}

Expand Down
9 changes: 7 additions & 2 deletions apps/admin/src/views/manage-notes/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { NButton, NEllipsis, NInput, NPopconfirm, NSpace } from 'naive-ui'
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue'
import { RouterLink } from 'vue-router'
import { toast } from 'vue-sonner'
import type { NoteSortKey } from '~/api/notes'
import type { NoteModel } from '~/models/note'
import type { TableColumns } from 'naive-ui/lib/data-table/src/interface'
import type { PropType } from 'vue'
Expand Down Expand Up @@ -225,8 +226,12 @@ export const ManageNoteListView = defineComponent({
return notesApi.getList({
page: params.page,
size: params.size,
sortBy: params.sortBy || undefined,
sortOrder: params.sortOrder || undefined,
...(params.sortBy
? {
sort_by: params.sortBy as NoteSortKey,
sort_order: params.sortOrder === 1 ? 'asc' : 'desc',
}
: {}),
db_query: params.filters?.dbQuery,
}) as Promise<any>
},
Expand Down
5 changes: 3 additions & 2 deletions apps/admin/src/views/manage-posts/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from 'vue'
import { RouterLink } from 'vue-router'
import { toast } from 'vue-sonner'
import type { PostSortKey } from '~/api/posts'
import type {
FilterOption,
FilterState,
Expand Down Expand Up @@ -206,8 +207,8 @@ export const ManagePostListView = defineComponent({
categoryIds: params.filters?.categoryIds,
...(params.sortBy
? {
sortBy: params.sortBy,
sortOrder: params.sortOrder,
sort_by: params.sortBy as PostSortKey,
sort_order: params.sortOrder === 1 ? 'asc' : 'desc',
}
: {}),
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ docs/superpowers/specs/2026-05-06-react-migration/
├── 05-data-layer.md ← request.ts port, TanStack Query patterns, socket.io hook
├── 06-routing-auth.md ← react-router tree, ProtectedRoute, better-auth + passkey
├── 07-layouts-patterns.md ← AppShell, Sidebar, MasterDetailLayout, header-action injection
├── 08-form-system.md ← react-hook-form + zod, ConfigForm DSL port
├── 08-form-system.md ← @tanstack/react-form + zod (Standard Schema), ConfigForm DSL port
├── 09-editors.md ← haklex direct-mount, monaco, codemirror, xterm, draft system
├── 10-charts-misc.md ← G2 hook, kbar swap, shiki/marked, diffs, excalidraw, confetti
├── 11-views-migration.md ← 21 views in batches; preconditions + acceptance per batch
Expand All @@ -43,7 +43,7 @@ docs/superpowers/specs/2026-05-06-react-migration/
| UI primitives | Base UI (`@base-ui-components/react`) | headless; pairs with css.ts |
| Styling | css.ts (`@vanilla-extract/css` + vite plugin) | Compile-time CSS; tokens live as TS exports |
| Design system | Linear dark-canvas (per pasted DESIGN.md) | Lavender accent, four-step surface ladder, no second chromatic accent |
| Forms | `react-hook-form` + `zod` | `zod` already in source repo |
| Forms | `@tanstack/react-form` + `zod` (Standard Schema) | aligns with TanStack Query/Table; zod already in source repo; library swap landed 2026-05-10 |
| Animation | `motion` | User-specified |
| Icons | `lucide-react` | 1:1 swap from `lucide-vue-next` |
| Toast | `sonner` | 1:1 swap from `vue-sonner` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,7 @@ admin-react/
"lucide-react": "latest",
"sonner": "^1.7.0",
"kbar": "^0.1.0",
"react-hook-form": "^7.53.0",
"@hookform/resolvers": "^3.9.0",
"@tanstack/react-form": "^1.29.3",
"zod": "4.3.6",
"ofetch": "1.5.1",
"better-auth": "1.4.18",
Expand Down
138 changes: 134 additions & 4 deletions docs/superpowers/specs/2026-05-06-react-migration/02-design-tokens.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 02 · Design Tokens

**Date**: 2026-05-06
**Status**: v1 calibrated (2026-05-10)
**Owner spec**: [00-roadmap.md](./00-roadmap.md)
**Phase**: P0 (v0) → calibrated end of P1
**Depends on**: 01 (vanilla-extract plugin wired)
Expand Down Expand Up @@ -89,11 +90,30 @@ export const color = {
```ts
// src/styles/tokens/typography.ts
export const fontFamily = {
display: "'Inter', 'SF Pro Display', -apple-system, system-ui, 'Segoe UI', Roboto, sans-serif",
text: "'Inter', 'SF Pro Text', -apple-system, system-ui, 'Segoe UI', Roboto, sans-serif",
display: "'Inter Variable', 'Inter', 'SF Pro Display', -apple-system, system-ui, 'Segoe UI', Roboto, sans-serif",
text: "'Inter Variable', 'Inter', 'SF Pro Text', -apple-system, system-ui, 'Segoe UI', Roboto, sans-serif",
mono: "'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace",
} as const

/** Atomic axes — pick one size + one weight when authoring a new component. */
export const fontSize = {
xs: '11px',
sm: '12px',
md: '13px',
base: '14px',
lg: '16px',
} as const

export const fontWeight = {
regular: '450', // Inter Variable axis; static fonts fall back to 400
medium: '500',
semibold: '600',
} as const

/** Linear icon scale: 16 row-start / 14 secondary / 12 inline-with-text. */
export const iconSize = { lg: 16, md: 14, sm: 12 } as const

/** Composed presets — bundle size + weight + lineHeight + color hint. */
export const typography = {
displayXl: { size: '80px', weight: '600', lineHeight: '1.05', letterSpacing: '-3px' },
displayLg: { size: '56px', weight: '600', lineHeight: '1.10', letterSpacing: '-1.8px' },
Expand All @@ -108,10 +128,25 @@ export const typography = {
button: { size: '14px', weight: '500', lineHeight: '1.20', letterSpacing: '0' },
eyebrow: { size: '13px', weight: '500', lineHeight: '1.30', letterSpacing: '0.4px' },
mono: { size: '13px', weight: '400', lineHeight: '1.50', letterSpacing: '0' },
// Linear-aligned compact-list density (inbox / posts list / notes list / …).
// Only TWO sizes (13/12), only TWO weights (500/450). Hierarchy comes from
// weight + color, NOT size. line-height: normal = font-intrinsic ≈ 1.2.
listTitle: { size: fontSize.md, weight: fontWeight.medium, lineHeight: 'normal', letterSpacing: '0' },
listMeta: { size: fontSize.md, weight: fontWeight.regular, lineHeight: 'normal', letterSpacing: '0' },
listLabel: { size: fontSize.sm, weight: fontWeight.regular, lineHeight: 'normal', letterSpacing: '0' },
} as const
```

**Admin context override**: most admin surfaces will live in `body` / `bodySm`; `display*` tokens exist for the rare oversized empty state, marketing-style auth screen, or onboarding hero. Keep them in the contract; use sparingly.
**Admin context override**: most admin surfaces live in `body` / `bodySm`; `display*` tokens exist for the rare oversized empty state, marketing-style auth screen, or onboarding hero. Keep them in the contract; use sparingly.

**Compact-list density rules** (Linear inbox-aligned — applies to every read-list view: posts / notes / pages / says / recently / comments / etc.)

- Use `listTitle` (13/500/normal/`ink`) for the row's primary text. It is the only text on a row that uses `medium` weight.
- Use `listMeta` (13/450/normal/`inkSubtle`) for everything else on the meta line — status, id, time, counts. Same metric as the title; only color differentiates it.
- `listLabel` (12/450/normal/`inkMuted`) is reserved for chip / pill bodies on coloured backgrounds.
- Never introduce a third list size. If you feel like you need 11 px or 14 px for a list element, change the **color** instead.
- Row height: `min-height: 57px` with `padding: 10px 16px` and `gap: 4px` between the title line and the meta line. This is the "B variant" used by `2026-05-10-posts-list-design.md`.
- Icon-in-text uses `iconSize.sm` (12). Row-start semantic icons use `iconSize.lg` (16). Right-of-row status / action glyphs use `iconSize.md` (14).

### spacing

Expand Down Expand Up @@ -364,6 +399,101 @@ A short header at the top of DESIGN.md will state:

## Open questions

- **Surface ladder calibration**: the four surface step values need confirmation against linear.app computed styles. Owner: whoever picks up implementation. Resolves in P1 calibration window.
- ~~**Surface ladder calibration**~~ — resolved in v1 calibration pass below (2026-05-10).
- **Light theme priority**: defer to 02b. Decide post-P1 whether light theme is part of v1 cutover or a follow-up.
- **Charts theme**: `@antv/g2` and CodeMirror One Dark have their own theme APIs. Spec 09/10 own the bridge from `themeContract.color.*` into those libraries.

---

## v1 calibrated · 2026-05-10

Calibration pass per spec §7 of [`2026-05-09-p1-layout-shell-design.md`](../2026-05-09-p1-layout-shell-design.md). Targets: color surface ladder + hairlines + canvas + active-nav background. Spacing / typography / radius / motion / zIndex unchanged.

### color · before / after

| Token | v0 | v1 | Rationale |
|---|---|---|---|
| `canvas` | `#010102` | `#08090b` | 纯黑过死,与 surface1 之差不可辨;微提亮且偏蓝调以拟 Linear 之深底;保 canvas → surface1 之 step。 |
| `surface1` | `#0f1011` | `#101113` | 微调 hue,使 5-lightness step 更匀;Card / panel 默认底。 |
| `surface2` | `#181a1c` | `#171a1d` | 与 surface1 之差更线性;hover / faint elevation。 |
| `surface3` | `#1f2124` | `#1f2226` | popover / dropdown / 强 hover;保留较显著之提升。 |
| `surface4` | `#26282b` | `#272b30` | 顶层 chip / floating popover;微调 hue 与 step。 |
| `hairline` | `#23252a` | `#23262b` | 标准分隔线;hue 微调贴 surface 序列。 |
| `hairlineStrong` | `#2f3137` | `#2f3239` | 强调分隔,重 emphasis 时用。 |
| `hairlineTertiary` | `#1a1c20` | `#181b1f` | 轻分隔(如卡内分组),原值过亮。 |
| `primary / primaryHover / primaryFocus` | `#5e6ad2 / #828fff / #5e69d1` | unchanged | Linear brand 精确值,不动。 |

### sidebar 之 active 项背景

非 token,乃局部静态值,置于 `src/layouts/AppShell/Sidebar.css.ts`:

| | v0 | v1 | Rationale |
|---|---|---|---|
| `itemRecipe.variants.active.true.background` | `themeContract.color.surface3` | `'#1a1c20'` | 旧值过亮喧宾,新值居 surface2 与 surface3 之间,与 sidebar 底色协而不杂。spec §7 «out of scope: 静态值可解者勿污 contract»,故不入 token。 |

### 不动者

- `spacing` · `radius` · `zIndex` · `typography` · `fontFamily` · `motion` · `elevation` :v0 即定,无变。
- `chrome` 维度 token:在 P1 layout shell 落地时已就位(`src/styles/tokens/chrome.ts`),非本次 v1 calibration 之新增。
- 测试无依赖 hex 值者,`pnpm test` 仍绿。

### 影响

- `:root, :root.dark` 之 CSS 变量值随之而变;所有引用 `themeContract.color.*` 之处自动生效。
- `src/styles/tokens/elevation.ts` 之 recipe 仍引用 `themeContract`,故 elevation 表象随 surface 序列同步刷新。
- 一处静态 hex(active nav)外,无其他直引值。

---

## v2 calibrated · 2026-05-11

Typography pass triggered by Linear inbox-row anatomy: visual hierarchy in compact lists must come from **weight + color**, not size. Posts-list v1 first surfaced the gap (rows felt crowded, 14/500 title vs 12/400 meta read as two competing weights). Source: live-fetched computed styles from `linear.app/lobehub/reviews` of the "Ready to merge" list. Only typography & icon-size tokens shift — color / spacing / radius / motion / zIndex unchanged.

### Linear's measured row anatomy (sample row #14597)

| Element | size | weight | line-height | letter-spacing | color |
|---|---|---|---|---|---|
| Title | 13px | 500 | normal (≈15.6) | normal | `lch(100 0 272)` (≈ ink) |
| Status / id / time | 13px | 450 | normal | normal | `lch(61.4 1.15 272)` (≈ inkSubtle) |
| Branch / chip label | 12px | 450 | normal (≈14.4) | normal | `lch(90.35 1.15 272)` (≈ inkMuted) |
| Row icon (start) | 16 × 16 | — | — | — | `currentColor` |
| Inline meta icon | 12 × 12 | — | — | — | `currentColor` |
| Right-side status / action | 14 × 14 | — | — | — | `currentColor` |

Row container: 889 × **57** px, `padding: 0` outer, `2px 0` wrapper, two text lines centered with ~12 px between baselines. `font-family: 'Inter Variable', 'SF Pro Display', system-ui, …`.

### token additions · before / after

| Symbol | v1 | v2 | Rationale |
|---|---|---|---|
| `fontFamily.text / display` | `'Inter', 'SF Pro …'` | `'Inter Variable', 'Inter', 'SF Pro …'` | 加 Inter Variable 首位,俾 weight 450 真生效;无变体字体之机退至 'Inter' 静体(450 → 400 之舍入)。 |
| `fontSize` | (无原子轴) | `xs:11 / sm:12 / md:13 / base:14 / lg:16` | Linear 之轴只 13/12 二档,但 admin 偶有 11/14/16 之需(pane title / button / right-pane title),故全表立。 |
| `fontWeight` | (无原子轴) | `regular:450 / medium:500 / semibold:600` | Linear inbox 实测之 450 而非 400,俾 meta line 较 caption 更厚;title 之 500 与之差 50,正好成轻重对比而不喧。 |
| `iconSize` | (无原子轴) | `lg:16 / md:14 / sm:12` | Linear 三档:行首语义 16,次级状态 14,inline-with-text 12。无 18/20 — 大于 16 之图标当回到 components 自决。 |
| `typography.listTitle` | (未存) | `13/500/normal/0` | 行标,唯一之 medium-weight 元素。 |
| `typography.listMeta` | (未存) | `13/450/normal/0` | 状态 / id / time / counts 之同一规格,仅以色分级。 |
| `typography.listLabel` | (未存) | `12/450/normal/0` | chip / pill 之内文,inkMuted 色以平衡彩底。 |

### compact-list 密度之规

不再以「每个列表自定 size」而以 token 表所记。posts list / notes list / pages list / says / recently / comments — 凡 read-list 之视,皆遵:

- 行 `min-height: 57px`,padding `10px 16px`,title↔meta 行间 `gap: 4px`。
- 标题 = `typography.listTitle`,唯一 medium。
- 元数据 = `typography.listMeta`,唯色分级(`inkSubtle` / `inkMuted` / `inkTertiary`)。
- 不引第三号字。如须变化,换色,毋换号。
- icon 三档严守 `iconSize.{lg|md|sm}`。

### 不动者

- `color` · `spacing` · `radius` · `zIndex` · `motion` · `elevation` · `chrome` :v1 即定,无变。
- 既有 `typography.{displayXl..mono}` preset 全保留 — 仅追新者 `listTitle / listMeta / listLabel` 与原子轴;既有引用之处不动。
- `body` 全局之 16px / 400 / 1.5 不变(global.css.ts 仍引 `typography.body`)。

### 影响

- `src/pages/posts/view/PostsView.css.ts` 之 row / meta / time / category 全改用 `typography.list*` + `themeContract.color.*`,row 高 56 → **57**。
- `src/pages/posts/view/list/Row.tsx` 之 `lucide` icon size 11 → `iconSize.sm`(12)。
- 后续 list views(spec 11 batch 3a 之 notes / pages / says / recently)必直引此处 token,毋再于 view 内立私规模。
- 既有非 list 视面(dashboard / setup / login / 表单)之 typography preset 不动,故无视觉回归。
- `pnpm typecheck` + `oxlint` + 122 vitest 皆绿(2026-05-11)。
Loading
Loading