blob: fe8412c26a8dfd47d1b67aba747a5ba8d8023ac9 [file] [view]
# Reference Settings — full reverse-engineer (2026-06-23)
> Archived on 2026-07-13. This reverse-engineering snapshot is provenance, not the current Settings contract.
Cross-checked by 4 parallel Explore agents on the extracted bundle at
`/tmp/qoder-asar/out/renderer/assets/`.
This note captures the reference's Settings module so future Maka PRs can
align without re-doing the dig. Naming hygiene applies: do not surface
the reference product name in code, commits, or end-user copy.
---
## 1. Shell shape
Settings is a **full-pane modal** that takes over the workspace area. It
is NOT a small overlay sheet.
- `role="dialog"` + `aria-modal="true"`
- Marker attributes: `data-modal="agents-settings"`,
`data-canvas-dialog="true"`, `data-settings-layout="page"`
- Outer shell:
```
flex h-full min-h-0 w-full flex-col overflow-hidden bg-background select-none
```
- The rest of the chat surface stays visible behind a subtle backdrop;
the modal does not float over a blurred mask — it occupies the canvas.
### Two-rail layout
| Region | Width / sizing | Notes |
|----------|--------------------------------------|------------------------------------------|
| Nav rail | `w-[256px] shrink-0` | Left side, `bg-transparent`, full height |
| Content | `flex-1 min-w-0 overflow-hidden` | Right side, scrolls on the inner column |
| Gap | `var(--agents-content-area-gap)` 4px | Margin between rail and content plate |
Content pane class: `agents-settings-wide-content`:
```css
.agents-settings-wide-content {
background: var(--agents-content-area-bg);
margin: var(--agents-content-area-gap)
var(--agents-content-area-gap)
var(--agents-content-area-gap) 0;
border-radius: var(--agents-content-area-radius); /* 6px */
}
```
Content body inner wrapper (where the page lives):
```
mx-auto min-h-[200px] max-w-3xl pb-16 pt-10
```
`max-w-3xl` = 48rem / 768px. That's the optical reading column for any
settings page body.
### Responsive
| Breakpoint | Behavior |
|------------|-------------------------------------------------------|
| ≥ 1200px | Two-rail (256px rail + content) |
| 900–1199 | Two-rail, tightened |
| < 900px | Compact: rail collapses; header shows back-arrow + title |
---
## 2. Nav rail
The rail is a flat vertical list (no group headings — purely flat).
### Items shown to the end user (in order)
1. `preferences` — **Preferences** (the big aggregator)
2. `profile` — Profile / account
3. `system` — **System** (intentionally near-empty placeholder)
4. `keyboard` — Keyboard shortcuts
5. `vm` — Secure workspace / VM mode
6. `experimental` — Experimental flags
7. `permissions` — System capability toggles
8. `connector` — Integrations / connectors
9. `appshot` — App snapshot (macOS only, gated by `platforms: ["darwin"]`)
### Hidden / dev-only tabs
`appearance` (handled via the Preferences page itself), `privacy`,
`debug`, `hiddenConnector`, `awareness`, `appUpdate`, `models`,
`customModels`, `commands`, `agents`, `worktrees`, `quickpick`,
`voiceInput`, `wechat`, `beta`, `channels`, `legokit`, `archived` — all
`exposed: false`.
Tab registry lives in `index-C02cm0ok.js` around offset ~213400 (look
for `Su=[]`).
### Nav-row recipe
```html
<!-- inactive -->
<button class="squircle-md text-text-secondary/80
hover:bg-[var(--settings-nav-row-selected-bg)]
hover:text-text/80">
<Icon size=… />
<span>Label</span>
</button>
<!-- active -->
<button class="squircle-md
bg-[var(--settings-nav-row-selected-bg)]
text-text/80">
</button>
```
- Icon library: Lucide
- Active state is a **fill** (not border / left-bar), with
`--settings-nav-row-selected-bg` = `var(--color-fill-secondary)`
- Typography: text-sm (14px), normal weight at rest; no weight bump on
active (color/background carry the state)
- Padding (educated from sibling rows): `px-4 py-3`
- Gap between glyph + label: `gap-3`
- Border radius: `squircle-md` (custom corner, not pure rounded-md)
---
## 3. Per-tab content inventory
### 3.1 Preferences (the kitchen sink)
Globals offset ~76380–80000. This single tab carries the bulk of the
end-user controls:
| Control | Type | Notes |
|----------------------------------|-----------------|-------------------------------------------------------------|
| Language | select | UI locale |
| Extended Thinking | switch | Bigger reasoning budget, disables streaming |
| Launch at Login (autoLaunch) | switch | Auto-start |
| Close Window Behavior | radio / select | Ask / Minimize-to-tray / Quit |
| Desktop Notifications | switch | Native OS notifications |
| Sound Notifications | switch | Audio on task completion |
| Include Co-Authored-By | switch | Adds `Co-authored-by:` trailer to git commits |
| Quick Switch (Ctrl/Cmd+Tab) | radio | Workspaces vs Agents |
| Auto-advance after archive | radio | Next / Prev / Close |
| Default Mode (new agent) | radio | Agent / Plan |
| Preview Mode (artifactPreview) | radio | New window / Right sidebar |
| Expand tool calls by default | switch | Tool-block collapsed vs expanded |
| Show tool steps in IM channels | switch | Whether bot-reply surfaces include tool-call sub-steps |
| Tool execution limit | number input | Soft pause when one turn exceeds N tool calls |
| Keep System Awake (preventSleep) | switch | While Agent works |
| Network Proxy | proxy block | System / Manual; URL input + test button + scheme validation |
| Chat Settings (sub-section) | composite | Theme brightness, interface style, glass effects, font face, font size, chat width |
**Key insight**: reference does NOT split "system-level" toggles
(autoLaunch / preventSleep / notifications / proxy / closeWindowAction)
into a separate System tab. They live alongside chat/agent toggles in
ONE long Preferences page.
### 3.2 System
Title only. No fields. Placeholder for future use. The page exists in
the nav but its body is intentionally empty / "to be defined." Confirmed
by walking the bundle.
### 3.3 Profile
Two sections:
- **Avatar**: glyph (first char or emoji) + background color (preset
palette of ~11 tones)
- **Account & Subscription**: email (copy), Account UID (copy),
product UUID (copy), subscription tier (read-only), pricing-page link,
logout
### 3.4 Appearance (`appearance` i18n key — actually rendered inside
Preferences in production)
| Control | Type | Options |
|--------------------------|----------|-----------------------------------------------------------------|
| Interface theme | select | System pref / Light / Dark / Light Glass / Light Clear (win) / Dark Glass / Dark Clear (win) / Classic Dark / Parchment Light / Parchment Dark / From editors |
| Light variant | select | Per-mode theme |
| Dark variant | select | Per-mode theme |
| Workspace icon in sidebar| switch | Project glyph in the sidebar workspace list |
| Always expand to-do list | switch | Full vs compact to-do view |
### 3.5 Keyboard
- Search field across all shortcuts
- Per-action row with primary + alternative binding
- "Reset all to defaults"
- Categories: General / Chats (workspaces) / Agents
- General: Show shortcuts / Open settings / Toggle sidebar / Undo archive
- Chats: Quick switch
- Agents: Create new task / Search all tasks / Search in current task /
Prev tab / Next tab / Focus input / Toggle input focus / Send / New line
### 3.6 VM (Secure workspace)
Brief description: "dedicated space on your computer for running tasks
— faster, more reliable, on-device." Enable/disable toggle.
### 3.7 Experimental
Beta feature toggles. Per-flag rows; roster managed in the renderer
component, not visible as a literal array in the bundle.
### 3.8 Permissions
"System permissions QoderWork needs on this Mac." Capability rows
(filesystem / network / mic / screen recording / accessibility) with
"granted / not granted" status and a "request" CTA.
### 3.9 Connector
Integrations with external apps. Builtin connectors / market connectors
/ custom connectors. Each row: brand glyph, status, enable toggle,
"configure" button.
### 3.10 App Snapshot
macOS-only. Frontmost-app screenshot capture for chat context. Needs
Accessibility + Screen Recording perms. Permission status row + capture
preview.
---
## 4. Repeating visual patterns inside a page
Reference does NOT use bordered card containers around groups of
controls. The default presentation is a **flat row list inside the
`max-w-3xl` column**.
### 4.0 Content-column padding — WAWQAQ msg `2c810f2d` 2026-06-23
WAWQAQ called out the left/right whitespace as visibly wider in
reference than in Maka. Concrete numbers from a second RE pass:
- The content column is `mx-auto max-w-3xl pt-10 pb-16` (= 768px wide,
40px top, 64px bottom). No explicit `px-N` is applied inside the
column — the side whitespace comes from the column NOT filling the
right pane.
- At the typical desktop window width (~1280px viewport, 256px nav
rail), the right pane is ~1024px. A 768px centered column leaves
~128px of whitespace on each side. That's the "wider padding" effect.
- Inside each row, padding is `px-5 py-4` (20px / 16px). Rows do NOT
carry their own border, background plate, or shadow — separation is
pure spacing.
**Maka delta found 2026-06-23**:
- `.settingsStructuredPage` had `gap: 16px` but NO `max-width` / `margin: 0 auto` — rows stretched to fill the right pane. Fixed to `max-width: 768px; margin: 0 auto; padding: 40px 24px 64px`.
- `.settingsFormRow` had `border-bottom: 1px solid var(--border)` and `padding: 6px 0` — both wrong. Reference uses spacing-only and `px-5 py-4`. Fixed to `padding: 16px 20px`, no border-bottom.
- `.settingsRow` was sharing `.providerCard` chrome (bordered, tinted, lifted). Provider cards keep their chrome (they ARE cards); `.settingsRow` flattened to a flex row.
- Label was 12px / 600 weight; bumped to 14px / 500. Sub-text 11px → 12px. Matches reference `text-sm font-medium` + `text-xs text-text-quaternary`.
### 4.1 Row
```
flex items-center gap-3 px-4 py-3 /* group */
├── label column
│ ├── truncate text-sm font-medium text-text (title)
│ └── mt-0.5 truncate text-xs text-text-quaternary (hint)
└── control (right-aligned)
```
- No border / no card background per row by default
- Visual separation between rows is achieved by spacing alone (no
border-bottom)
- Hover state moves via the parent `.group` (e.g., shows a kebab menu)
### 4.2 Sub-section heading inside a tab
```
text-sm font-semibold text-text
mb-4
```
### 4.3 Switch
- ~20×20px wrapper, `rounded-md`
- On: `bg-primary/20 ring-1 ring-primary/40 scale-[0.97]`
- Off: subtle gray fill (`bg-gray-100 dark:bg-neutral-600`) with inset shadow
- Transition: `transition-all duration-200`
### 4.4 Input
```
w-full px-3 py-2 text-sm bg-background border rounded-md
border-border-tertiary/80
focus:outline-none focus:ring-2 focus:ring-primary/50
```
- Error: `border-error-border`
- Password / icon suffix: `pr-9`
### 4.5 Button
- Primary: `bg-primary text-primary-foreground`
- Secondary: `bg-muted text-foreground`
- Outline: `border border-border text-foreground hover:bg-muted`
- Destructive: `text-error hover:bg-error-bg`
- Icon-only sizes: `size-7 rounded-md` (28px square), `size-8 rounded-full` (32 round)
### 4.6 Chip / badge
```
rounded bg-fill-tertiary px-1.5 py-0.5 text-[10px] font-medium text-text-tertiary
```
### 4.7 Content scroll
The page body owns its scroll:
```
min-h-0 flex-1 overflow-y-auto scrollbar-none
```
Custom scrollbar is hidden. The inner content column has
`pb-16` bottom padding so the last row never hugs the modal edge.
---
## 5. Tokens used inside Settings
| Token | Value |
|----------------------------------|----------------------------------------------------------------|
| `--agents-content-area-bg` | `var(--color-bg-container)` |
| `--agents-content-area-gap` | `4px` |
| `--agents-content-area-radius` | `12px` |
| `--agents-layout-bg` | `var(--surface-canvas)` (flat neutral shell — **DO NOT** paint a 172deg gradient here, see warning below) |
| `--settings-nav-row-selected-bg` | `var(--color-fill-secondary)` |
> ⚠️ **DO NOT paint a 172deg gradient on the layout/shell background.**
>
> The RE notes here historically said `--agents-layout-bg` is
> `linear-gradient(172deg, var(--color-fill-tertiary) 19.61%, var(--color-bg-container) 81.35%)`. That value was an **artifact of the bundle CSS we copied**, not what the live reference product actually paints.
> WAWQAQ has called this out four times in a row (msgs `1e693dee` /
> `5d3b10e5` / `486b5611` / `4a1b8c13`) — "谁让你他妈的用渐变的啊?
> 参考实现就没有啊". Every time we re-added the gradient because
> some atlas/RE note claimed it was canon.
>
> Source-side enforcement: `apps/desktop/src/main/__tests__/chat-chrome-no-gradient-contract.test.ts` fails if any of `.appFrame`, `html[data-os="darwin"] .maka-session-panel`, or the floating panel reintroduces a gradient / border-right / smaller-than-12px radius. If a future RE iteration finds a *new* place where the reference product genuinely does paint a gradient, please update the contract test with the new exception in the same PR, don't silently lift the assertion.
>
> Use a **flat neutral shell** (`var(--surface-canvas)`) behind the
> white content surface. Do not collapse the shell to the same white
> as the content card: that removes the visible lower radius and lets
> the bug come back even without a border or gradient.
>
> Rendered-pixel enforcement: after capturing
> `sidebar-long-sessions/light-1280-motion`, run
> `npm --workspace @maka/desktop run screenshots:chat-chrome:check`.
> It verifies the actual PNG has a visible shell/content delta, no dark
> one-pixel seam, and readable bottom surface corners.
>
> Surface radius is **12px** (was historically noted as 6px). 6px was
> geometrically there but optically invisible — bottom corners of the
> floating content card didn't read against a same-color shell.
Color tokens consumed inside settings:
- `--color-text` (primary text)
- `--color-text-secondary`
- `--color-text-tertiary` (hints)
- `--color-text-quaternary` (de-emphasized)
- `--color-border` (strong divider)
- `--color-border-tertiary` (input borders)
- `--color-fill-secondary` (active row fill)
- `--color-fill-tertiary` (chip background)
- `--color-bg-container` (page surface)
- `--color-primary`, `--color-primary/20`, `--color-primary/40` (focus, switch on, ring)
---
## 6. Routing, state, behaviors
- Active tab is stored in the URL: `?view=settings&tab=preferences`.
No localStorage for the active tab — round-tripping the URL restores
the page.
- Deeplink scheme: external triggers can deep-link to a specific tab:
`qoder-work://settings/preferences`. Maka's equivalent could be
`maka://settings/<tab>`.
- Default tab when opened with no `tab` param: `preferences`.
- Modal close is escape-key + click-outside backdrop; focus is trapped
inside `[role="dialog"]`.
- Telemetry: `settings_tab_view` fires when a tab is selected;
`settings_heartbeat` ticks every 300s with a feature-flag snapshot.
- Saves: per-control, no explicit batch / "save" button visible.
Renderer dispatches IPC writes per change; toasts surface failures.
---
## 7. Implications for Maka
This RE matters because WAWQAQ's ask — "add a 系统设置 section" — is
ALMOST CORRECT but the deeper truth from the reference is:
**Reference does NOT scatter system-level toggles into their own
narrow pages.** It collapses them into ONE long `Preferences` page,
and reserves a near-empty `system` tab for future expansion.
Maka today has 17 nav items in Settings (some of them — `网络`,
`健康`, `开放网关`, `语音模型`, `每日回顾`, `记忆` — are single-purpose
pages that mirror exactly the "wasteful single-purpose page" pattern
WAWQAQ flagged for `网络`).
The closer-to-reference move is to **consolidate**, not add another
section. Concrete proposal:
### Proposed consolidated Maka Settings nav (target: ~9 items)
| Group | Item | Holds |
|-------------|-------------|------------------------------------------------------------|
| 基础 | 通用 | Aggregator: 隐身模式, 启动行为, 新对话模式, 默认模型, 通知, 网络代理, 保持唤醒, 自动更新, 关闭窗口行为 |
| 基础 | 外观 | 个性化 + theme + palette + font (merge 主题 + 个性化) |
| AI | 模型 | Provider connections + OAuth + per-model config |
| AI | 记忆与回顾 | Merge `记忆` + `每日回顾` |
| AI | 语音与网关 | Merge `语音模型` + `开放网关` |
| 集成 | 机器人对话 | Telegram / WeChat / Slack bot config |
| 集成 | 联网搜索 | Tavily provider config |
| 数据/系统 | 数据 | Export / import / wipe |
| 数据/系统 | 权限与能力 | Capability toggles + filesystem allowlist |
| 其他 | 健康 | Diagnostic / status / logs |
| 其他 | 关于 | Version + license + acknowledgements |
Specifically: **drop the standalone `网络` page; move proxy into 通用**.
That's the minimum WAWQAQ asked for, but ride the same PR to merge
`主题 + 个性化` into `外观` per reference, since 外观 is reference's
canonical name for that grouping.
### Settings page-body recipe to copy
For ANY settings page body, switch to:
- `max-w-3xl mx-auto pt-10 pb-16` inner column
- Flat row list (`flex items-center gap-3 px-4 py-3` per row)
- `text-sm font-medium` title + `text-xs text-text-quaternary` hint
- No bordered card around row groups
- Sub-section heading: `text-sm font-semibold mb-4`
- Switches / inputs / chips per §4 above
This is the visual "reference 真好看" pattern WAWQAQ asked us to study —
the page is uncluttered because rows have NO chrome, only spacing.
---
## 8. PR plan suggestion
Two PRs, ship in sequence:
**PR-SETTINGS-ALIGN-IA-0** — Information architecture consolidation
- Drop standalone `网络` page; fold proxy into a "网络代理" sub-section of 通用
- Merge `主题 + 个性化` → `外观`
- Merge `记忆 + 每日回顾` → `记忆与回顾`
- Merge `语音模型 + 开放网关` → `语音与网关`
- Update SETTINGS_NAV + section render switch + all deeplinks (`maka://settings/...`)
- Pin via contract test: nav id allowlist + deeplink mapping
**PR-SETTINGS-PAGE-BODY-0** — Visual pattern conversion
- Convert every page body from `settingsStructuredPage / SettingRow`
card chrome to the flat-row + `max-w-3xl` recipe
- Add `.maka-settings-row`, `.maka-settings-page` primitives in styles.css
- Sweep all SettingsPage components to use them
Tackle them in this order so the IA stops shifting while we're polishing
the bodies.