Skip to content

Document Runtime v2 architecture evolution - #4

Merged
likun666661 merged 5 commits into
runtime-kernel-upstreamfrom
runtime-v2-architecture-doc
Jun 14, 2026
Merged

Document Runtime v2 architecture evolution#4
likun666661 merged 5 commits into
runtime-kernel-upstreamfrom
runtime-v2-architecture-doc

Conversation

@likun666661

Copy link
Copy Markdown
Member

Summary

  • Add docs/runtime-v2-architecture-evolution.md as the long-term Runtime v2 architecture guide.
  • Document the current SessionManager -> AgentRun -> AiSdkBackend -> AI SDK streamText runtime path.
  • Propose RuntimeRunner, InvocationContext, AgentFlow, AiSdkFlow, RuntimeEvent ledger, projections, and migration phases while keeping AI SDK as the first-class default flow engine.

Verification

  • git diff --cached --check before commit
  • doc-only change

@likun666661
likun666661 merged commit 191fe8f into runtime-kernel-upstream Jun 14, 2026
jackwener added a commit that referenced this pull request Jun 21, 2026
…llback source label

UI side of the Z.ai-only-shows-glm-4.x bug @WAWQAQ flagged. Backend
patch lives with @xuan (`packages/runtime/src/model-fetcher.ts` —
remove silent fallback catch, throw generalizedErrorMessage instead).

This commit makes the renderer:

1. **Auto-fetch after save** — when a connection is saved with a new
   secret (or the model list is still empty), `save()` kicks
   `refreshModels({ silent: true })` so the user doesn't land on a
   row whose dropdown only contains the static fallback list (e.g.
   Z.ai → just glm-4.7 / 4.6 / 4.5). Closes the discoverability gap.

2. **Toast on refresh failure** — `refreshModels()` now catches and
   surfaces a structured toast: title "拉取模型失败 · {name}", body
   "{generalizedErrorMessage} · 当前继续显示静态列表,请确认 API
   key / Base URL / 代理设置后重试。" Manual refresh also gets a
   success toast on the count of fetched models so the user sees the
   action landed.

3. **Fetched-vs-fallback source label** — `.providerModelSource` line
   under the dropdown explicitly says which list is shown. Fetched
   → success tone "实时拉取的 N 个模型(最新一次成功)". Fallback →
   info tone "静态备用列表(N 项)。点「从 API 刷新」拉取该 provider 的
   真实模型清单。" Per @kenji's status contract item #4: fetched vs
   fallback must never look the same.

Typecheck + build green.
jackwener added a commit that referenced this pull request Jun 21, 2026
…doc-merge)

1. **iframe sandbox/CSP language tightened** (@kenji #1):
   - Remove the "separate webContents partition + permissive CSP for
     iframe" promise — DOM `<iframe>` doesn't get an independent
     partition without `<webview>` / BrowserView. Don't promise what
     the DOM can't deliver.
   - Add `allow-forms` and `allow-modals` to the not-allowed list
   - Switch from `src="file://..."` to `srcdoc={await readText()}` so
     content delivery goes through the path-guard read helper
   - Note that global CSP `default-src 'self'` stays locked — sandbox
     attribute carries all isolation

2. **Symlink-aware path guard** (@kenji #2):
   - Both `realpath(artifactRoot)` AND `realpath(target)` required,
     then containment check
   - Mirror PR56 open-path-guard which already has symlink-escape +
     `..` + URL-scheme test coverage

3. **readBinary MIME allow-list** (@kenji #3):
   - Allowlist: image/png/jpeg/gif/webp/svg+xml, application/pdf
   - MIME sniffed at read time, NOT trusted from record.kind or
     record.mimeType
   - Unknown → `reason: 'unsupported_mime'` + UI shows metadata +
     "在 Finder 中打开" / "另存为" actions (no raw bytes to renderer)

4. **Deleted tombstone blocks reads** (@kenji #4):
   - `status: 'deleted'` records immediately return `reason: 'deleted'`
     from readText / readBinary — even if the underlying file still
     exists on disk
   - Surface table updated: deleted row preview shows "此 artifact 已
     删除,预览已停止"
   - Permanent path purge is a separate Settings · 数据 flow

5. **PR108c runtime hook scope** (@kenji #5):
   - First version does NOT do LLM extractor
   - Only deterministic outputs: Write→file record, Edit→diff record,
     Bash with explicit redirect → file record
   - stdout/stderr NOT auto-promoted (noise)
   - LLM extractor (parse ```html``` from assistant message) deferred
     to PR109+

Also: type rename `storagePath` → `relativePath` to enforce contract
in the type system; full reason union widened on both read helpers.

doc total: 1416 lines.
jackwener added a commit that referenced this pull request Jun 21, 2026
Earlier §9.1.5 #4 promised that iframe `<a href>` clicks would route
through PR96 `setWindowOpenHandler` → `shell.openExternal`. That was
documentation incorrect — per @kenji's review: without `allow-popups`
in the sandbox attribute, iframe link clicks don't bubble up to the
parent renderer's window-open handler. They silently fail or get
blocked by the sandbox.

Replaces #4 with the right MVP shape:
- iframe sandbox blocks ALL navigation by default — keep that as the
  contract
- preview top status bar shows "此预览中已禁用外部链接 · {N} 个链接"
  (count of <a href> in srcdoc), so users aren't confused why
  clicks do nothing
- Users who need to follow links use "在 Finder 中打开" from the
  artifact toolbar and open the source file in their browser

Future enhancements (NOT PR108b):
- HTML transform: rewrite <a href> → <button data-maka-link> + inline
  postMessage script, main renderer is the only shell.openExternal
  gateway
- Or: postMessage allowlist for iframe ↔ main renderer

Safety-first MVP: HTML can be VIEWED, not NAVIGATED. Cheap UX
sacrifice, clear trust boundary.
jackwener added a commit that referenced this pull request Jun 21, 2026
Consumes the turn-level contract from PR109c. Implements two of @kenji's
remaining PR109d review gates:

**(d) Failed turn AlertOctagon banner** (@kenji gate #3)
TurnView renders a destructive-tone banner above the assistant body
when `turn.status === 'failed'`. Distinct from PR58 tool-error banner
because it's turn-scoped (the whole turn failed, not just a tool).

Copy is generalized Chinese phrase translated from `turn.errorClass`
via new pure helper `describeTurnErrorClass()` in
`session-status-presentation.ts`:

  timeout                → 请求超时
  auth / 401 / 403       → 鉴权失败
  rate_limit / rate_*    → 触发模型速率限制
  network / fetch / econn → 网络错误
  provider_unavailable / 5xx → 模型服务暂不可用
  tool_failed            → 工具调用失败
  permission_required    → 等待权限确认
  (unknown / empty)      → 未知错误

10 node:test cases lock the mapping invariant: never leak raw enum
identifier, always Chinese, case-insensitive, fallback to "未知错误".

**(e) Lineage badges** (@kenji gate #4)
- Forward badges sit at the top of the new turn:
  "重试自 turn ${shortId}" / "重新生成自 turn ${shortId}".
  Source: `turn.retriedFromTurnId` / `turn.regeneratedFromTurnId` on
  the turn record itself.
- Reverse badges sit at the bottom (above the footer actions):
  "已重试 → turn ${shortId}" / "已重新生成 → turn ${shortId}".
  Source: derived UI-side via existing `deriveTurnLineageMap()` —
  NOT persisted in storage (@kenji review on PR109c kept this side
  immutable).
- Both directions share the same TurnLineageBadge type; UI
  distinguishes via `data-direction="forward"|"reverse"` for tone
  (forward = info-tinted, reverse = success-tinted).
- Click handler scrolls the target turn into view via
  `data-turn-id` attribute querySelector (smooth, block center).

Files:
- `packages/ui/src/components.tsx`:
  - `TurnView` gains `failedReasonLabel` + `lineageBadges` +
    `onLineageBadgeClick` props
  - New `TurnLineageBadge` exported interface
  - Renders failed banner + forward/reverse badge rows
  - `ChatView` gains `turnFailedReasonLabels` +
    `turnLineageBadgesByTurn` + `onLineageBadgeClick` props
- `apps/desktop/src/renderer/main.tsx`:
  - Consolidates `turnFooterActionsByTurn` + `turnFailedReasonLabels`
    + `turnLineageBadgesByTurn` into a single useMemo (one pass over
    materialized turns)
  - `handleLineageBadgeClick()` scrolls target turn via querySelector
- `apps/desktop/src/renderer/session-status-presentation.ts` —
  new `describeTurnErrorClass()` helper
- `apps/desktop/src/renderer/styles.css` — `.maka-turn-failed-banner`
  (destructive), `.maka-turn-lineage-row` + `.maka-turn-lineage-badge`
  (info-tinted forward, brand-deep-tinted reverse)

Per @kenji review (PR109e thread): lineage badges only do scroll/link
— no IPC, no pending mask needed. If a future "retry from badge"
shortcut is added, it MUST reuse the same `pendingTurnActions`
channel from PR109d.

Tests: 234 → 243 desktop passing (+9 describeTurnErrorClass).
check-a11y 0 violations.

Deferred to PR109f:
- Branched session banner ("分自 ${parentSessionName}" + aborted
  starting-point copy "从中断前分支")
- `turn-control-history` fixture + smoke Path 15
jackwener added a commit that referenced this pull request Jun 21, 2026
…inese-first labels, stub views for non-implemented modules

Implements the IA refactor WAWQAQ locked in msg `b86b47d1`: the
sidebar's second part is now a top-level 5-module nav (会话 / 搜索 /
计划 / 技能 / 每日回顾) rather than session filter tabs. Pinned /
Archived / Chats become internal Sessions filters. Settings stays at
the bottom; a placeholder 版本更新 button sits above it.

Per xuan `47e204f2` priorities applied directly:
  - #2 IA: top-level modules; filters moved into Sessions.
  - #4 visual hierarchy: nav rows reuse `.maka-nav-row` (transparent
    bg, accent-tinted selected state, no card stacking).
  - #5 Chinese-first naming throughout the surface; English stays
    as accessible keywords in the command palette.
  - #7 Search nav item placeholder for Phase 4 modal; clicking now
    switches the section to `'search'` and renders an empty-state
    stub. Phase 4 will swap the section switch for an
    `onOpenSearchModal()` callback (already plumbed through props).
  - #8 disabled / hover / focus states pinned for the Update
    placeholder (aria-disabled + data-disabled + CSS dim).

== Type-level changes ==

`packages/ui/src/components.tsx`:

  export type NavSelection =
    | { section: 'sessions'; filter: SessionFilter }
    | { section: 'search' }
    | { section: 'automations' }
    | { section: 'skills' }
    | { section: 'daily-review' };

  // New module label map (Chinese-first):
  const MODULE_NAV_LABEL: Record<NavSelection['section'], string> = {
    sessions: '会话', search: '搜索', automations: '计划',
    skills: '技能', 'daily-review': '每日回顾',
  };

== SessionListPanel JSX shape ==

  <aside>
    <header>新建对话 button</header>
    <nav.maka-sidebar-modules>          5 module nav rows  (NEW)
    <div.maka-session-filter hidden={!sessions}>      Pinned/Chats/Archived
    <div.maka-session-search hidden={!sessions}>      session-name search
    <section.maka-session-list>
      ├ skills view (existing, reused for `selection.section==='skills'`)
      ├ sessions view (existing, when 'sessions')
      └ STUB_VIEWS[section] (search / automations / daily-review)
    <footer.maka-session-panel-footer>
      ├ 版本更新 button (NEW; UI placeholder, aria-disabled by default)
      └ 设置 button (existing; renamed from "Settings")
  </aside>

Skills no longer appears in the footer — it lives in the top-level
module nav alongside the other 4. The chat surface (`ChatPanel`)
shows a neutral "从 会话 选择对话" message when the active section
is `search` / `automations` / `daily-review`.

== Persistence ==

`apps/desktop/src/renderer/main.tsx readNavSelection`: recognizes
`search` / `automations` / `daily-review` as valid section variants
when restoring from localStorage. Corrupted entries fall back to
`{ section: 'sessions', filter: 'chats' }`.

== CSS ==

`apps/desktop/src/renderer/styles.css`:

  .maka-session-panel grid-template-rows:
    auto auto auto auto minmax(0, 1fr) auto;

  /* Row 2 is the new module nav. Phase 1's `.maka-session-list` grid
   * fix (auto + minmax(0, 1fr)) and `.maka-list-stack` overflow:auto
   * are preserved verbatim. */

  .maka-sidebar-modules { display: grid; gap: 2px; padding: 4px 8px; }

  /* Disabled nav-row dimming (used by 版本更新 placeholder). */
  .maka-nav-row[aria-disabled="true"],
  .maka-nav-row[data-disabled="true"] { cursor: not-allowed; opacity: 0.55; }
  .maka-nav-row[aria-disabled="true"]:hover,
  .maka-nav-row[data-disabled="true"]:hover { background: transparent; }

== Tests ==

  core 392 / storage 50 / runtime 103 / desktop 645 = 1190 pass.
  Typecheck + build green across all workspaces.
  Phase 1's CSS contract test still passes (grid + min-height + overflow
  pins survive the IA refactor).

== Out of scope (Phase 3 / 4) ==

  - Session row slimming (~85px → ~32px). Phase 3.
  - Search modal implementation. Phase 4 (resurrects `useThreadSearch`
    from `yuejing/pr-search-2.6`; swaps current Search stub for modal
    trigger).
  - Real auto-update wiring (Electron updater). Future PR-AUTOUPDATE-0.
  - Automations / Daily Review feature implementation. Future
    PR-TIME-* / PR-DAILY-*.

Anchors:
  WAWQAQ msg `b86b47d1` + `f5f6f834` (IA + Skills inclusion + Update button).
  xuan msg `47e204f2` (8-point priority list); `dc790a54` (Phase 1 scope refinement);
  `adcf0c95` (per-phase gate workflow).
  kenji msg `fcd9c54f` (consolidated packet design); `0f7bb872` (scroll P0 architecture).

Branch base: `655a09f` (Phase 1 + fixup, on top of main `809875a`).
jackwener added a commit that referenced this pull request Jun 21, 2026
…l + 版本更新 passive (4 nits + capability state CSS)

WAWQAQ msg `1893c08e` 没动静 → starting PR-UX-POLISH-1 in parallel
with kenji's PR-DESKTOP-SMOKE-0 instead of waiting on the Sidebar
resize verdict. Commit lands on top of `yuejing/pr-sidebar-ia-0`
so it merges as a stack once Sidebar is greenlit.

Per ratified scope (yuejing UX audit msg `9c779b56` + xuan +
kenji boundary 1 closed-state enum):

1. Filter input shortcut hint corrected:
   `筛选会话…  F 聚焦` → `筛选会话…  ⌘F 聚焦`
   The actual binding is ⌘F (Meta+F) via the existing
   `useEffect` in `SessionListPanel`; the lone `F` placeholder
   was misdocumenting the chord.

2. Composer streaming hint disambiguated from reasoning stream:
   `Maka 正在思考…` → `Maka 正在回答…` (zh)
   `Maka is thinking…` → `Maka is responding…` (en)
   ReasoningPanel still uses `正在思考…` / `Thinking…` for the
   model's extended-thinking stream. Distinct signals now have
   distinct copy — composer = output-streaming;
   ReasoningPanel = reasoning-streaming.

3. Sidebar group label visual gap:
   Without `display: flex; gap` on `.maka-list-group-label`, the
   `<span>label</span><span>count</span>` rendered as `会话65`
   (no separator). Added `display: flex; align-items: baseline;
   gap: 6px` so the rendered form is `会话 65` (or with future
   bullet separator: `会话 · 65`).

4. 版本更新 footer entry switched from aria-disabled fake-button
   to passive `data-state="coming_soon"` informational tag with
   small `即将推出` badge. Removes the "looks tappable but does
   nothing" affordance. When `onOpenUpdate` IS wired (future
   PR-AUTOUPDATE-0), the button form returns automatically.

   This is the first integration of the capability presentation
   enum (kenji boundary 1, msg `88a9f29d`): UI consumes the state
   via props, never derives `operational` from token/config
   presence. CSS adds `.maka-nav-row[data-state="coming_soon"]`
   styling + `.maka-nav-row-state-badge` pill for the "即将推出"
   chip. The closed enum
   (`available / coming_soon / needs_config / blocked_by_permission /
   disabled_by_policy / unsupported_platform`) is documented in
   `notes/pr-ux-polish-1-gate.md`.

Eyebrow icon variety (audit P2 #5) skipped — re-examined source
and setup heroes already use varied icons (SettingsIcon, KeyRound,
Cpu, AlertCircle via SetupHero); only NeedsConnectionHero and
ReadyEmptyHero still use Sparkles which is correct for those
states.

Stale pill tone (audit P1 #4) skipped — already amber-toned
(`--warning` token at 0.18 alpha + warning-text); the only weak
signal is the 0.7 opacity row dim, which is the intentional
secondary signal.

Gates:
- typecheck clean, UI + renderer build clean
- desktop tests: 643 pass / 0 fail (no test additions yet —
  capability enum contract test arrives in commit 2 alongside
  the search-service rename + daily-review consolidation +
  permission-center cleanup + modal backdrop token unification)
- core tests: 392 pass

Subsequent commits in this PR (commit 2+):
- Permission Center "即将可用" hide / change to passive copy
- Settings · 搜索服务 → 联网搜索 rename
- Daily Review Settings sub-page consolidation
- Modal backdrop token unification
- `? 快捷键` sidebar chip (small a11y discoverability add)
- Extend visible-copy-hygiene-contract with capability enum
  presentation gate (no `coming_soon` should render as <button>
  without explicit `data-state="coming_soon"`)
jackwener added a commit that referenced this pull request Jun 21, 2026
…doc-merge)

1. **iframe sandbox/CSP language tightened** (@kenji #1):
   - Remove the "separate webContents partition + permissive CSP for
     iframe" promise — DOM `<iframe>` doesn't get an independent
     partition without `<webview>` / BrowserView. Don't promise what
     the DOM can't deliver.
   - Add `allow-forms` and `allow-modals` to the not-allowed list
   - Switch from `src="file://..."` to `srcdoc={await readText()}` so
     content delivery goes through the path-guard read helper
   - Note that global CSP `default-src 'self'` stays locked — sandbox
     attribute carries all isolation

2. **Symlink-aware path guard** (@kenji #2):
   - Both `realpath(artifactRoot)` AND `realpath(target)` required,
     then containment check
   - Mirror PR56 open-path-guard which already has symlink-escape +
     `..` + URL-scheme test coverage

3. **readBinary MIME allow-list** (@kenji #3):
   - Allowlist: image/png/jpeg/gif/webp/svg+xml, application/pdf
   - MIME sniffed at read time, NOT trusted from record.kind or
     record.mimeType
   - Unknown → `reason: 'unsupported_mime'` + UI shows metadata +
     "在 Finder 中打开" / "另存为" actions (no raw bytes to renderer)

4. **Deleted tombstone blocks reads** (@kenji #4):
   - `status: 'deleted'` records immediately return `reason: 'deleted'`
     from readText / readBinary — even if the underlying file still
     exists on disk
   - Surface table updated: deleted row preview shows "此 artifact 已
     删除,预览已停止"
   - Permanent path purge is a separate Settings · 数据 flow

5. **PR108c runtime hook scope** (@kenji #5):
   - First version does NOT do LLM extractor
   - Only deterministic outputs: Write→file record, Edit→diff record,
     Bash with explicit redirect → file record
   - stdout/stderr NOT auto-promoted (noise)
   - LLM extractor (parse ```html``` from assistant message) deferred
     to PR109+

Also: type rename `storagePath` → `relativePath` to enforce contract
in the type system; full reason union widened on both read helpers.

doc total: 1416 lines.
jackwener added a commit that referenced this pull request Jun 21, 2026
Earlier §9.1.5 #4 promised that iframe `<a href>` clicks would route
through PR96 `setWindowOpenHandler` → `shell.openExternal`. That was
documentation incorrect — per @kenji's review: without `allow-popups`
in the sandbox attribute, iframe link clicks don't bubble up to the
parent renderer's window-open handler. They silently fail or get
blocked by the sandbox.

Replaces #4 with the right MVP shape:
- iframe sandbox blocks ALL navigation by default — keep that as the
  contract
- preview top status bar shows "此预览中已禁用外部链接 · {N} 个链接"
  (count of <a href> in srcdoc), so users aren't confused why
  clicks do nothing
- Users who need to follow links use "在 Finder 中打开" from the
  artifact toolbar and open the source file in their browser

Future enhancements (NOT PR108b):
- HTML transform: rewrite <a href> → <button data-maka-link> + inline
  postMessage script, main renderer is the only shell.openExternal
  gateway
- Or: postMessage allowlist for iframe ↔ main renderer

Safety-first MVP: HTML can be VIEWED, not NAVIGATED. Cheap UX
sacrifice, clear trust boundary.
jackwener added a commit that referenced this pull request Jun 21, 2026
Consumes the turn-level contract from PR109c. Implements two of @kenji's
remaining PR109d review gates:

**(d) Failed turn AlertOctagon banner** (@kenji gate #3)
TurnView renders a destructive-tone banner above the assistant body
when `turn.status === 'failed'`. Distinct from PR58 tool-error banner
because it's turn-scoped (the whole turn failed, not just a tool).

Copy is generalized Chinese phrase translated from `turn.errorClass`
via new pure helper `describeTurnErrorClass()` in
`session-status-presentation.ts`:

  timeout                → 请求超时
  auth / 401 / 403       → 鉴权失败
  rate_limit / rate_*    → 触发模型速率限制
  network / fetch / econn → 网络错误
  provider_unavailable / 5xx → 模型服务暂不可用
  tool_failed            → 工具调用失败
  permission_required    → 等待权限确认
  (unknown / empty)      → 未知错误

10 node:test cases lock the mapping invariant: never leak raw enum
identifier, always Chinese, case-insensitive, fallback to "未知错误".

**(e) Lineage badges** (@kenji gate #4)
- Forward badges sit at the top of the new turn:
  "重试自 turn ${shortId}" / "重新生成自 turn ${shortId}".
  Source: `turn.retriedFromTurnId` / `turn.regeneratedFromTurnId` on
  the turn record itself.
- Reverse badges sit at the bottom (above the footer actions):
  "已重试 → turn ${shortId}" / "已重新生成 → turn ${shortId}".
  Source: derived UI-side via existing `deriveTurnLineageMap()` —
  NOT persisted in storage (@kenji review on PR109c kept this side
  immutable).
- Both directions share the same TurnLineageBadge type; UI
  distinguishes via `data-direction="forward"|"reverse"` for tone
  (forward = info-tinted, reverse = success-tinted).
- Click handler scrolls the target turn into view via
  `data-turn-id` attribute querySelector (smooth, block center).

Files:
- `packages/ui/src/components.tsx`:
  - `TurnView` gains `failedReasonLabel` + `lineageBadges` +
    `onLineageBadgeClick` props
  - New `TurnLineageBadge` exported interface
  - Renders failed banner + forward/reverse badge rows
  - `ChatView` gains `turnFailedReasonLabels` +
    `turnLineageBadgesByTurn` + `onLineageBadgeClick` props
- `apps/desktop/src/renderer/main.tsx`:
  - Consolidates `turnFooterActionsByTurn` + `turnFailedReasonLabels`
    + `turnLineageBadgesByTurn` into a single useMemo (one pass over
    materialized turns)
  - `handleLineageBadgeClick()` scrolls target turn via querySelector
- `apps/desktop/src/renderer/session-status-presentation.ts` —
  new `describeTurnErrorClass()` helper
- `apps/desktop/src/renderer/styles.css` — `.maka-turn-failed-banner`
  (destructive), `.maka-turn-lineage-row` + `.maka-turn-lineage-badge`
  (info-tinted forward, brand-deep-tinted reverse)

Per @kenji review (PR109e thread): lineage badges only do scroll/link
— no IPC, no pending mask needed. If a future "retry from badge"
shortcut is added, it MUST reuse the same `pendingTurnActions`
channel from PR109d.

Tests: 234 → 243 desktop passing (+9 describeTurnErrorClass).
check-a11y 0 violations.

Deferred to PR109f:
- Branched session banner ("分自 ${parentSessionName}" + aborted
  starting-point copy "从中断前分支")
- `turn-control-history` fixture + smoke Path 15
jackwener added a commit that referenced this pull request Jun 24, 2026
…scape hatch (#214)

Kenji aesthetic-audit reminder 9 (msg `bacde601` 2026-06-24,
findings #2 + #3): two chrome design-system holes still leaking
gradients / non-token blur into the app.

## #2 — darwin sidebar footer gradient

`html[data-os="darwin"] .maka-session-panel-footer` was setting:
`background: linear-gradient(180deg, transparent 0%, oklch(.../0.30) 100%)`

The panel above already has a 50% glass base + 8px backdrop blur,
so the footer doesn't need any additional material — but the
180deg fade IS the same "上面灰下面白" pattern WAWQAQ called out
twice before in chat sweep (`1e693dee` / `5d3b10e5`). Replaced
with flat `oklch(...) / 0.15` + hairline border-top.

## #3 — `[data-gradual-blur-layer]` block

A 56-line block that let any element opt into:
- `linear-gradient(to bottom/top, var(--background) → transparent)`
- `backdrop-filter: blur(6px)`
- raw `z-index: 5`

just by wearing the `data-gradual-blur-layer` attribute. Kenji
called it a "blur escape hatch" — a future surface could re-
introduce blur and a non-token z-index without going through
any review of the design system.

`grep -rn data-gradual-blur-layer --include=*.tsx` confirms zero
JSX consumers — the CSS is dead. Removing the entire block kills
the escape hatch + retires one of the four raw z-index numbers
kenji flagged in finding #4.

`notes/reference-atlas.md` references stay — those describe what
reference upstream does, not what we ship.

## Diff

1 file changed, 18 insertions(+), 60 deletions(-) net delete.
No JSX touched. No tests touched (no contract pinned this attr).
Footer change is darwin-only so non-darwin platforms unaffected.

Far from PR #202 styles.css edit area (onboarding ~1500-1700 +
cubic-bezier sweep on transition rules). PR #210 touches main.tsx
debounce, not styles.css. No merge conflicts.
jackwener added a commit that referenced this pull request Jun 24, 2026
Kenji aesthetic-audit reminder 9 (msg `bacde601` 2026-06-24,
finding #4): four bare integer z-index sites in styles.css
(line 407 z-4, 3858 z-2, 7193 z-35, 14463 z-5) sidestep the
`--z-*` token scale defined in maka-tokens.css. PR #214 already
took out the gradual-blur-layer site (z-5). This PR closes the
remaining three.

## Changes

1. `maka-tokens.css`: add two new tokens preserving the previous
   raw values exactly:
   - `--z-panel-action: 4`
   - `--z-settings-fullpage: 35`

2. `styles.css` line 407 (`.maka-workspace-top-actions`):
   `z-index: 4` → `var(--z-panel-action)`. Identical stacking.

3. `styles.css` line 7193 (`.settingsModal.settingsPage`):
   `z-index: 35` → `var(--z-settings-fullpage)`. 35 sits between
   `--z-titlebar` (40) and `--z-sticky` (20) so the OS titlebar
   still owns top of the stack. Preserved visual behavior exactly.

4. `styles.css` line 3858 (`.maka-skill-featured-art span:nth-child(2)`):
   bare `z-index: 2` kept. This is local stacking among 3 decorative
   sticker spans inside a fixed-size container — they never escape
   the parent and don't need a global token. Added explicit
   allowlist comment.

5. New `apps/desktop/src/main/__tests__/z-index-contract.test.ts`
   enforces:
   - every bare `z-index: <integer>;` must be tokenized or
     allowlisted (with a documented reason)
   - allowlist entries must be present in styles.css (no stale
     entries)
   - the 10 semantic `--z-*` tokens stay defined in maka-tokens.css

Future PRs that re-introduce a bare z-index will fail this test
unless they explicitly bump the allowlist.

## Diff

3 files: maka-tokens.css +7, styles.css +17/-2, new test +~110 lines.
No visual change. Far from PR #202 (transition sweep), PR #210
(main.tsx debounce), PR #214 (footer/gradual-blur). No conflict.

## Verification

Disk still ~100%, couldn't run `pnpm install && pnpm test`.
Contract test is greenfield — first run will validate the 4 sites
in this same PR.
jackwener added a commit that referenced this pull request Jun 24, 2026
…ji audit #3+#4) (#219)

* test(contract): lock drawer.tsx + tabs.tsx motion escape hatches (kenji audit #3 + #4)

Sibling of PR-FE-BUG-HUNT-12 (which locked ui.tsx). Closes kenji's
audit reminder 4-6 findings #3 + #4 (msg `6cc0e04d` 2026-06-24):

## drawer.tsx escape hatches (5)

- `cubic-bezier(0.32,0.72,0,1)` ×2 — iOS-style settle curve, raw on
  backdrop + popup. Should move to `--ease-drawer` token.
- `duration-450` ×2 — drawer settle duration. Doesn't match any
  current `--duration-*` token. Should tokenize.
- `transition-[transform,box-shadow,height,background-color]` ×1 —
  animating `height` is layout-trigger. Intentional because drawer
  snap points (peek / half / full) drive variable height and
  `transform: scaleY` would distort children. Layout-property
  transition is acknowledged here.
- `backdrop-blur-sm` ×1 — same finding as kenji #6.
- `z-50` ×1 — same convention as ui.tsx; pending tokenization.

## tabs.tsx escape hatches (3)

- `transition-[width,translate]` ×1 — animating `width` is layout-
  trigger. Cleaner refactor is `translate + scaleX` with measured
  base width, but that needs measurement infrastructure not in
  place. Layout transition acknowledged.
- `duration-200` ×1 — matches `--duration-base` (200ms) by value
  but uses bare Tailwind utility.
- `ease-in-out` ×1 — generic Tailwind easing, not project's
  `--ease-out-strong`.

## Approach

Same as PR-FE-BUG-HUNT-12 (ui.tsx contract): pin EXACT count of
each allowlisted pattern. Adding new sites OR removing stale
allowlist entries both fail. Plus a "no other layout transitions"
sweep that verifies no NEW `transition-[<bracketed-list>]` showed
up beyond the allowlisted ones.

Not touching source — touches primitive wrappers; each tokenization
needs kenji / WAWQAQ review per primitive. Contract locks perimeter.

## Diff

1 new file, ~140 lines test. No source change, no visual change.
No overlap with PR-FE-BUG-HUNT-12 (different file scope) or any
other open PR.

## Verification

Disk still ~100%, couldn't run tests. Greenfield contract is
self-validating — first run confirms counts match current file
state. The allowlist counts were derived from grep + manual read.

* test(self-review): fix wrong occurrence counts in primitives contract

Verified counts via `grep -o ... | wc -l` before pushing PR #219 to
review and caught my own miscounts:

- drawer.tsx `z-50`: 1 → actual 3 (gesture wrapper, backdrop scrim,
  cushion overlay all stack at z-50)
- drawer.tsx had TWO additional `transition-[<bracketed>]` patterns
  I missed: `transition-[transform,box-shadow,height,background-color,
  margin,padding]` (drawer popup bottom-edge — worse layout trigger
  than the base, adds margin+padding) and `transition-[background-
  color,box-shadow]` (drawer-internal switch handle — paint-only,
  safe)
- tabs.tsx had ONE additional `transition-[color,background-color,
  box-shadow]` pattern (tabs trigger paint transition — paint-only,
  safe)

All five additions added to the allowlist with explicit reason. The
`no unexpected transition-[<bracketed>]` sweep rewritten to subtract
the full allowlist from the found set (set-difference) instead of
naively iterating, so it doesn't false-fail when there are multiple
paint-only patterns.

Pre-push count verification is now baked into the test contract
itself — if a future maintainer changes a count without re-running,
the test will fail loudly.
jackwener added a commit that referenced this pull request Jun 25, 2026
Per @kenji audit `26a221be` against #237 main `4e0247f6`:

**#1 — bottom 「想先看看效果?」 hero buttons left-aligned.**
PR #236 right-aligned the top 「打开每日回顾」 button via
`.settingsFeatureStatusHeroActions` but the bottom 「生成每日回顾 /
生成深度分析」 row still used inline `style={{ display: 'flex', gap:
8, marginTop: 8 }}`, so it stayed glued to the paragraph margin.
Route the bottom row through the same class. Add `gap: 8px` to the
class so multi-button clusters keep proper spacing.

**#2 — 执行时间 disabled when the master switch is off.**
The disable condition was `formDisabled || savingKey === 'executeTime'
|| !(effectiveConfig?.enabled ?? false)`. That last clause blocked the
common UX of "pick a time first, then turn it on". Drop it — the time
value is harmless to edit while disabled (it doesn't fire until the
switch flips), and the disabled state was misread as "broken UI".

**#3 — 分析模型 default-row label still verbose.**
WAWQAQ's directive: "所有模型选择只用模型名". PR #236 went from
`使用对话默认模型(Codex OAuth · email · gpt-5.5)` to
`对话默认(gpt-5.5)` — better, but the model id parenthetical still
mixes "selectable model" with "follow chat default". Per @kenji's
"don't conflate the two semantics", the default row becomes plain
`跟随对话默认`; the explicit options carry the model id.

**#4 — duplicate model ids across connections looked unselectable.**
When two enabled connections both expose `gpt-5.5`, the flat
`model`-only labels read as two identical entries. Detect the collision
at build time and append `· ${connection.name}` ONLY to the colliding
entries; unique model ids stay terse per WAWQAQ's directive.

Deferred (with @kenji concurrence):
- **#5** (`data-control-width` semantic API replacing CSS-selector width
  patches) — pure refactor, no end-result change. WAWQAQ explicitly
  said 「只看最后的结果」.
- **#6** (collapse Daily Review hero card pile into a single grouped
  settings surface) — bigger visual restructure judgment call; needs
  WAWQAQ sign-off before swinging at the whole page layout.
jackwener added a commit that referenced this pull request Jun 25, 2026
@kenji audit thread `#my-ai:4821a792` msg `e4cfbfb0` vs main `71d88454`.
WAWQAQ msg `782a1663` + `继续`: bundle these, don't split.

**#1 — Memory page top control rows had no outer card surface.**
The three control rows (本地 MEMORY.md / 模型上下文可读取 / 项目指令
文件) were direct children of `.settingsStructuredPage`, which is
transparent. `.settingsFormRow` only carries a hairline bottom border,
so users saw three floating rows without the grouped-card visual every
other settings surface uses. Wrap them in `.settingsRows` so they
inherit the 12px outer border + radius + overflow:hidden treatment.

**#2 — Plan Reminder delivery picker showed text-only IM channels.**
Settings → 机器人对话 switched to real brand logos (Iconify simple-icons)
in #BOT-SETTINGS-UI-0, but the chat-side Plan Reminder delivery flow's
`平台` select was still rendering plain Chinese labels. Same channels
should read the same everywhere.
- Move `BOT_BRAND` to `packages/ui/src/bot-brand.ts` so both surfaces
  share a single brand-metadata source instead of one duplicating the
  other.
- Extend `PlanReminderSelect` to accept a third tuple slot (optional
  `ReactNode` icon); `PlanReminderPanel` now passes an `<IconifyIcon
  icon={brand.iconifyId}>` per provider with the existing offline
  glyph fallback.
- Update `SettingsModal.tsx` to import `BOT_BRAND` from `@maka/ui`
  instead of declaring a local copy.

**#5 — Two inline `style={{ marginTop }}` spacing patches.**
- `Alert variant="error" style={{ marginTop: 12 }}` → `className=
  "settingsSurfaceAlert"`.
- `<div className="settingsActionRow" style={{ marginTop: 8 }}>` →
  `className="settingsActionRow settingsNoticeAction"`.
The new classes pin the spacing decision in CSS where the design
tokens live, instead of inline JSX.

Deferred per kenji's own scope note + WAWQAQ 「只看最后的结果」:
- **#3** (`RadioCard` / `ChoiceCard` primitive for theme/palette picker)
  — pure design-system refactor, no visible delta on Settings → 外观.
- **#4** (`SettingsSelect` uplift to rich-option contract) — bigger
  cross-cutting refactor; current fix covers the immediate model-name
  and disambiguation issues kenji raised in round 1.
- **#6** (layout-property transitions in sidebar/tabs/accordion) —
  kenji explicitly flagged these as visual-smoke-gated; will land
  alongside the smoke config in a separate PR.
jackwener added a commit that referenced this pull request Jun 25, 2026
… dvh (#250)

@kenji audit msg `232aec0f` (`#my-ai:c28a6293`). Round 3 against
`af66ebb7` post-#249.

**#2 — Plan Reminder select collapsed state was text-only.**
PR #247 added a brand icon to each `<SelectItem>`, but `<SelectValue
/>` defaulted to rendering just the label string — open dropdown
showed a logo, collapsed trigger showed plain text. Build a
`value → { label, icon }` lookup inside `PlanReminderSelect` and pass
a function-child to `SelectValue` so the picked state renders the same
icon + label row the dropdown items render.

**#6 — `100vh` residue in renderer CSS.**
Four sites still on `100vh`:
- `maka-tokens.css:760` `.maka-shell` `height`
- `maka-tokens.css:1941` `.maka-modal` `max-height: calc(100vh - 80px)`
- `styles.css:5028` `.maka-help-modal` `max-height: calc(100vh - 96px)`
- `styles.css:13121` `.maka-onboarding-stack` `min-height: calc(100vh - 84px)`

Replace each with `100dvh` so Electron browser frames with dynamic
chrome (or a future mobile-style viewport) don't get half a viewport
of height calculation drift. Other renderer surfaces already use
`100dvh`; this brings the legacy four into line.

Deferred per kenji's own scope:
- **#1** feishu/dingtalk official-brand-kit sourcing — separate PR.
- **#3 / #4** SettingsSelect / Segmented / ChoiceCard primitive
  unification — design-system refactor; no end-result delta on its
  own.
- **#5** layout-property transitions in sidebar/tabs/accordion —
  kenji explicitly gated on visual smoke harness.
anaconda110 added a commit to anaconda110/maka-agent that referenced this pull request Aug 3, 2026
…NEW_ARCHITECTURE_ENABLED (P0 apache#4)

MainApplication.kt unconditionally called DefaultNewArchitectureEntryPoint.load()
in its companion init, but build.gradle sets react { newArchEnabled = false } and
gradle.properties sets newArchEnabled=false. Calling load() with the new
architecture disabled at build time raises 'Unable to load script. Make sure
you're running Metro' or crashes because no native codegen code was compiled.

Gate the call behind BuildConfig.IS_NEW_ARCHITECTURE_ENABLED to mirror the
RN 0.79 official template, so load() is a no-op when newArchEnabled=false.
anaconda110 added a commit to anaconda110/maka-agent that referenced this pull request Aug 3, 2026
… (P1 apache#5)

The previous MainApplication.kt diverged from the RN 0.79.7 official template
(@react-native-community/template@0.79.7) in ways that risk compile/runtime
failure under the bridgeless ReactHost path:

- reactNativeHost was a plain ReactNativeHost anonymous object. The
  getDefaultReactHost(context, reactNativeHost) overload (DefaultReactHost.kt:287)
  requires a DefaultReactNativeHost (it does `require(reactNativeHost is
  DefaultReactNativeHost)`), so this would throw at runtime. Switched to
  DefaultReactNativeHost, overriding isNewArchEnabled/isHermesEnabled from
  BuildConfig so the host reports the correct engine/arch flags.
- getReactHost() was declared as `override fun getReactHost(): ReactHost`,
  but ReactApplication#reactHost is a property (`val reactHost: ReactHost?`).
  Changed to `override val reactHost: ReactHost` (Kotlin property form), matching
  the template and the interface.
- MainApplication did not implement ReactApplication. Added the interface so
  the RN gradle plugin / tooling can locate the host via the standard contract.
- SoLoader.init used the legacy 2-arg SoLoader.init(this, false). Switched to the
  template's SoLoader.init(this, OpenSourceMergedSoMapping), which is the
  0.79.7 public API for so-merging JNI_OnLoad registration.
- DefaultNewArchitectureEntryPoint.load() is now imported statically and called
  as load(), matching the template; the IS_NEW_ARCHITECTURE_ENABLED gate is
  preserved (P0 apache#4).

Aligns with .hive/android-code-review.md P1 apache#5.
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