From 38d76216a49bcc0bc89b4cbd33f011dfce69dc91 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 01:11:21 +0800 Subject: [PATCH 01/18] docs(ui): add architecture README for @maka/ui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Target-oriented README for the shared UI package: four export surfaces (primitives / ui.tsx / top-level features / components.tsx), the off-barrel convention, the data-slot hook rule with its exceptions, where new code goes, and the ui.tsx→primitives convergence direction. Transitional surfaces are marked with direction + end state, not TODOs. --- packages/ui/README.md | 52 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 packages/ui/README.md diff --git a/packages/ui/README.md b/packages/ui/README.md new file mode 100644 index 0000000000..6c1be22463 --- /dev/null +++ b/packages/ui/README.md @@ -0,0 +1,52 @@ +# @maka/ui + +Shared UI layer for the Maka desktop app: React + Tailwind v4 + shadcn (base-nova) + `@base-ui/react`, bound to Maka's token system. Consumed by `apps/desktop`'s renderer, and only that today. + +This package is the **target carrier of the frontend convergence**: hand-rolled renderer CSS recipes are being retired onto primitives exported here. When in doubt, extend a primitive rather than add CSS at the call site. + +## Layer map + +Four export surfaces, in the order to look: + +| Surface | Role | Status | +|---|---|---| +| `src/primitives/` | One file per primitive (accordion, alert, badge, card, chip, dialog-header, empty, input, input-group, item, kbd, menu, number-field, page-header, scroll-area, section-header, settings-segmented/select/switch, spinner, stat-tile, tabs, textarea, toolbar, tooltip, …). **New primitives go here.** | target layer | +| `src/ui.tsx` | Earlier Base UI wrappers + `buttonVariants` (cva) in one file: Button, Checkbox, Dialog/AlertDialog, Select, Switch, Toggle, Radio, Progress, Separator, Field/Label. | transitional — wrappers migrate into `primitives/` as touched (Badge moved to `primitives/badge.tsx` earlier; Button/Select/etc. still live here) | +| `src/*.tsx` / `src/*.ts` (top-level) | Feature components + pure logic: `chat-view.tsx`, `composer.tsx`, `tool-activity.tsx`, `permission-dialog.tsx`, `search-modal.tsx`, `session-list-panel.tsx`, `skills-panel.tsx`, `plan-reminder-panel.tsx`, `daily-review-panel.tsx`, plus pure helpers (`materialize.ts`, `redact.ts`, `smooth-stream.ts`, `stream-fade.ts`, `live-turn-projection.ts`, …). | stable | +| `src/components.tsx` | Re-export barrel for the feature components above (ChatView, Composer, ToolActivity, PermissionDialog, SearchModal, SessionListPanel, RelativeTime, …). | stable | + +`src/index.ts` is the package barrel. It follows an **off-barrel convention**: internal styling tables and single-consumer dots (`markerVariants`, `streamVariants`, `toolVariants`, `LiveIndicator`) are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a second consumer or a cross-package consumer (the promotion condition is documented inline in `index.ts`). Don't add to the barrel speculatively. + +## `data-slot` hooks + +Most primitives expose a stable `data-slot=""` attribute so renderer CSS can target a slot (e.g. `[data-slot="dialog-header"]`) rather than a drifting class. Exceptions without one: `choice-card`, `spinner`, `scroll-area`, `settings-segmented` — their styling lives on the consumer's class or an underlying Base UI component, so a `[data-slot="..."]` selector won't match. New primitives should still expose a `data-slot`. + +## Consuming + +```ts +import { Button, ChatView, Composer, Badge, Chip, PageHeader, useToast } from '@maka/ui'; +import { ProviderLogo } from '@maka/ui/icons'; +``` + +Sub-path exports (declared in `package.json` `exports`): `@maka/ui/artifact-preview-registry`, `@maka/ui/assistant-stream`, `@maka/ui/icons`, `@maka/ui/maka-uri`, `@maka/ui/smooth-stream`. + +Renderer CSS may target a primitive via its `data-slot` attribute, never by overriding the primitive's own utility classes. + +## Where new code goes + +- **New primitive** (button-like, dialog-like, form control) → a new file in `src/primitives/`, exposing `data-slot`, re-exported from `index.ts`. +- **New feature component** → top-level `src/.tsx`, re-exported from `src/components.tsx` and `index.ts`. +- **Don't** add a per-surface hand-rolled CSS recipe in the renderer if a primitive can carry it — extend the primitive's API/slots instead. +- **Don't** re-export a single-consumer symbol from the barrel; keep it a relative import until a second consumer appears. + +## Convergence direction (transitional surfaces) + +Acknowledged transitional states — not TODOs; track actual work in issues/PRs. + +- `ui.tsx` ↔ `primitives/`: end state is one primitive layer in `primitives/`. Wrappers in `ui.tsx` move over when touched (Badge is the precedent). `buttonVariants` has external consumers, so its move is a coordinated rename, not a silent one. + +## Contracts & guardrails + +Component contracts (5-state, ARIA, keyboard, tone/token per component), the token registry, and anti-patterns live in `docs/design-system.md`. Where that doc disagrees with the code or the contract tests (`*-converge-contract.test.ts`, `state-token-governance-*`, `tab-spec-*`, …), the code and the tests are the source of truth. + +Stories (`stories/`) and unit tests (`src/__tests__/`) exist per primitive/feature. Build/test entry points are in the root `AGENTS.md`. \ No newline at end of file From 54e2f0def90c0959852f393085d833c48c52815c Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 01:11:21 +0800 Subject: [PATCH 02/18] docs(desktop): add architecture READMEs for the desktop app and renderer Target-oriented READMEs for the Electron app shell and its renderer: the main/preload/renderer split, the main naming convention, the three-pattern IPC contract and the registerIpc() registration step, the actual main.ts startup order, the renderer AppShell + app-shell-- split, the styles/tokens layout (with the --foreground-N wash-vs-text split), and the primitive-first authoring rule. Direction + end state only, no TODOs. --- apps/desktop/README.md | 52 ++++++++++++++++++++++++ apps/desktop/src/renderer/README.md | 62 +++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 apps/desktop/README.md create mode 100644 apps/desktop/src/renderer/README.md diff --git a/apps/desktop/README.md b/apps/desktop/README.md new file mode 100644 index 0000000000..5ffee5a8ff --- /dev/null +++ b/apps/desktop/README.md @@ -0,0 +1,52 @@ +# @maka/desktop + +The Electron desktop app: `main` (Node/Electron main process) + `preload` (context bridge) + `renderer` (React UI). This file covers the three-layer split and the IPC contract — the parts root `AGENTS.md` doesn't repeat. For build/test commands and the test-layer selection guide, see root `AGENTS.md`; for the renderer interior, see `src/renderer/README.md`. + +## Three layers + +| Layer | Path | Role | +|---|---|---| +| main | `src/main/` | Node/Electron main process. Owns window lifecycle, credentials, attachments, permissions, IPC handlers, and the bridge to `@maka/runtime` + `@maka/storage`. | +| preload | `src/preload/preload.ts` (single file) | `contextBridge.exposeInMainWorld('maka', …)` — the only surface the renderer may call to reach Node/Electron. No Node API is directly exposed. | +| renderer | `src/renderer/` | React UI body. See `src/renderer/README.md`. | + +## main process layout + +`src/main/` is flat with a naming convention: + +| Suffix | Role | Examples | +|---|---|---| +| `*-ipc-main.ts` | Exports a `register*Ipc(...)` that wires `ipcMain.handle` / `ipcMain.on` for one IPC domain | `connections-ipc-main`, `config-ipc-main`, `daily-review-ipc-main`, `memory-ipc-main`, `notifications-ipc-main`, `plan-reminders-ipc-main`, `subscription-ipc-main`, `usage-ipc-main`, `web-search-ipc-main`, `workspace-resources-ipc-main` | +| `*-main.ts` / `*-service.ts` | A service owned by main (no `ipcMain` calls of its own) | `daily-review-main`, `bot-incoming-main`, `plan-reminders-main`, `system-prompt-main`, `oauth-model-connections-main`, `local-memory-service` | +| `*-guard.ts` | Validation / security boundary | `external-link-guard`, `open-path-guard`, `permission-response-guard` | +| (other) | Window, state, platform wiring | `main.ts` (entry), `main-window`, `window-state`, `window-reveal`, `theme-source`, `credential-store`, `capability-snapshot`, `skills`, `attachment-*`, `build-info` | + +Sub-folders: `browser/` (embedded browser view + its `browser-ipc-main`), `oauth/`, `search/` (thread search), `web-search/`, `types/`. + +`main.ts` startup order: the stores and the runtime/controller are created at module load (top-level `create*Store` / runtime wiring); `registerIpc()` is then called at top level, **before** `app.whenReady()`; the main window is created last, inside the `app.whenReady()` callback. The renderer fires its onboarding IPC at first mount, so the handlers must be registered before the window exists. + +## IPC contract + +Three patterns, all rooted in preload's `maka` namespace: + +- **Request/response** — `ipcRenderer.invoke(':', …args)` in preload ↔ `ipcMain.handle(':', …)` in a `*-ipc-main.ts`. Channel names are `:` (`sessions:list`, `connections:test`, `settings:get`, `daily-review:day`, `web-search:query`, …). +- **Main→renderer push** — main calls `webContents.send('')`; preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn. Event channels: `sessions:changed`, `sessions:event:`, `connections:event`, `plans:changed`, `plans:due`, `artifacts:changed`, `gateway:statusChanged`, `settings:externalChanged`, `window:openSettings`, `browser:state`, `browser:live`, `settings:bots:statusChanged`. +- **Renderer→main fire-and-forget** — `ipcRenderer.send(':', …)` in preload ↔ `ipcMain.on(':', …)` in a `*-ipc-main.ts`. Used when no response is needed (e.g. `browser:active-session`, `browser:setViewport`). + +Adding a new IPC surface: write the `*-ipc-main.ts` exporting a `register*Ipc(...)`, import it in `main.ts`, and call it inside `registerIpc()`; add the matching method to the `maka` namespace in `preload.ts`; keep the `:` channel naming. A handler file that isn't registered in `registerIpc()` compiles but never mounts. + +## Data flow + +``` +renderer (React) + └─ window.maka..(…) // typed surface, see preload.ts + └─ ipcRenderer.invoke / send / on + └─ main: ipcMain.handle / ipcMain.on / webContents.send + └─ @maka/runtime (agent runtime) + @maka/storage (JSONL persistence) +``` + +The renderer never imports `@maka/runtime` or `@maka/storage` at runtime — all Node-side access goes through the preload `maka` bridge. The renderer only pulls `import type` from them for a few shared types (e.g. `BotStatus`, `ConfigCategory`); types shared across the IPC boundary come from `@maka/core`. + +## Convergence note + +The renderer side carries the frontend convergence debt (hand-rolled CSS, primitive overrides); see `src/renderer/README.md`. The main process itself is not part of that convergence — its boundaries (IPC channel names, the preload bridge, the `*-guard.ts` files) are stable contract seams. \ No newline at end of file diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md new file mode 100644 index 0000000000..22f0ef67cd --- /dev/null +++ b/apps/desktop/src/renderer/README.md @@ -0,0 +1,62 @@ +# Renderer (`apps/desktop/src/renderer`) + +The Electron renderer process: the React UI body of the Maka desktop app. React + Vite + Tailwind v4, consuming `@maka/ui` primitives. This is the **frontend governance hot zone** — most of the hand-rolled CSS and primitive-override transitional debt lives here. + +For the main/preload/renderer split and the IPC contract, see `apps/desktop/README.md`. This file covers the renderer interior. + +## Entry + +`main.tsx` → `app.tsx` → `AppShell` (`app-shell.tsx`). `index.html` is the Vite HTML shell. `main.tsx` prefetches the onboarding snapshot before mounting React so the first commit paints the real surface (no loading flash); `app.tsx` wraps `AppShell` in `ToastProvider` + `ErrorBoundary`. + +`styles.css` is the **only** style entry: it `@import`s Tailwind, fonts, `maka-tokens.css`, `reference-shell.css`, and every `styles/*.css`. Per CSS governance, `styles.css` may only contain `@import` / `@source` / `@theme` / top-level orchestration — real selector rules go in `styles/*.css`. + +## AppShell + the action modules + +`app-shell.tsx` is the shell component: owns session state, wires the `@maka/ui` panels (SessionListPanel, ChatView, Composer, ToolActivity), and mounts lazy panels (ArtifactPane, BrowserPanel). It is supported by a set of `app-shell--.ts(x)` modules, each a narrow slice of shell logic split by concern: + +| Prefix | Concern | +|---|---| +| `app-shell-session-*` | session list / row / settings / UI-state / events | +| `app-shell-chat-actions`, `app-shell-turn-*` | send / stop / regenerate, turn view-model | +| `app-shell-plan-*`, `app-shell-daily-review-*`, `app-shell-skill-*` | module panes | +| `app-shell-project-*` | project / workspace selection | +| `app-shell-command-actions`, `app-shell-quick-chat-actions` | command palette, quick chat | +| `app-shell-effects` | the effect / `effectEvent` wiring | +| `app-shell-overlays`, `app-shell-chrome-actions`, `app-shell-layout-actions` | overlays, window chrome, layout | +| `app-shell-visual-smoke`, `app-shell-pending-attachments`, `app-shell-copy` | fixture capture, attachments, clipboard | + +Naming convention for a new slice: `app-shell--.ts`. Keep a slice to one concern; if it grows, split along the same `app-shell--` seam. + +`settings/` holds the settings pages and the `SettingsModal` shell — one page per section (about, account, appearance, bot-chat, daily-review, data, general, health, memory, open-gateway, permission, usage, voice, web-search), plus the `provider-*` files and the shared `settings-rows` / `settings-skeleton` / `settings-surface` helpers. + +## Styles & tokens + +| File | Role | +|---|---| +| `maka-tokens.css` | Single source of CSS tokens (color / shadow / typography / radius / spacing / motion / z / layout) **and** a few component-recipe fallbacks at the tail. Transitional: tokens and recipes coexist in one file. | +| `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (see its header comment). **Transitional** — meant to be folded back into the token/style system and removed. | +| `styles/*.css` | Per-surface hand-written recipes (`chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`, …). | + +Token authoring rule: custom CSS variables go in `maka-tokens.css`; only component-local vars are excepted and must carry `/* local: ... */`. No new hardcoded color / radius / z-index. + +Note the `--foreground-N` split: the wash stops (`-2/-3/-5/-8/-10`) are surface fills for backgrounds and borders, **not** text. The 3-tier semantic aliases (`--foreground` / `--foreground-secondary` / `--muted-foreground`) are the text-color vocabulary. They are separate concerns — don't collapse the wash stops into the text aliases. + +## New code: primitive first, CSS last + +1. Reach for a `@maka/ui` primitive or a Tailwind utility class first. +2. Only if no primitive carries it, write CSS in the matching `styles/.css`, following `docs/frontend-css-governance.md` (layer rules, the unlayered override list, the `!important` audit, the dead-CSS allowlist). +3. Don't add a token without registering it in `maka-tokens.css`. + +## Convergence direction (transitional surfaces) + +Acknowledged transitional states — not TODOs; track work in issues/PRs. + +- Hand-written `styles/*.css` recipes + overrides on `@maka/ui` primitives: end state is structure carried by primitives, renderer CSS left only with layout primitives can't cover. Per-recipe retirement is mapped in `notes/ui-convergence-map-2026-07-09.md` (Chip / Item / PageHeader / StatTile / SectionHeader already converged). +- `reference-shell.css`: end state is folded into the token/style system and the file removed. +- `maka-tokens.css` mixing tokens + recipes: end state is tokens-only here, recipes living on primitives / `styles/`. + +## Contracts & guardrails + +- CSS cascade / layer / `!important` / dead-CSS / token rules: `docs/frontend-css-governance.md`. The dead-CSS check runs from the repo root via `check:release` (`scripts/check-dead-css.mjs --check`); its baseline is `scripts/check-dead-css-baseline.json`. +- Component 5-state / ARIA / token / copy contracts: `docs/design-system.md`. +- Where either doc disagrees with the code or the contract tests, the code and the tests are the source of truth. Key guardrail tests live in `apps/desktop/src/main/__tests__/` (renderer-style-layer-cascade-contract, renderer-important-audit-contract, typography / spacing / radius / state-token / foreground-tier governance, dead-css baseline). Build/test entry points are in the root `AGENTS.md`. \ No newline at end of file From 019cb03d7d4f9475e1ac8a15fe0bb2703a1c0fef Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 01:30:08 +0800 Subject: [PATCH 03/18] docs(ui): add architecture README for @maka/ui --- packages/ui/README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/ui/README.md b/packages/ui/README.md index 6c1be22463..b61be06809 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -10,12 +10,12 @@ Four export surfaces, in the order to look: | Surface | Role | Status | |---|---|---| -| `src/primitives/` | One file per primitive (accordion, alert, badge, card, chip, dialog-header, empty, input, input-group, item, kbd, menu, number-field, page-header, scroll-area, section-header, settings-segmented/select/switch, spinner, stat-tile, tabs, textarea, toolbar, tooltip, …). **New primitives go here.** | target layer | -| `src/ui.tsx` | Earlier Base UI wrappers + `buttonVariants` (cva) in one file: Button, Checkbox, Dialog/AlertDialog, Select, Switch, Toggle, Radio, Progress, Separator, Field/Label. | transitional — wrappers migrate into `primitives/` as touched (Badge moved to `primitives/badge.tsx` earlier; Button/Select/etc. still live here) | -| `src/*.tsx` / `src/*.ts` (top-level) | Feature components + pure logic: `chat-view.tsx`, `composer.tsx`, `tool-activity.tsx`, `permission-dialog.tsx`, `search-modal.tsx`, `session-list-panel.tsx`, `skills-panel.tsx`, `plan-reminder-panel.tsx`, `daily-review-panel.tsx`, plus pure helpers (`materialize.ts`, `redact.ts`, `smooth-stream.ts`, `stream-fade.ts`, `live-turn-projection.ts`, …). | stable | -| `src/components.tsx` | Re-export barrel for the feature components above (ChatView, Composer, ToolActivity, PermissionDialog, SearchModal, SessionListPanel, RelativeTime, …). | stable | +| `src/primitives/` | One file per primitive (e.g. `accordion`, `badge`, `chip`, `dialog-header`, `input`, `page-header`, `tabs`, `textarea`, `toolbar`, `tooltip`, …). **New primitives go here.** | target layer | +| `src/ui.tsx` | Earlier Base UI wrappers + `buttonVariants` (cva) in one file (Button, Checkbox, Dialog/AlertDialog, Select, Switch, Toggle, Radio, Progress, Separator, Field/Label). | transitional — wrappers migrate into `primitives/` as touched (Badge moved to `primitives/badge.tsx` earlier; Button/Select/etc. still live here) | +| `src/*.tsx` / `src/*.ts` (top-level) | Feature components + pure logic (e.g. `chat-view.tsx`, `composer.tsx`, `permission-dialog.tsx`, `session-list-panel.tsx`, plus pure helpers like `materialize.ts`, `redact.ts`, `smooth-stream.ts`). | stable | +| `src/components.tsx` | Re-export barrel for the feature components above (ChatView, Composer, PermissionDialog, …). | stable | -`src/index.ts` is the package barrel. It follows an **off-barrel convention**: internal styling tables and single-consumer dots (`markerVariants`, `streamVariants`, `toolVariants`, `LiveIndicator`) are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a second consumer or a cross-package consumer (the promotion condition is documented inline in `index.ts`). Don't add to the barrel speculatively. +`src/index.ts` is the package barrel. It follows an **off-barrel convention**: internal styling tables and single-consumer dots (e.g. `markerVariants`, `LiveIndicator`) are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a second consumer or a cross-package consumer (the promotion condition is documented inline in `index.ts`). Don't add to the barrel speculatively. ## `data-slot` hooks @@ -47,6 +47,6 @@ Acknowledged transitional states — not TODOs; track actual work in issues/PRs. ## Contracts & guardrails -Component contracts (5-state, ARIA, keyboard, tone/token per component), the token registry, and anti-patterns live in `docs/design-system.md`. Where that doc disagrees with the code or the contract tests (`*-converge-contract.test.ts`, `state-token-governance-*`, `tab-spec-*`, …), the code and the tests are the source of truth. +Component contracts (5-state, ARIA, keyboard, tone/token per component), the token registry, and anti-patterns live in `docs/design-system.md`. Where that doc disagrees with the code or the contract tests (`*-converge-contract.test.ts`, `state-token-governance-*`, …), the code and the tests are the source of truth. -Stories (`stories/`) and unit tests (`src/__tests__/`) exist per primitive/feature. Build/test entry points are in the root `AGENTS.md`. \ No newline at end of file +Selected primitives and features have stories (`stories/`) and unit tests (`src/__tests__/`); coverage is partial, not exhaustive. Build/test entry points are the npm scripts in the root `package.json` (see the top-level `README.md`). \ No newline at end of file From 7183d49448e9a8d1ca612f86e26007d8c28dbab4 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 01:30:08 +0800 Subject: [PATCH 04/18] docs(desktop): add architecture READMEs for the desktop app and renderer --- apps/desktop/README.md | 22 +++++++++++----------- apps/desktop/src/renderer/README.md | 23 ++++++----------------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 5ffee5a8ff..33e05cdc6f 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -1,6 +1,6 @@ # @maka/desktop -The Electron desktop app: `main` (Node/Electron main process) + `preload` (context bridge) + `renderer` (React UI). This file covers the three-layer split and the IPC contract — the parts root `AGENTS.md` doesn't repeat. For build/test commands and the test-layer selection guide, see root `AGENTS.md`; for the renderer interior, see `src/renderer/README.md`. +The Electron desktop app: `main` (Node/Electron main process) + `preload` (context bridge) + `renderer` (React UI). This file covers the three-layer split and the IPC contract. For build/test commands and the test-layer selection guide, see the top-level `README.md`; for the renderer interior, see `src/renderer/README.md`. ## Three layers @@ -16,24 +16,24 @@ The Electron desktop app: `main` (Node/Electron main process) + `preload` (conte | Suffix | Role | Examples | |---|---|---| -| `*-ipc-main.ts` | Exports a `register*Ipc(...)` that wires `ipcMain.handle` / `ipcMain.on` for one IPC domain | `connections-ipc-main`, `config-ipc-main`, `daily-review-ipc-main`, `memory-ipc-main`, `notifications-ipc-main`, `plan-reminders-ipc-main`, `subscription-ipc-main`, `usage-ipc-main`, `web-search-ipc-main`, `workspace-resources-ipc-main` | -| `*-main.ts` / `*-service.ts` | A service owned by main (no `ipcMain` calls of its own) | `daily-review-main`, `bot-incoming-main`, `plan-reminders-main`, `system-prompt-main`, `oauth-model-connections-main`, `local-memory-service` | +| `*-ipc-main.ts` | Exports a `register*Ipc(...)` that wires `ipcMain.handle` / `ipcMain.on` for one IPC domain | `connections-ipc-main`, `daily-review-ipc-main`, `memory-ipc-main`, `web-search-ipc-main`, `workspace-resources-ipc-main` | +| `*-main.ts` / `*-service.ts` | A service owned by main (no `ipcMain` calls of its own) | `daily-review-main`, `system-prompt-main`, `oauth-model-connections-main`, `local-memory-service` | | `*-guard.ts` | Validation / security boundary | `external-link-guard`, `open-path-guard`, `permission-response-guard` | -| (other) | Window, state, platform wiring | `main.ts` (entry), `main-window`, `window-state`, `window-reveal`, `theme-source`, `credential-store`, `capability-snapshot`, `skills`, `attachment-*`, `build-info` | +| (other) | Window, state, platform wiring | `main.ts` (entry), `main-window`, `window-state`, `theme-source`, `credential-store`, `skills`, `attachment-*` | -Sub-folders: `browser/` (embedded browser view + its `browser-ipc-main`), `oauth/`, `search/` (thread search), `web-search/`, `types/`. +Sub-folders: `browser/` (embedded browser view), `oauth/`, `search/` (thread search), `web-search/`, `types/`. The browser IPC handler itself (`browser-ipc-main.ts`) is flat in `src/main/`, not under `browser/`. `main.ts` startup order: the stores and the runtime/controller are created at module load (top-level `create*Store` / runtime wiring); `registerIpc()` is then called at top level, **before** `app.whenReady()`; the main window is created last, inside the `app.whenReady()` callback. The renderer fires its onboarding IPC at first mount, so the handlers must be registered before the window exists. ## IPC contract -Three patterns, all rooted in preload's `maka` namespace: +Three patterns, all rooted in preload's `maka` namespace. Channel names are `:`. -- **Request/response** — `ipcRenderer.invoke(':', …args)` in preload ↔ `ipcMain.handle(':', …)` in a `*-ipc-main.ts`. Channel names are `:` (`sessions:list`, `connections:test`, `settings:get`, `daily-review:day`, `web-search:query`, …). -- **Main→renderer push** — main calls `webContents.send('')`; preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn. Event channels: `sessions:changed`, `sessions:event:`, `connections:event`, `plans:changed`, `plans:due`, `artifacts:changed`, `gateway:statusChanged`, `settings:externalChanged`, `window:openSettings`, `browser:state`, `browser:live`, `settings:bots:statusChanged`. -- **Renderer→main fire-and-forget** — `ipcRenderer.send(':', …)` in preload ↔ `ipcMain.on(':', …)` in a `*-ipc-main.ts`. Used when no response is needed (e.g. `browser:active-session`, `browser:setViewport`). +- **Request/response** — `ipcRenderer.invoke(':', …args)` in preload ↔ `ipcMain.handle(':', …)`. The handler lives either inline in `main.ts` (e.g. `sessions:list`, `settings:get`) or in a `*-ipc-main.ts` extracted by domain (e.g. `connections-ipc-main`, `daily-review-ipc-main`). Both forms coexist; prefer extracting a new domain to its own `*-ipc-main.ts`. +- **Main→renderer push** — main calls `webContents.send('')`; preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn (e.g. `sessions:changed`, `plans:changed`, `artifacts:changed`, `gateway:statusChanged`). +- **Renderer→main fire-and-forget** — `ipcRenderer.send(':', …)` in preload ↔ `ipcMain.on(':', …)`. Used when no response is needed (e.g. `browser:active-session`, `browser:setViewport`). -Adding a new IPC surface: write the `*-ipc-main.ts` exporting a `register*Ipc(...)`, import it in `main.ts`, and call it inside `registerIpc()`; add the matching method to the `maka` namespace in `preload.ts`; keep the `:` channel naming. A handler file that isn't registered in `registerIpc()` compiles but never mounts. +Adding a new IPC surface: if extracting, write the `*-ipc-main.ts` exporting a `register*Ipc(...)`, import it in `main.ts`, and call it inside `registerIpc()`; add the matching method to the `maka` namespace in `preload.ts`; keep the `:` channel naming. A handler file that isn't registered in `registerIpc()` compiles but never mounts. ## Data flow @@ -45,7 +45,7 @@ renderer (React) └─ @maka/runtime (agent runtime) + @maka/storage (JSONL persistence) ``` -The renderer never imports `@maka/runtime` or `@maka/storage` at runtime — all Node-side access goes through the preload `maka` bridge. The renderer only pulls `import type` from them for a few shared types (e.g. `BotStatus`, `ConfigCategory`); types shared across the IPC boundary come from `@maka/core`. +The renderer never imports `@maka/runtime` or `@maka/storage` at runtime — all Node-side access goes through the preload `maka` bridge. The renderer only pulls `import type` from them for a few shared types. Types shared across the IPC boundary mostly come from `@maka/core`, with some from `@maka/runtime`, `@maka/storage`, and `@maka/ui` (see `preload.ts` imports). ## Convergence note diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 22f0ef67cd..5bf424c6b6 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -12,22 +12,11 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ ## AppShell + the action modules -`app-shell.tsx` is the shell component: owns session state, wires the `@maka/ui` panels (SessionListPanel, ChatView, Composer, ToolActivity), and mounts lazy panels (ArtifactPane, BrowserPanel). It is supported by a set of `app-shell--.ts(x)` modules, each a narrow slice of shell logic split by concern: +`app-shell.tsx` is the shell component: owns session state, wires the `@maka/ui` panels (SessionListPanel, ChatView, Composer — ChatView renders the tool stream via `ToolTrow`), and mounts lazy panels (ArtifactPane, BrowserPanel). It is supported by a set of `app-shell--.ts(x)` modules, each a narrow slice of shell logic split by concern (e.g. `app-shell-session-events.ts`, `app-shell-chat-actions.ts`, `app-shell-plan-actions.ts`, `app-shell-effects.ts`, `app-shell-stop-action.ts`). -| Prefix | Concern | -|---|---| -| `app-shell-session-*` | session list / row / settings / UI-state / events | -| `app-shell-chat-actions`, `app-shell-turn-*` | send / stop / regenerate, turn view-model | -| `app-shell-plan-*`, `app-shell-daily-review-*`, `app-shell-skill-*` | module panes | -| `app-shell-project-*` | project / workspace selection | -| `app-shell-command-actions`, `app-shell-quick-chat-actions` | command palette, quick chat | -| `app-shell-effects` | the effect / `effectEvent` wiring | -| `app-shell-overlays`, `app-shell-chrome-actions`, `app-shell-layout-actions` | overlays, window chrome, layout | -| `app-shell-visual-smoke`, `app-shell-pending-attachments`, `app-shell-copy` | fixture capture, attachments, clipboard | - -Naming convention for a new slice: `app-shell--.ts`. Keep a slice to one concern; if it grows, split along the same `app-shell--` seam. +Naming convention for a new slice: `app-shell--.ts` (or `.tsx` when it returns JSX). Keep a slice to one concern; if it grows, split along the same `app-shell--` seam. -`settings/` holds the settings pages and the `SettingsModal` shell — one page per section (about, account, appearance, bot-chat, daily-review, data, general, health, memory, open-gateway, permission, usage, voice, web-search), plus the `provider-*` files and the shared `settings-rows` / `settings-skeleton` / `settings-surface` helpers. +`settings/` holds the settings pages and the `SettingsModal` shell — one page per `SettingsSection` (defined in `@maka/core`); the models/providers page is `ProvidersPanel`. Plus the `provider-*` files and the shared `settings-rows` / `settings-skeleton` / `settings-surface` helpers. ## Styles & tokens @@ -35,7 +24,7 @@ Naming convention for a new slice: `app-shell--.ts`. Keep a slice |---|---| | `maka-tokens.css` | Single source of CSS tokens (color / shadow / typography / radius / spacing / motion / z / layout) **and** a few component-recipe fallbacks at the tail. Transitional: tokens and recipes coexist in one file. | | `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (see its header comment). **Transitional** — meant to be folded back into the token/style system and removed. | -| `styles/*.css` | Per-surface hand-written recipes (`chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`, …). | +| `styles/*.css` | Per-surface hand-written recipes (e.g. `chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`). | Token authoring rule: custom CSS variables go in `maka-tokens.css`; only component-local vars are excepted and must carry `/* local: ... */`. No new hardcoded color / radius / z-index. @@ -51,7 +40,7 @@ Note the `--foreground-N` split: the wash stops (`-2/-3/-5/-8/-10`) are surface Acknowledged transitional states — not TODOs; track work in issues/PRs. -- Hand-written `styles/*.css` recipes + overrides on `@maka/ui` primitives: end state is structure carried by primitives, renderer CSS left only with layout primitives can't cover. Per-recipe retirement is mapped in `notes/ui-convergence-map-2026-07-09.md` (Chip / Item / PageHeader / StatTile / SectionHeader already converged). +- Hand-written `styles/*.css` recipes + overrides on `@maka/ui` primitives: end state is structure carried by primitives, renderer CSS left only with layout primitives can't cover. Per-recipe retirement is tracked in `notes/ui-convergence-map-2026-07-09.md`. - `reference-shell.css`: end state is folded into the token/style system and the file removed. - `maka-tokens.css` mixing tokens + recipes: end state is tokens-only here, recipes living on primitives / `styles/`. @@ -59,4 +48,4 @@ Acknowledged transitional states — not TODOs; track work in issues/PRs. - CSS cascade / layer / `!important` / dead-CSS / token rules: `docs/frontend-css-governance.md`. The dead-CSS check runs from the repo root via `check:release` (`scripts/check-dead-css.mjs --check`); its baseline is `scripts/check-dead-css-baseline.json`. - Component 5-state / ARIA / token / copy contracts: `docs/design-system.md`. -- Where either doc disagrees with the code or the contract tests, the code and the tests are the source of truth. Key guardrail tests live in `apps/desktop/src/main/__tests__/` (renderer-style-layer-cascade-contract, renderer-important-audit-contract, typography / spacing / radius / state-token / foreground-tier governance, dead-css baseline). Build/test entry points are in the root `AGENTS.md`. \ No newline at end of file +- Where either doc disagrees with the code or the contract tests, the code and the tests are the source of truth. Key guardrail tests live in `apps/desktop/src/main/__tests__/` (style-layer-cascade, important-audit, typography / spacing / radius / state-token / foreground-tier governance). Build/test entry points are the npm scripts in the root `package.json` (see the top-level `README.md`). \ No newline at end of file From 9384a7e52b22db2577cac94c7a13e350ebcffd3f Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 01:51:17 +0800 Subject: [PATCH 05/18] docs(frontend): fix round-3 review findings in READMEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ui: drop the broken @maka/ui/icons ProviderLogo example (it lives in the renderer); note icons re-exports Lucide symbols; use markerVariants as a real off-barrel example instead of the zero-consumer LiveIndicator; clarify 'runtime consumer' (preload imports types). - renderer: describe AppShell slices as app-shell-* one-concern modules (not a strict two-segment rule several existing slices violate); make the reference-shell.css breadcrumb point at the file's own header. - desktop: fix startup order (window created early, background startup concurrent, handlers before renderer entry that prefetches pre-mount); route main→renderer push through safeSendToRenderer (raw webContents.send throws on destroyed windows); add src/global.d.ts to the new-IPC steps. --- apps/desktop/README.md | 8 ++++---- apps/desktop/src/renderer/README.md | 6 ++---- packages/ui/README.md | 7 +++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 33e05cdc6f..fc13ea79c5 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -23,17 +23,17 @@ The Electron desktop app: `main` (Node/Electron main process) + `preload` (conte Sub-folders: `browser/` (embedded browser view), `oauth/`, `search/` (thread search), `web-search/`, `types/`. The browser IPC handler itself (`browser-ipc-main.ts`) is flat in `src/main/`, not under `browser/`. -`main.ts` startup order: the stores and the runtime/controller are created at module load (top-level `create*Store` / runtime wiring); `registerIpc()` is then called at top level, **before** `app.whenReady()`; the main window is created last, inside the `app.whenReady()` callback. The renderer fires its onboarding IPC at first mount, so the handlers must be registered before the window exists. +`main.ts` startup order: stores and the runtime/controller are created synchronously at module load (top-level `create*Store` / runtime wiring); `registerIpc()` runs at top level, **before** `app.whenReady()`; inside `whenReady`, the main window is created as early as possible (preload skeleton shows within milliseconds) and background startup (credential migration, connection bootstrapping, telemetry, bots, gateway, schedulers) runs concurrently without blocking first paint. The real invariant for IPC: handlers must be registered before the renderer entry runs, because `main.tsx` prefetches the onboarding snapshot before mounting React. Background startup mutations are pushed to the renderer via the `sessions:changed` / `connections:event` / `settings:bots:statusChanged` channels, so the UI converges lazily. ## IPC contract Three patterns, all rooted in preload's `maka` namespace. Channel names are `:`. - **Request/response** — `ipcRenderer.invoke(':', …args)` in preload ↔ `ipcMain.handle(':', …)`. The handler lives either inline in `main.ts` (e.g. `sessions:list`, `settings:get`) or in a `*-ipc-main.ts` extracted by domain (e.g. `connections-ipc-main`, `daily-review-ipc-main`). Both forms coexist; prefer extracting a new domain to its own `*-ipc-main.ts`. -- **Main→renderer push** — main calls `webContents.send('')`; preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn (e.g. `sessions:changed`, `plans:changed`, `artifacts:changed`, `gateway:statusChanged`). +- **Main→renderer push** — main sends through the safe-send guard (`safeSendToRenderer` via `mainWindowController.send`), not raw `webContents.send` (which throws when the window/`webContents` is destroyed); preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn (e.g. `sessions:changed`, `plans:changed`, `artifacts:changed`, `gateway:statusChanged`). A contract test enforces routing every main-window send through the guard. - **Renderer→main fire-and-forget** — `ipcRenderer.send(':', …)` in preload ↔ `ipcMain.on(':', …)`. Used when no response is needed (e.g. `browser:active-session`, `browser:setViewport`). -Adding a new IPC surface: if extracting, write the `*-ipc-main.ts` exporting a `register*Ipc(...)`, import it in `main.ts`, and call it inside `registerIpc()`; add the matching method to the `maka` namespace in `preload.ts`; keep the `:` channel naming. A handler file that isn't registered in `registerIpc()` compiles but never mounts. +Adding a new IPC surface: if extracting, write the `*-ipc-main.ts` exporting a `register*Ipc(...)`, import it in `main.ts`, and call it inside `registerIpc()`; add the matching method to the `maka` namespace in `preload.ts`; add the method to the `window.maka` type in `src/global.d.ts` (the renderer's typed bridge — without it, renderer calls get a TS error); keep the `:` channel naming. A handler file that isn't registered in `registerIpc()` compiles but never mounts. ## Data flow @@ -41,7 +41,7 @@ Adding a new IPC surface: if extracting, write the `*-ipc-main.ts` exporting a ` renderer (React) └─ window.maka..(…) // typed surface, see preload.ts └─ ipcRenderer.invoke / send / on - └─ main: ipcMain.handle / ipcMain.on / webContents.send + └─ main: safeSendToRenderer / ipcMain.handle / ipcMain.on └─ @maka/runtime (agent runtime) + @maka/storage (JSONL persistence) ``` diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 5bf424c6b6..cd8cac7270 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -12,9 +12,7 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ ## AppShell + the action modules -`app-shell.tsx` is the shell component: owns session state, wires the `@maka/ui` panels (SessionListPanel, ChatView, Composer — ChatView renders the tool stream via `ToolTrow`), and mounts lazy panels (ArtifactPane, BrowserPanel). It is supported by a set of `app-shell--.ts(x)` modules, each a narrow slice of shell logic split by concern (e.g. `app-shell-session-events.ts`, `app-shell-chat-actions.ts`, `app-shell-plan-actions.ts`, `app-shell-effects.ts`, `app-shell-stop-action.ts`). - -Naming convention for a new slice: `app-shell--.ts` (or `.tsx` when it returns JSX). Keep a slice to one concern; if it grows, split along the same `app-shell--` seam. +`app-shell.tsx` is the shell component: owns session state, wires the `@maka/ui` panels (SessionListPanel, ChatView, Composer — ChatView renders the tool stream via `ToolTrow`), and mounts lazy panels (ArtifactPane, BrowserPanel). It is supported by a set of `app-shell-*` modules, each a narrow slice of shell logic split by one concern (e.g. `app-shell-session-events.ts`, `app-shell-chat-actions.ts`, `app-shell-plan-actions.ts`, `app-shell-effects.ts`, `app-shell-stop-action.ts`, `app-shell-overlays.tsx`). Most follow `app-shell--.ts(x)`; a few single-word slices like `app-shell-effects.ts` or `app-shell-copy.ts` drop the action segment. Keep a slice to one concern; if it grows, split along the same seam. `settings/` holds the settings pages and the `SettingsModal` shell — one page per `SettingsSection` (defined in `@maka/core`); the models/providers page is `ProvidersPanel`. Plus the `provider-*` files and the shared `settings-rows` / `settings-skeleton` / `settings-surface` helpers. @@ -23,7 +21,7 @@ Naming convention for a new slice: `app-shell--.ts` (or `.tsx` wh | File | Role | |---|---| | `maka-tokens.css` | Single source of CSS tokens (color / shadow / typography / radius / spacing / motion / z / layout) **and** a few component-recipe fallbacks at the tail. Transitional: tokens and recipes coexist in one file. | -| `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (see its header comment). **Transitional** — meant to be folded back into the token/style system and removed. | +| `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (its header comment documents the provenance). **Transitional** — meant to be folded back into the token/style system and removed. | | `styles/*.css` | Per-surface hand-written recipes (e.g. `chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`). | Token authoring rule: custom CSS variables go in `maka-tokens.css`; only component-local vars are excepted and must carry `/* local: ... */`. No new hardcoded color / radius / z-index. diff --git a/packages/ui/README.md b/packages/ui/README.md index b61be06809..91490c7950 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -1,6 +1,6 @@ # @maka/ui -Shared UI layer for the Maka desktop app: React + Tailwind v4 + shadcn (base-nova) + `@base-ui/react`, bound to Maka's token system. Consumed by `apps/desktop`'s renderer, and only that today. +Shared UI layer for the Maka desktop app: React + Tailwind v4 + shadcn (base-nova) + `@base-ui/react`, bound to Maka's token system. Consumed at runtime by `apps/desktop`'s renderer; the preload bridge also imports types from it (`import type` only). This package is the **target carrier of the frontend convergence**: hand-rolled renderer CSS recipes are being retired onto primitives exported here. When in doubt, extend a primitive rather than add CSS at the call site. @@ -15,7 +15,7 @@ Four export surfaces, in the order to look: | `src/*.tsx` / `src/*.ts` (top-level) | Feature components + pure logic (e.g. `chat-view.tsx`, `composer.tsx`, `permission-dialog.tsx`, `session-list-panel.tsx`, plus pure helpers like `materialize.ts`, `redact.ts`, `smooth-stream.ts`). | stable | | `src/components.tsx` | Re-export barrel for the feature components above (ChatView, Composer, PermissionDialog, …). | stable | -`src/index.ts` is the package barrel. It follows an **off-barrel convention**: internal styling tables and single-consumer dots (e.g. `markerVariants`, `LiveIndicator`) are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a second consumer or a cross-package consumer (the promotion condition is documented inline in `index.ts`). Don't add to the barrel speculatively. +`src/index.ts` is the package barrel. It follows an **off-barrel convention**: some styling tables and per-surface helpers are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a second consumer or a cross-package consumer (the promotion condition is documented inline in `index.ts`). Don't add to the barrel speculatively. Example: `markerVariants` in `primitives/chat.tsx` has real consumers but is reached via relative import, not the barrel. ## `data-slot` hooks @@ -25,10 +25,9 @@ Most primitives expose a stable `data-slot=""` attribute so renderer CSS c ```ts import { Button, ChatView, Composer, Badge, Chip, PageHeader, useToast } from '@maka/ui'; -import { ProviderLogo } from '@maka/ui/icons'; ``` -Sub-path exports (declared in `package.json` `exports`): `@maka/ui/artifact-preview-registry`, `@maka/ui/assistant-stream`, `@maka/ui/icons`, `@maka/ui/maka-uri`, `@maka/ui/smooth-stream`. +Sub-path exports (declared in `package.json` `exports`): `@maka/ui/artifact-preview-registry`, `@maka/ui/assistant-stream`, `@maka/ui/icons`, `@maka/ui/maka-uri`, `@maka/ui/smooth-stream`. (`@maka/ui/icons` re-exports Lucide symbols; provider brand logos live in the renderer, not here.) Renderer CSS may target a primitive via its `data-slot` attribute, never by overriding the primitive's own utility classes. From a1d7e42554b61f863148d54420c49708b5c74c1f Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 02:09:17 +0800 Subject: [PATCH 06/18] docs(frontend): fix round-4 review findings in READMEs - desktop: window is created hidden, revealed after first AppShell paint (notifyRendererReady gate), not 'preload skeleton shows within ms'; drop the false 'background mutations always push via channels, UI converges lazily' invariant (interrupted-session recovery doesn't emit). - ui: scope the off-barrel 'don't re-export' rule to single in-package consumers with no cross-package consumer (previewVariants is re-exported for exactly that cross-package reason), resolving the contradiction with the promotion rule. --- apps/desktop/README.md | 2 +- packages/ui/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/README.md b/apps/desktop/README.md index fc13ea79c5..253425f313 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -23,7 +23,7 @@ The Electron desktop app: `main` (Node/Electron main process) + `preload` (conte Sub-folders: `browser/` (embedded browser view), `oauth/`, `search/` (thread search), `web-search/`, `types/`. The browser IPC handler itself (`browser-ipc-main.ts`) is flat in `src/main/`, not under `browser/`. -`main.ts` startup order: stores and the runtime/controller are created synchronously at module load (top-level `create*Store` / runtime wiring); `registerIpc()` runs at top level, **before** `app.whenReady()`; inside `whenReady`, the main window is created as early as possible (preload skeleton shows within milliseconds) and background startup (credential migration, connection bootstrapping, telemetry, bots, gateway, schedulers) runs concurrently without blocking first paint. The real invariant for IPC: handlers must be registered before the renderer entry runs, because `main.tsx` prefetches the onboarding snapshot before mounting React. Background startup mutations are pushed to the renderer via the `sessions:changed` / `connections:event` / `settings:bots:statusChanged` channels, so the UI converges lazily. +`main.ts` startup order: stores and the runtime/controller are created synchronously at module load; `registerIpc()` runs at top level, **before** `app.whenReady()`; inside `whenReady`, the main window is created **hidden** early and background startup (credential migration, connection bootstrapping, telemetry, bots, gateway, schedulers) runs concurrently without blocking first paint. The window is revealed only after the renderer's first AppShell paint (the `window:notifyRendererReady` gate in `app.tsx`), so the user never sees the preload skeleton. The real invariant for IPC: handlers must be registered before the renderer entry runs, because `main.tsx` prefetches the onboarding snapshot before mounting React. Background startup may mutate state after the renderer's first read, so don't assume it has already settled when wiring the UI. ## IPC contract diff --git a/packages/ui/README.md b/packages/ui/README.md index 91490c7950..26e8e80993 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -36,7 +36,7 @@ Renderer CSS may target a primitive via its `data-slot` attribute, never by over - **New primitive** (button-like, dialog-like, form control) → a new file in `src/primitives/`, exposing `data-slot`, re-exported from `index.ts`. - **New feature component** → top-level `src/.tsx`, re-exported from `src/components.tsx` and `index.ts`. - **Don't** add a per-surface hand-rolled CSS recipe in the renderer if a primitive can carry it — extend the primitive's API/slots instead. -- **Don't** re-export a single-consumer symbol from the barrel; keep it a relative import until a second consumer appears. +- **Don't** re-export a symbol that has a single in-package consumer and no cross-package consumer; keep it a relative import until a second or cross-package consumer appears (a cross-package consumer can't use a relative import — `previewVariants` is re-exported for exactly that reason). ## Convergence direction (transitional surfaces) From c9884018ee892f0e2dee27a9dac0b28801130101 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 02:24:34 +0800 Subject: [PATCH 07/18] docs(frontend): fix round-5 review findings in READMEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - desktop/renderer: window reveal has a fallback timer, and main.tsx's onboarding prefetch can time out to a fail-soft loading state — stop claiming 'only after first paint' / 'never sees skeleton' / 'no loading flash' as absolute paths. - renderer: maka-tokens.css is the main token source, but a few @theme Tailwind-bridge values (e.g. --shadow-maka-panel) live in styles.css and are contract-pinned there — document the exception instead of claiming a single source. - ui: clarify 'model-provider brand logos' (renderer settings/provider-*); bot-provider logos are in @maka/ui's bot-brand-logo. --- apps/desktop/README.md | 2 +- apps/desktop/src/renderer/README.md | 6 +++--- packages/ui/README.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 253425f313..7e31b7c418 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -23,7 +23,7 @@ The Electron desktop app: `main` (Node/Electron main process) + `preload` (conte Sub-folders: `browser/` (embedded browser view), `oauth/`, `search/` (thread search), `web-search/`, `types/`. The browser IPC handler itself (`browser-ipc-main.ts`) is flat in `src/main/`, not under `browser/`. -`main.ts` startup order: stores and the runtime/controller are created synchronously at module load; `registerIpc()` runs at top level, **before** `app.whenReady()`; inside `whenReady`, the main window is created **hidden** early and background startup (credential migration, connection bootstrapping, telemetry, bots, gateway, schedulers) runs concurrently without blocking first paint. The window is revealed only after the renderer's first AppShell paint (the `window:notifyRendererReady` gate in `app.tsx`), so the user never sees the preload skeleton. The real invariant for IPC: handlers must be registered before the renderer entry runs, because `main.tsx` prefetches the onboarding snapshot before mounting React. Background startup may mutate state after the renderer's first read, so don't assume it has already settled when wiring the UI. +`main.ts` startup order: stores and the runtime/controller are created synchronously at module load; `registerIpc()` runs at top level, **before** `app.whenReady()`; inside `whenReady`, the main window is created **hidden** early and background startup (credential migration, connection bootstrapping, telemetry, bots, gateway, schedulers) runs concurrently without blocking first paint. The window is created hidden and revealed after the renderer's first AppShell paint (the `window:notifyRendererReady` gate in `app.tsx`); a fallback timer reveals it if the renderer never signals, so a fail-soft loading state can show (e.g. if `main.tsx`'s onboarding prefetch times out). The real invariant for IPC: handlers must be registered before the renderer entry runs, because `main.tsx` prefetches the onboarding snapshot before mounting React. Background startup may mutate state after the renderer's first read, so don't assume it has already settled when wiring the UI. ## IPC contract diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index cd8cac7270..3cb635360b 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -6,7 +6,7 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ ## Entry -`main.tsx` → `app.tsx` → `AppShell` (`app-shell.tsx`). `index.html` is the Vite HTML shell. `main.tsx` prefetches the onboarding snapshot before mounting React so the first commit paints the real surface (no loading flash); `app.tsx` wraps `AppShell` in `ToastProvider` + `ErrorBoundary`. +`main.tsx` → `app.tsx` → `AppShell` (`app-shell.tsx`). `index.html` is the Vite HTML shell. `main.tsx` prefetches the onboarding snapshot before mounting React so the normal-path first commit paints the real surface (if the prefetch times out it mounts with `null` and a fail-soft loading state); `app.tsx` wraps `AppShell` in `ToastProvider` + `ErrorBoundary`. `styles.css` is the **only** style entry: it `@import`s Tailwind, fonts, `maka-tokens.css`, `reference-shell.css`, and every `styles/*.css`. Per CSS governance, `styles.css` may only contain `@import` / `@source` / `@theme` / top-level orchestration — real selector rules go in `styles/*.css`. @@ -20,11 +20,11 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ | File | Role | |---|---| -| `maka-tokens.css` | Single source of CSS tokens (color / shadow / typography / radius / spacing / motion / z / layout) **and** a few component-recipe fallbacks at the tail. Transitional: tokens and recipes coexist in one file. | +| `maka-tokens.css` | The main source of CSS tokens (color / shadow / typography / radius / spacing / motion / z / layout) **and** a few component-recipe fallbacks at the tail. Transitional: tokens and recipes coexist in one file. A few `@theme` Tailwind-bridge values (e.g. `--shadow-maka-panel`) live in `styles.css` and are contract-pinned there, not here. | | `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (its header comment documents the provenance). **Transitional** — meant to be folded back into the token/style system and removed. | | `styles/*.css` | Per-surface hand-written recipes (e.g. `chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`). | -Token authoring rule: custom CSS variables go in `maka-tokens.css`; only component-local vars are excepted and must carry `/* local: ... */`. No new hardcoded color / radius / z-index. +Token authoring rule: custom CSS variables go in `maka-tokens.css`; component-local vars must carry `/* local: ... */`, and `@theme` Tailwind-bridge values live in `styles.css` (contract-pinned). No new hardcoded color / radius / z-index. Note the `--foreground-N` split: the wash stops (`-2/-3/-5/-8/-10`) are surface fills for backgrounds and borders, **not** text. The 3-tier semantic aliases (`--foreground` / `--foreground-secondary` / `--muted-foreground`) are the text-color vocabulary. They are separate concerns — don't collapse the wash stops into the text aliases. diff --git a/packages/ui/README.md b/packages/ui/README.md index 26e8e80993..9a2be4a2ed 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -27,7 +27,7 @@ Most primitives expose a stable `data-slot=""` attribute so renderer CSS c import { Button, ChatView, Composer, Badge, Chip, PageHeader, useToast } from '@maka/ui'; ``` -Sub-path exports (declared in `package.json` `exports`): `@maka/ui/artifact-preview-registry`, `@maka/ui/assistant-stream`, `@maka/ui/icons`, `@maka/ui/maka-uri`, `@maka/ui/smooth-stream`. (`@maka/ui/icons` re-exports Lucide symbols; provider brand logos live in the renderer, not here.) +Sub-path exports (declared in `package.json` `exports`): `@maka/ui/artifact-preview-registry`, `@maka/ui/assistant-stream`, `@maka/ui/icons`, `@maka/ui/maka-uri`, `@maka/ui/smooth-stream`. (`@maka/ui/icons` re-exports Lucide symbols; model-provider brand logos live in the renderer's `settings/provider-*`, not here — bot-provider logos are in `@maka/ui`'s `bot-brand-logo`.) Renderer CSS may target a primitive via its `data-slot` attribute, never by overriding the primitive's own utility classes. From 3f5126282155b857dd2e0e36a178a760fb9baa11 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 02:52:32 +0800 Subject: [PATCH 08/18] docs(frontend): fix round-6 review findings in READMEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - renderer: document the contract-pinned index.html inline .maka-preload skeleton (hardcoded colors, no CSS vars — maka-tokens.css hasn't loaded yet) as the narrow exception to 'styles.css is the only CSS entry'. - ui: resolve the barrel-rule contradiction — new feature components re-export from components.tsx, but only reach index.ts when they have a second or cross-package consumer (primitives are always re-exported). --- apps/desktop/src/renderer/README.md | 2 +- packages/ui/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 3cb635360b..6d6ae7f1e0 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -8,7 +8,7 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ `main.tsx` → `app.tsx` → `AppShell` (`app-shell.tsx`). `index.html` is the Vite HTML shell. `main.tsx` prefetches the onboarding snapshot before mounting React so the normal-path first commit paints the real surface (if the prefetch times out it mounts with `null` and a fail-soft loading state); `app.tsx` wraps `AppShell` in `ToastProvider` + `ErrorBoundary`. -`styles.css` is the **only** style entry: it `@import`s Tailwind, fonts, `maka-tokens.css`, `reference-shell.css`, and every `styles/*.css`. Per CSS governance, `styles.css` may only contain `@import` / `@source` / `@theme` / top-level orchestration — real selector rules go in `styles/*.css`. +`styles.css` is the **only** bundled style entry: it `@import`s Tailwind, fonts, `maka-tokens.css`, `reference-shell.css`, and every `styles/*.css`. Per CSS governance, `styles.css` may only contain `@import` / `@source` / `@theme` / top-level orchestration — real selector rules go in `styles/*.css`. One contract-pinned exception: `index.html` carries an inline `.maka-preload` skeleton with hardcoded colors (no CSS variables — `maka-tokens.css` hasn't loaded yet) so there's no blank window during the CSS + JS load gap; `createRoot` replaces it on mount. ## AppShell + the action modules diff --git a/packages/ui/README.md b/packages/ui/README.md index 9a2be4a2ed..3b90e63fe0 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -33,8 +33,8 @@ Renderer CSS may target a primitive via its `data-slot` attribute, never by over ## Where new code goes -- **New primitive** (button-like, dialog-like, form control) → a new file in `src/primitives/`, exposing `data-slot`, re-exported from `index.ts`. -- **New feature component** → top-level `src/.tsx`, re-exported from `src/components.tsx` and `index.ts`. +- **New primitive** (button-like, dialog-like, form control) → a new file in `src/primitives/`, exposing `data-slot`, re-exported from `index.ts` (primitives are the public surface). +- **New feature component** → top-level `src/.tsx`, re-exported from `src/components.tsx`; re-export from `index.ts` only when it has a second or cross-package consumer (a single in-package consumer stays a relative import). - **Don't** add a per-surface hand-rolled CSS recipe in the renderer if a primitive can carry it — extend the primitive's API/slots instead. - **Don't** re-export a symbol that has a single in-package consumer and no cross-package consumer; keep it a relative import until a second or cross-package consumer appears (a cross-package consumer can't use a relative import — `previewVariants` is re-exported for exactly that reason). From 180a35dc5199e3446ca456942b4097a3785d8c84 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 03:02:15 +0800 Subject: [PATCH 09/18] docs(ui): fix the feature-component barrel rule (index.ts re-exports components.tsx) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit index.ts does 'export * from ./components.js', so re-exporting a feature component from components.tsx already puts it on the package barrel — there is no separate 'add to index.ts later' stage. Rule now: relative import while single in-package consumer; re-export from components.tsx (barrel follows automatically) once a second or cross-package consumer appears. --- packages/ui/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/README.md b/packages/ui/README.md index 3b90e63fe0..19f0985987 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -34,7 +34,7 @@ Renderer CSS may target a primitive via its `data-slot` attribute, never by over ## Where new code goes - **New primitive** (button-like, dialog-like, form control) → a new file in `src/primitives/`, exposing `data-slot`, re-exported from `index.ts` (primitives are the public surface). -- **New feature component** → top-level `src/.tsx`, re-exported from `src/components.tsx`; re-export from `index.ts` only when it has a second or cross-package consumer (a single in-package consumer stays a relative import). +- **New feature component** → top-level `src/.tsx`. Keep it a relative import while it has only a single in-package consumer; once it has a second or cross-package consumer, re-export it from `src/components.tsx` (`index.ts` does `export * from './components.js'`, so anything re-exported there is already on the package barrel — there's no separate “add to index.ts” step). - **Don't** add a per-surface hand-rolled CSS recipe in the renderer if a primitive can carry it — extend the primitive's API/slots instead. - **Don't** re-export a symbol that has a single in-package consumer and no cross-package consumer; keep it a relative import until a second or cross-package consumer appears (a cross-package consumer can't use a relative import — `previewVariants` is re-exported for exactly that reason). From 0c5cd1016186d47e6e06045c55df29f1129eec83 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 03:17:09 +0800 Subject: [PATCH 10/18] docs(renderer): fix the @theme bridge and local-var rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The @theme Tailwind bridge is split: most aliases (color/typography/spacing/ radius) live in maka-tokens.css, a few values (e.g. --shadow-maka-panel) in styles.css — both contract-pinned, so check which file owns a value before moving it. /* local: ... */ is the rule for new component-local vars; existing ones don't all carry it yet. --- apps/desktop/src/renderer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 6d6ae7f1e0..53b0ce7e81 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -24,7 +24,7 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ | `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (its header comment documents the provenance). **Transitional** — meant to be folded back into the token/style system and removed. | | `styles/*.css` | Per-surface hand-written recipes (e.g. `chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`). | -Token authoring rule: custom CSS variables go in `maka-tokens.css`; component-local vars must carry `/* local: ... */`, and `@theme` Tailwind-bridge values live in `styles.css` (contract-pinned). No new hardcoded color / radius / z-index. +Token authoring rule: custom CSS variables go in `maka-tokens.css`. The `@theme` Tailwind bridge is split — most aliases (color / typography / spacing / radius) live in `maka-tokens.css`, a few values (e.g. `--shadow-maka-panel`) live in `styles.css`; both locations are contract-pinned, so check which file owns a given bridge value before moving it. New component-local vars should carry `/* local: ... */` (existing ones don't all have it yet). No new hardcoded color / radius / z-index. Note the `--foreground-N` split: the wash stops (`-2/-3/-5/-8/-10`) are surface fills for backgrounds and borders, **not** text. The 3-tier semantic aliases (`--foreground` / `--foreground-secondary` / `--muted-foreground`) are the text-color vocabulary. They are separate concerns — don't collapse the wash stops into the text aliases. From 22bd75aa50e959e0c5a2bd03dbc6d3a0d9c180dc Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 03:27:15 +0800 Subject: [PATCH 11/18] docs(renderer): describe the @theme bridge accurately (overlaps, not split) Both maka-tokens.css and styles.css carry an @theme inline block, and their color aliases overlap (--color-background/accent/muted appear in both); styles.css also carries the typography/line-height/font-weight/tracking/ spacing/radius/shadow bridges. Each value's home is contract-pinned (spacing/letter-spacing/foreground-tier contracts), so stop describing it as a clean split and point to the owning contract instead. --- apps/desktop/src/renderer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 53b0ce7e81..6138da1149 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -24,7 +24,7 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ | `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (its header comment documents the provenance). **Transitional** — meant to be folded back into the token/style system and removed. | | `styles/*.css` | Per-surface hand-written recipes (e.g. `chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`). | -Token authoring rule: custom CSS variables go in `maka-tokens.css`. The `@theme` Tailwind bridge is split — most aliases (color / typography / spacing / radius) live in `maka-tokens.css`, a few values (e.g. `--shadow-maka-panel`) live in `styles.css`; both locations are contract-pinned, so check which file owns a given bridge value before moving it. New component-local vars should carry `/* local: ... */` (existing ones don't all have it yet). No new hardcoded color / radius / z-index. +Token authoring rule: custom CSS variables go in `maka-tokens.css`. The `@theme inline` Tailwind bridge is **not** cleanly split — both `maka-tokens.css` and `styles.css` carry one, and their color aliases overlap (e.g. `--color-background`, `--color-accent`, `--color-muted` appear in both); `styles.css`'s block also carries the typography / line-height / font-weight / tracking / spacing / radius / shadow bridges. Each bridge value's home is contract-pinned (see e.g. `spacing-converge-contract`, `letter-spacing-converge-contract`, `foreground-tier-contract`), so check the owning contract before moving one. New component-local vars should carry `/* local: ... */` (existing ones don't all have it yet). No new hardcoded color / radius / z-index. Note the `--foreground-N` split: the wash stops (`-2/-3/-5/-8/-10`) are surface fills for backgrounds and borders, **not** text. The 3-tier semantic aliases (`--foreground` / `--foreground-secondary` / `--muted-foreground`) are the text-color vocabulary. They are separate concerns — don't collapse the wash stops into the text aliases. From 11a865a6a2b00c89887c89275f4a499d9f939e49 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 03:41:30 +0800 Subject: [PATCH 12/18] docs(frontend): fix round-10 review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ui: barrel promotion is cross-package consumer or explicit public-API need, not 'second in-package consumer' (attachment-file-card has two in-package consumers but stays off-barrel); remove the markerVariants example that implied otherwise. - renderer: maka-tokens.css tail is a large recipe section (base/utilities/ recipes/animations), not 'a few fallbacks'; the @theme bridge overlap is concrete (--color-muted maps to --foreground-5 in styles.css but --muted in maka-tokens.css); only some bridge values are contract-pinned (spacing/letter-spacing/foreground-tier), overlapping color aliases are not — don't claim 'each' is pinned. --- apps/desktop/src/renderer/README.md | 4 ++-- packages/ui/README.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 6138da1149..234c9161de 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -20,11 +20,11 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ | File | Role | |---|---| -| `maka-tokens.css` | The main source of CSS tokens (color / shadow / typography / radius / spacing / motion / z / layout) **and** a few component-recipe fallbacks at the tail. Transitional: tokens and recipes coexist in one file. A few `@theme` Tailwind-bridge values (e.g. `--shadow-maka-panel`) live in `styles.css` and are contract-pinned there, not here. | +| `maka-tokens.css` | The main source of CSS tokens (color / shadow / typography / radius / spacing / motion / z / layout), plus a large recipe section at the tail (base styles, utilities, component recipes, animations). Transitional: tokens and recipes coexist in one file. Its `@theme inline` block holds color aliases. | | `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (its header comment documents the provenance). **Transitional** — meant to be folded back into the token/style system and removed. | | `styles/*.css` | Per-surface hand-written recipes (e.g. `chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`). | -Token authoring rule: custom CSS variables go in `maka-tokens.css`. The `@theme inline` Tailwind bridge is **not** cleanly split — both `maka-tokens.css` and `styles.css` carry one, and their color aliases overlap (e.g. `--color-background`, `--color-accent`, `--color-muted` appear in both); `styles.css`'s block also carries the typography / line-height / font-weight / tracking / spacing / radius / shadow bridges. Each bridge value's home is contract-pinned (see e.g. `spacing-converge-contract`, `letter-spacing-converge-contract`, `foreground-tier-contract`), so check the owning contract before moving one. New component-local vars should carry `/* local: ... */` (existing ones don't all have it yet). No new hardcoded color / radius / z-index. +Token authoring rule: custom CSS variables go in `maka-tokens.css`. The `@theme inline` Tailwind bridge is **not** cleanly split — both `maka-tokens.css` and `styles.css` carry one, and their color aliases overlap (e.g. `--color-background`, `--color-accent`, `--color-muted` appear in both, and `--color-muted` maps to `--foreground-5` in `styles.css` but to `--muted` in `maka-tokens.css`); `styles.css`'s block also carries the typography / line-height / font-weight / tracking / spacing / radius / shadow bridges. Some bridge values are contract-pinned to a file (e.g. spacing / letter-spacing / foreground-tier contracts), but the overlapping color aliases are *not* — check the owning contract before moving one. New component-local vars should carry `/* local: ... */` (existing ones don't all have it yet). No new hardcoded color / radius / z-index. Note the `--foreground-N` split: the wash stops (`-2/-3/-5/-8/-10`) are surface fills for backgrounds and borders, **not** text. The 3-tier semantic aliases (`--foreground` / `--foreground-secondary` / `--muted-foreground`) are the text-color vocabulary. They are separate concerns — don't collapse the wash stops into the text aliases. diff --git a/packages/ui/README.md b/packages/ui/README.md index 19f0985987..58293edfe4 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -15,7 +15,7 @@ Four export surfaces, in the order to look: | `src/*.tsx` / `src/*.ts` (top-level) | Feature components + pure logic (e.g. `chat-view.tsx`, `composer.tsx`, `permission-dialog.tsx`, `session-list-panel.tsx`, plus pure helpers like `materialize.ts`, `redact.ts`, `smooth-stream.ts`). | stable | | `src/components.tsx` | Re-export barrel for the feature components above (ChatView, Composer, PermissionDialog, …). | stable | -`src/index.ts` is the package barrel. It follows an **off-barrel convention**: some styling tables and per-surface helpers are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a second consumer or a cross-package consumer (the promotion condition is documented inline in `index.ts`). Don't add to the barrel speculatively. Example: `markerVariants` in `primitives/chat.tsx` has real consumers but is reached via relative import, not the barrel. +`src/index.ts` is the package barrel. It follows an **off-barrel convention**: some styling tables and per-surface helpers are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a **cross-package consumer or an explicit public-API need** — not merely a second in-package consumer (`attachment-file-card` has two in-package consumers, `chat-view` and `composer`, but stays off-barrel). Don't add to the barrel speculatively. ## `data-slot` hooks @@ -34,9 +34,9 @@ Renderer CSS may target a primitive via its `data-slot` attribute, never by over ## Where new code goes - **New primitive** (button-like, dialog-like, form control) → a new file in `src/primitives/`, exposing `data-slot`, re-exported from `index.ts` (primitives are the public surface). -- **New feature component** → top-level `src/.tsx`. Keep it a relative import while it has only a single in-package consumer; once it has a second or cross-package consumer, re-export it from `src/components.tsx` (`index.ts` does `export * from './components.js'`, so anything re-exported there is already on the package barrel — there's no separate “add to index.ts” step). +- **New feature component** → top-level `src/.tsx`, kept as a relative import until it has a cross-package consumer or an explicit public-API need; then re-export it from `src/components.tsx` (`index.ts` does `export * from './components.js'`, so it lands on the barrel automatically). - **Don't** add a per-surface hand-rolled CSS recipe in the renderer if a primitive can carry it — extend the primitive's API/slots instead. -- **Don't** re-export a symbol that has a single in-package consumer and no cross-package consumer; keep it a relative import until a second or cross-package consumer appears (a cross-package consumer can't use a relative import — `previewVariants` is re-exported for exactly that reason). +- **Don't** re-export a symbol onto the barrel without a cross-package consumer or explicit public-API need; keep it a relative import even with multiple in-package consumers (a cross-package consumer can't use a relative import — `previewVariants` is re-exported for exactly that reason). ## Convergence direction (transitional surfaces) From 582f3729f2521f93159a998e3a7d318206cf06c5 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 03:59:02 +0800 Subject: [PATCH 13/18] docs(frontend): fix round-11 review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - renderer: stop claiming overlapping color aliases have no contract pin — some do (--color-control in styles.css via design-system-governance-406; --color-muted-foreground in maka-tokens.css via foreground-tier); only some (e.g. --color-background/accent/muted) are unpinned. - desktop: narrow the safe-send claim — the contract test scans a fixed file list for direct mainWindow.webContents.send forms; new *-ipc-main.ts files aren't auto-covered, so route through the guard in every new file. - ui: sync the index.ts and chat.tsx LiveIndicator comments to the README barrel rule (cross-package consumer or explicit public-API need, not a second in-package consumer; attachment-file-card precedent), so the README is the single source of the promotion rule. --- apps/desktop/README.md | 2 +- apps/desktop/src/renderer/README.md | 2 +- packages/ui/src/index.ts | 17 +++++++++-------- packages/ui/src/primitives/chat.tsx | 5 +++-- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 7e31b7c418..996513d9c0 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -30,7 +30,7 @@ Sub-folders: `browser/` (embedded browser view), `oauth/`, `search/` (thread sea Three patterns, all rooted in preload's `maka` namespace. Channel names are `:`. - **Request/response** — `ipcRenderer.invoke(':', …args)` in preload ↔ `ipcMain.handle(':', …)`. The handler lives either inline in `main.ts` (e.g. `sessions:list`, `settings:get`) or in a `*-ipc-main.ts` extracted by domain (e.g. `connections-ipc-main`, `daily-review-ipc-main`). Both forms coexist; prefer extracting a new domain to its own `*-ipc-main.ts`. -- **Main→renderer push** — main sends through the safe-send guard (`safeSendToRenderer` via `mainWindowController.send`), not raw `webContents.send` (which throws when the window/`webContents` is destroyed); preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn (e.g. `sessions:changed`, `plans:changed`, `artifacts:changed`, `gateway:statusChanged`). A contract test enforces routing every main-window send through the guard. +- **Main→renderer push** — main sends through the safe-send guard (`safeSendToRenderer` via `mainWindowController.send`), not raw `webContents.send` (which throws when the window/`webContents` is destroyed); preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn (e.g. `sessions:changed`, `plans:changed`, `artifacts:changed`, `gateway:statusChanged`). The safe-send contract test scans a fixed list of main-source files for direct `mainWindow.webContents.send(...)` forms — new `*-ipc-main.ts` files aren't auto-covered, so route sends through the guard in every new file (an alias for `mainWindow` can bypass the literal scan). - **Renderer→main fire-and-forget** — `ipcRenderer.send(':', …)` in preload ↔ `ipcMain.on(':', …)`. Used when no response is needed (e.g. `browser:active-session`, `browser:setViewport`). Adding a new IPC surface: if extracting, write the `*-ipc-main.ts` exporting a `register*Ipc(...)`, import it in `main.ts`, and call it inside `registerIpc()`; add the matching method to the `maka` namespace in `preload.ts`; add the method to the `window.maka` type in `src/global.d.ts` (the renderer's typed bridge — without it, renderer calls get a TS error); keep the `:` channel naming. A handler file that isn't registered in `registerIpc()` compiles but never mounts. diff --git a/apps/desktop/src/renderer/README.md b/apps/desktop/src/renderer/README.md index 234c9161de..27c08f11c5 100644 --- a/apps/desktop/src/renderer/README.md +++ b/apps/desktop/src/renderer/README.md @@ -24,7 +24,7 @@ For the main/preload/renderer split and the IPC contract, see `apps/desktop/READ | `reference-shell.css` | A target-layout shell rebuild, hand-authored from a reference-implementation extract (its header comment documents the provenance). **Transitional** — meant to be folded back into the token/style system and removed. | | `styles/*.css` | Per-surface hand-written recipes (e.g. `chat-*`, `sidebar`, `composer`, `palette`, `settings/*`, `module-pages/*`). | -Token authoring rule: custom CSS variables go in `maka-tokens.css`. The `@theme inline` Tailwind bridge is **not** cleanly split — both `maka-tokens.css` and `styles.css` carry one, and their color aliases overlap (e.g. `--color-background`, `--color-accent`, `--color-muted` appear in both, and `--color-muted` maps to `--foreground-5` in `styles.css` but to `--muted` in `maka-tokens.css`); `styles.css`'s block also carries the typography / line-height / font-weight / tracking / spacing / radius / shadow bridges. Some bridge values are contract-pinned to a file (e.g. spacing / letter-spacing / foreground-tier contracts), but the overlapping color aliases are *not* — check the owning contract before moving one. New component-local vars should carry `/* local: ... */` (existing ones don't all have it yet). No new hardcoded color / radius / z-index. +Token authoring rule: custom CSS variables go in `maka-tokens.css`. The `@theme inline` Tailwind bridge is **not** cleanly split — both `maka-tokens.css` and `styles.css` carry one, and their color aliases overlap (e.g. `--color-background`, `--color-accent`, `--color-muted` appear in both, and `--color-muted` maps to `--foreground-5` in `styles.css` but to `--muted` in `maka-tokens.css`); `styles.css`'s block also carries the typography / line-height / font-weight / tracking / spacing / radius / shadow bridges. Some bridge values are contract-pinned to a file — spacing / letter-spacing / typography contracts pin theirs to `styles.css`, `foreground-tier` pins `--color-muted-foreground` to `maka-tokens.css`, `design-system-governance-406` pins `--color-control` to `styles.css` — but other overlapping color aliases (e.g. `--color-background`, `--color-accent`, `--color-muted`) aren't pinned to a file; check the owning contract before moving one. New component-local vars should carry `/* local: ... */` (existing ones don't all have it yet). No new hardcoded color / radius / z-index. Note the `--foreground-N` split: the wash stops (`-2/-3/-5/-8/-10`) are surface fills for backgrounds and borders, **not** text. The 3-tier semantic aliases (`--foreground` / `--foreground-secondary` / `--muted-foreground`) are the text-color vocabulary. They are separate concerns — don't collapse the wash stops into the text aliases. diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 529c3198e5..0dd49c502a 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -34,18 +34,19 @@ export * from './bot-brand-logo.js'; export * from './primitives/alert.js'; export * from './primitives/card.js'; // `markerVariants` / `streamVariants` / `toolVariants` / `LiveIndicator` are -// deliberately NOT re-exported here: they are internal styling tables / a -// single-consumer dot that the chat call sites apply via relative import, so -// keeping them off the package +// deliberately NOT re-exported here: they are internal styling tables / dots +// the chat call sites apply via relative import, so keeping them off the package // barrel preserves the governance goal — they stay renamable/removable without a // public-API break. (Contrast `buttonVariants`, which IS public because it has -// external consumers.) `LiveIndicator` is exported to public only when the -// reasoning / composer / onboarding live dots actually migrate onto it — not -// speculatively before a second consumer exists. +// external consumers.) Promotion rule: a symbol earns barrel export on a +// **cross-package consumer or an explicit public-API need** — a second in-package +// consumer alone is not enough (e.g. `attachment-file-card` serves `chat-view` +// and `composer` but stays off-barrel). `LiveIndicator` is exported only when a +// cross-package consumer actually needs it, not speculatively. // -// `previewVariants` (#332 PR4) IS re-exported: its file-diff parts have a second, +// `previewVariants` (#332 PR4) IS re-exported: its file-diff parts have a // cross-package consumer — `apps/desktop`'s `artifact-preview.tsx` — which is the -// promotion condition the off-barrel convention named, so the export is the rule. +// promotion condition, so the export is the rule. export { Bubble, Marker, Message, previewVariants } from './primitives/chat.js'; export { formatTurnDuration } from './chat-display-helpers.js'; export type { diff --git a/packages/ui/src/primitives/chat.tsx b/packages/ui/src/primitives/chat.tsx index 06f01f1bd4..9d91bb5bc0 100644 --- a/packages/ui/src/primitives/chat.tsx +++ b/packages/ui/src/primitives/chat.tsx @@ -331,8 +331,9 @@ export { streamVariants }; * `streamVariants`): the tool stream is its only consumer today. The duplicate * reasoning / composer / onboarding live dots can adopt it in a follow-up motion * pass — retiring their own `*-pulse` keyframes onto `maka-pulse` — and that is - * when it would be promoted to a public export, not speculatively before a second - * consumer exists. Reduced-motion suppression rides on the `motion-reduce:` + * when it would be promoted to a public export — on a cross-package consumer or + * explicit public-API need, not a second in-package consumer (see + * `packages/ui/README.md` and the `attachment-file-card` precedent). Reduced-motion suppression rides on the `motion-reduce:` * utilities (real-OS `prefers-reduced-motion: reduce`), mirroring the retired * dot's `@media` rule; the visual-smoke fixture freeze is handled by `base.css`. */ From 8cfad5a87b284b89154e716d8a0748470506b2a6 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 04:11:29 +0800 Subject: [PATCH 14/18] docs(ui): correct the LiveIndicator/streamVariants comments (#712 retirement) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The synced comments still described chat call sites and the tool stream as consumers, but #712 retired streamVariants/LiveIndicator from the tool body — they have no production consumer now (chat-stream-cascade-contract pins it). State that in the comments, and drop the contradictory 'LiveIndicator exported only on cross-package consumer' line so the promotion rule is stated once (cross-package consumer or explicit public-API need) and points to README. --- packages/ui/src/index.ts | 10 +++++----- packages/ui/src/primitives/chat.tsx | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 0dd49c502a..94046138d2 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -35,14 +35,14 @@ export * from './primitives/alert.js'; export * from './primitives/card.js'; // `markerVariants` / `streamVariants` / `toolVariants` / `LiveIndicator` are // deliberately NOT re-exported here: they are internal styling tables / dots -// the chat call sites apply via relative import, so keeping them off the package -// barrel preserves the governance goal — they stay renamable/removable without a +// with no current production consumer (`streamVariants`/`LiveIndicator` were +// retired from the tool body in #712; see `chat-stream-cascade-contract`), so +// keeping them off the package barrel keeps them renamable/removable without a // public-API break. (Contrast `buttonVariants`, which IS public because it has // external consumers.) Promotion rule: a symbol earns barrel export on a // **cross-package consumer or an explicit public-API need** — a second in-package -// consumer alone is not enough (e.g. `attachment-file-card` serves `chat-view` -// and `composer` but stays off-barrel). `LiveIndicator` is exported only when a -// cross-package consumer actually needs it, not speculatively. +// consumer alone is not enough (see `packages/ui/README.md`; `attachment-file-card` +// serves `chat-view` and `composer` but stays off-barrel). // // `previewVariants` (#332 PR4) IS re-exported: its file-diff parts have a // cross-package consumer — `apps/desktop`'s `artifact-preview.tsx` — which is the diff --git a/packages/ui/src/primitives/chat.tsx b/packages/ui/src/primitives/chat.tsx index 9d91bb5bc0..c7d4507448 100644 --- a/packages/ui/src/primitives/chat.tsx +++ b/packages/ui/src/primitives/chat.tsx @@ -327,13 +327,13 @@ export { streamVariants }; * shared motion home) plus the literal values here, verified by a keyframe * contract + before/after screenshots rather than the diff harness. * - * It is kept INTERNAL (off the package barrel, applied by relative import like - * `streamVariants`): the tool stream is its only consumer today. The duplicate - * reasoning / composer / onboarding live dots can adopt it in a follow-up motion - * pass — retiring their own `*-pulse` keyframes onto `maka-pulse` — and that is - * when it would be promoted to a public export — on a cross-package consumer or - * explicit public-API need, not a second in-package consumer (see - * `packages/ui/README.md` and the `attachment-file-card` precedent). Reduced-motion suppression rides on the `motion-reduce:` + * It is kept INTERNAL (off the package barrel): it currently has no production + * consumer — the tool body retired it in #712 (see `chat-stream-cascade-contract`; + * the quiet tool-output panel is the production path now). If the duplicate + * reasoning / composer / onboarding live dots adopt it in a follow-up motion pass, + * promote it to a public export only on a cross-package consumer or explicit + * public-API need, not a second in-package consumer (see `packages/ui/README.md` + * and the `attachment-file-card` precedent). Reduced-motion suppression rides on the `motion-reduce:` * utilities (real-OS `prefers-reduced-motion: reduce`), mirroring the retired * dot's `@media` rule; the visual-smoke fixture freeze is handled by `base.css`. */ From b7c6f85224ea49c1a130479f566082429d668496 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 04:24:29 +0800 Subject: [PATCH 15/18] docs(ui): revert the source-comment edits; README is the barrel-rule owner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The barrel promotion rule is volatile when duplicated into inline source comments (consumer lists drift as symbols retire — e.g. streamVariants/ LiveIndicator in #712). Reverting the index.ts/chat.tsx comment edits keeps this PR docs-only and makes packages/ui/README.md the single source of truth (the README now says so explicitly). Cleaning up the stale source comments / dead symbols is a separate change. --- packages/ui/README.md | 2 +- packages/ui/src/index.ts | 19 +++++++++---------- packages/ui/src/primitives/chat.tsx | 13 ++++++------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/ui/README.md b/packages/ui/README.md index 58293edfe4..b00934ffe9 100644 --- a/packages/ui/README.md +++ b/packages/ui/README.md @@ -15,7 +15,7 @@ Four export surfaces, in the order to look: | `src/*.tsx` / `src/*.ts` (top-level) | Feature components + pure logic (e.g. `chat-view.tsx`, `composer.tsx`, `permission-dialog.tsx`, `session-list-panel.tsx`, plus pure helpers like `materialize.ts`, `redact.ts`, `smooth-stream.ts`). | stable | | `src/components.tsx` | Re-export barrel for the feature components above (ChatView, Composer, PermissionDialog, …). | stable | -`src/index.ts` is the package barrel. It follows an **off-barrel convention**: some styling tables and per-surface helpers are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a **cross-package consumer or an explicit public-API need** — not merely a second in-package consumer (`attachment-file-card` has two in-package consumers, `chat-view` and `composer`, but stays off-barrel). Don't add to the barrel speculatively. +`src/index.ts` is the package barrel. It follows an **off-barrel convention**: some styling tables and per-surface helpers are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a **cross-package consumer or an explicit public-API need** — not merely a second in-package consumer (`attachment-file-card` has two in-package consumers, `chat-view` and `composer`, but stays off-barrel). Don't add to the barrel speculatively. This README is the source of truth for the promotion rule; inline source comments may lag it. ## `data-slot` hooks diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 94046138d2..529c3198e5 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -34,19 +34,18 @@ export * from './bot-brand-logo.js'; export * from './primitives/alert.js'; export * from './primitives/card.js'; // `markerVariants` / `streamVariants` / `toolVariants` / `LiveIndicator` are -// deliberately NOT re-exported here: they are internal styling tables / dots -// with no current production consumer (`streamVariants`/`LiveIndicator` were -// retired from the tool body in #712; see `chat-stream-cascade-contract`), so -// keeping them off the package barrel keeps them renamable/removable without a +// deliberately NOT re-exported here: they are internal styling tables / a +// single-consumer dot that the chat call sites apply via relative import, so +// keeping them off the package +// barrel preserves the governance goal — they stay renamable/removable without a // public-API break. (Contrast `buttonVariants`, which IS public because it has -// external consumers.) Promotion rule: a symbol earns barrel export on a -// **cross-package consumer or an explicit public-API need** — a second in-package -// consumer alone is not enough (see `packages/ui/README.md`; `attachment-file-card` -// serves `chat-view` and `composer` but stays off-barrel). +// external consumers.) `LiveIndicator` is exported to public only when the +// reasoning / composer / onboarding live dots actually migrate onto it — not +// speculatively before a second consumer exists. // -// `previewVariants` (#332 PR4) IS re-exported: its file-diff parts have a +// `previewVariants` (#332 PR4) IS re-exported: its file-diff parts have a second, // cross-package consumer — `apps/desktop`'s `artifact-preview.tsx` — which is the -// promotion condition, so the export is the rule. +// promotion condition the off-barrel convention named, so the export is the rule. export { Bubble, Marker, Message, previewVariants } from './primitives/chat.js'; export { formatTurnDuration } from './chat-display-helpers.js'; export type { diff --git a/packages/ui/src/primitives/chat.tsx b/packages/ui/src/primitives/chat.tsx index c7d4507448..06f01f1bd4 100644 --- a/packages/ui/src/primitives/chat.tsx +++ b/packages/ui/src/primitives/chat.tsx @@ -327,13 +327,12 @@ export { streamVariants }; * shared motion home) plus the literal values here, verified by a keyframe * contract + before/after screenshots rather than the diff harness. * - * It is kept INTERNAL (off the package barrel): it currently has no production - * consumer — the tool body retired it in #712 (see `chat-stream-cascade-contract`; - * the quiet tool-output panel is the production path now). If the duplicate - * reasoning / composer / onboarding live dots adopt it in a follow-up motion pass, - * promote it to a public export only on a cross-package consumer or explicit - * public-API need, not a second in-package consumer (see `packages/ui/README.md` - * and the `attachment-file-card` precedent). Reduced-motion suppression rides on the `motion-reduce:` + * It is kept INTERNAL (off the package barrel, applied by relative import like + * `streamVariants`): the tool stream is its only consumer today. The duplicate + * reasoning / composer / onboarding live dots can adopt it in a follow-up motion + * pass — retiring their own `*-pulse` keyframes onto `maka-pulse` — and that is + * when it would be promoted to a public export, not speculatively before a second + * consumer exists. Reduced-motion suppression rides on the `motion-reduce:` * utilities (real-OS `prefers-reduced-motion: reduce`), mirroring the retired * dot's `@media` rule; the visual-smoke fixture freeze is handled by `base.css`. */ From 5b2bcf2895151b3662192c5d88737238e2113da7 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Sun, 12 Jul 2026 04:35:17 +0800 Subject: [PATCH 16/18] docs(ui): point inline barrel comments at README (true root correction) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round 14 review: 'revert + README says comments may lag' still left the stale 'second consumer' / streamVariants/LiveIndicator consumer lists in the source comments, so the conflict source survived. Real root correction: the inline comments no longer re-derive the promotion rule or track consumers (that list drifts as symbols retire, e.g. #712) — they keep their local implementation intent and point at packages/ui/README.md for the rule. README stays the single owner; dead-symbol cleanup stays a separate change. --- packages/ui/src/index.ts | 17 +++++------------ packages/ui/src/primitives/chat.tsx | 28 +++++++++------------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 529c3198e5..840109c21b 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -34,18 +34,11 @@ export * from './bot-brand-logo.js'; export * from './primitives/alert.js'; export * from './primitives/card.js'; // `markerVariants` / `streamVariants` / `toolVariants` / `LiveIndicator` are -// deliberately NOT re-exported here: they are internal styling tables / a -// single-consumer dot that the chat call sites apply via relative import, so -// keeping them off the package -// barrel preserves the governance goal — they stay renamable/removable without a -// public-API break. (Contrast `buttonVariants`, which IS public because it has -// external consumers.) `LiveIndicator` is exported to public only when the -// reasoning / composer / onboarding live dots actually migrate onto it — not -// speculatively before a second consumer exists. -// -// `previewVariants` (#332 PR4) IS re-exported: its file-diff parts have a second, -// cross-package consumer — `apps/desktop`'s `artifact-preview.tsx` — which is the -// promotion condition the off-barrel convention named, so the export is the rule. +// deliberately NOT re-exported here (internal styling tables / dots, kept +// renamable/removable without a public-API break). `buttonVariants` and +// `previewVariants` ARE re-exported (they meet the rule). The barrel promotion +// rule lives in `packages/ui/README.md` — don't re-derive it or track consumers +// here; that list drifts as symbols retire. export { Bubble, Marker, Message, previewVariants } from './primitives/chat.js'; export { formatTurnDuration } from './chat-display-helpers.js'; export type { diff --git a/packages/ui/src/primitives/chat.tsx b/packages/ui/src/primitives/chat.tsx index 06f01f1bd4..fa0e2baa72 100644 --- a/packages/ui/src/primitives/chat.tsx +++ b/packages/ui/src/primitives/chat.tsx @@ -262,17 +262,9 @@ export function Marker({ * replaces, so the cva source string IS the computed-style proof (the cascade * contract asserts the exact strings). * - * The single consumer (`ToolOutputStream`) keeps its semantic tags - * (`
` / `
` / ``) and applies these by `className` rather than
- * through a wrapper component — there is one call site, the tags differ, and the
- * literalize vehicle (this table) is what the test net asserts. `streamVariants`
- * is kept OFF the package barrel for the same reason as `markerVariants`: the
- * only consumer imports it by relative path, so the part set stays an internal,
- * freely-removable styling detail.
- *
- * The live pulse dot is NOT a part here — it moves onto the governed
- * `LiveIndicator` primitive below (animation can't be a leaf-literal, so it gets
- * a primitive + a single canonical keyframe instead of a per-feature one).
+ * Off the package barrel (applied by relative import at its call site). The
+ * barrel promotion rule lives in `packages/ui/README.md` — don't track consumers
+ * or re-derive the rule here; that list drifts as symbols retire.
  */
 const streamVariants = cva("", {
   variants: {
@@ -327,14 +319,12 @@ export { streamVariants };
  * shared motion home) plus the literal values here, verified by a keyframe
  * contract + before/after screenshots rather than the diff harness.
  *
- * It is kept INTERNAL (off the package barrel, applied by relative import like
- * `streamVariants`): the tool stream is its only consumer today. The duplicate
- * reasoning / composer / onboarding live dots can adopt it in a follow-up motion
- * pass — retiring their own `*-pulse` keyframes onto `maka-pulse` — and that is
- * when it would be promoted to a public export, not speculatively before a second
- * consumer exists. Reduced-motion suppression rides on the `motion-reduce:`
- * utilities (real-OS `prefers-reduced-motion: reduce`), mirroring the retired
- * dot's `@media` rule; the visual-smoke fixture freeze is handled by `base.css`.
+ * It is kept INTERNAL (off the package barrel, applied by relative import).
+ * The barrel promotion rule lives in `packages/ui/README.md` — don't track
+ * consumers or re-derive the rule here; that list drifts as symbols retire.
+ * Reduced-motion suppression rides on the `motion-reduce:` utilities (real-OS
+ * `prefers-reduced-motion: reduce`), mirroring the retired dot's `@media` rule;
+ * the visual-smoke fixture freeze is handled by `base.css`.
  */
 export function LiveIndicator({
   className,

From dc759558caada5d16b6bb07d3f67be2f3f222af5 Mon Sep 17 00:00:00 2001
From: AstroHan 
Date: Sun, 12 Jul 2026 04:46:27 +0800
Subject: [PATCH 17/18] docs(ui): exhaustively point all chat.tsx barrel
 comments at README

Round 15 found the prior root correction only covered 2 of the consumer/
promotion comment blocks; markerVariants/TextShimmer/toolVariants/previewVariants
still re-derived the rule or tracked consumers, and streamVariants/LiveIndicator
still assumed a call site that #712 removed. Exhaustively replace every such
block: keep local implementation intent + a short pointer to the README, drop
all consumer counts and promotion derivations. Also drop the README's 'inline
comments may lag' line (no longer needed once the comments don't re-derive).
---
 packages/ui/README.md               |  2 +-
 packages/ui/src/primitives/chat.tsx | 39 +++++++++++++----------------
 2 files changed, 19 insertions(+), 22 deletions(-)

diff --git a/packages/ui/README.md b/packages/ui/README.md
index b00934ffe9..1963f1e4ad 100644
--- a/packages/ui/README.md
+++ b/packages/ui/README.md
@@ -15,7 +15,7 @@ Four export surfaces, in the order to look:
 | `src/*.tsx` / `src/*.ts` (top-level) | Feature components + pure logic (e.g. `chat-view.tsx`, `composer.tsx`, `permission-dialog.tsx`, `session-list-panel.tsx`, plus pure helpers like `materialize.ts`, `redact.ts`, `smooth-stream.ts`). | stable |
 | `src/components.tsx` | Re-export barrel for the feature components above (ChatView, Composer, PermissionDialog, …). | stable |
 
-`src/index.ts` is the package barrel. It follows an **off-barrel convention**: some styling tables and per-surface helpers are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a **cross-package consumer or an explicit public-API need** — not merely a second in-package consumer (`attachment-file-card` has two in-package consumers, `chat-view` and `composer`, but stays off-barrel). Don't add to the barrel speculatively. This README is the source of truth for the promotion rule; inline source comments may lag it.
+`src/index.ts` is the package barrel. It follows an **off-barrel convention**: some styling tables and per-surface helpers are deliberately *not* re-exported, so they stay renamable/removable without a public-API break. A symbol earns barrel export when it has a **cross-package consumer or an explicit public-API need** — not merely a second in-package consumer (`attachment-file-card` has two in-package consumers, `chat-view` and `composer`, but stays off-barrel). Don't add to the barrel speculatively. This README is the source of truth for the barrel promotion rule.
 
 ## `data-slot` hooks
 
diff --git a/packages/ui/src/primitives/chat.tsx b/packages/ui/src/primitives/chat.tsx
index fa0e2baa72..e65ade20f1 100644
--- a/packages/ui/src/primitives/chat.tsx
+++ b/packages/ui/src/primitives/chat.tsx
@@ -128,9 +128,9 @@ export function Bubble({
  * so the lineage badge + footer action — which render as `UiButton` and can't
  * be wrapped — apply the shell via `className`; `Button` runs it through
  * `cn`/tailwind-merge last, so it wins over the button's own variant utilities.
- * It is intentionally kept OFF the `@maka/ui` package barrel (see `index.ts`):
- * the only consumers import it by relative path, so the variant table stays an
- * internal, freely-removable styling detail rather than public API.
+ * Intentionally OFF the `@maka/ui` package barrel — an internal,
+ * freely-removable styling detail. The barrel promotion rule lives in
+ * `packages/ui/README.md`.
  *
  * NOTE: `.maka-turn-thinking` (the committed-turn reasoning `
`) is * deliberately NOT migrated here. Its chrome lives in `summary::before` / @@ -262,9 +262,9 @@ export function Marker({ * replaces, so the cva source string IS the computed-style proof (the cascade * contract asserts the exact strings). * - * Off the package barrel (applied by relative import at its call site). The - * barrel promotion rule lives in `packages/ui/README.md` — don't track consumers - * or re-derive the rule here; that list drifts as symbols retire. + * Off the package barrel — an internal, freely-removable styling detail. The + * barrel promotion rule lives in `packages/ui/README.md`; don't re-derive it or + * track consumers here (that list drifts as symbols retire). */ const streamVariants = cva("", { variants: { @@ -319,9 +319,9 @@ export { streamVariants }; * shared motion home) plus the literal values here, verified by a keyframe * contract + before/after screenshots rather than the diff harness. * - * It is kept INTERNAL (off the package barrel, applied by relative import). - * The barrel promotion rule lives in `packages/ui/README.md` — don't track - * consumers or re-derive the rule here; that list drifts as symbols retire. + * Kept INTERNAL (off the package barrel) — an internal, freely-removable detail. + * The barrel promotion rule lives in `packages/ui/README.md`; don't re-derive it + * or track consumers here (that list drifts as symbols retire). * Reduced-motion suppression rides on the `motion-reduce:` utilities (real-OS * `prefers-reduced-motion: reduce`), mirroring the retired dot's `@media` rule; * the visual-smoke fixture freeze is handled by `base.css`. @@ -359,8 +359,8 @@ export function LiveIndicator({ * * `active={false}` (or reduced-motion) renders just the base text — callers * pass `active` false for settled/snap states so the sweep never runs in a - * deterministic capture. Kept INTERNAL (off the package barrel, imported by - * relative path) like `LiveIndicator` — its only consumers live in `@maka/ui`. + * deterministic capture. Kept INTERNAL (off the package barrel) — the barrel + * promotion rule lives in `packages/ui/README.md`. * * `delayed` (#646 run→done seam) holds the sweep at its resting frame for * `--duration-emphasized` (~200ms) before it starts — a purely CSS de-flicker so @@ -444,11 +444,10 @@ export function TextShimmer({ * that carries its own `motion-reduce:` guards — the dot and card need no * per-element motion utilities; the same global rules cover them as before.) * - * The single consumer (`ToolActivity`) renders a Base UI Collapsible and applies - * these by `className`. `toolVariants` is kept OFF the package barrel for the - * same reason as `markerVariants` / `streamVariants`: the only consumer imports - * it by relative path, so the part set stays an internal, freely-removable - * styling detail. + * Applied by `ToolActivity` (renders a Base UI Collapsible, applies these by + * `className`). `toolVariants` is kept OFF the package barrel — an internal, + * freely-removable styling detail; the promotion rule lives in + * `packages/ui/README.md`. * * NOTE: the args `
` keeps the shared `.maka-code` inline-code base (used by
  * Markdown / artifact previews too — out of scope); the `args` part below is only
@@ -562,11 +561,9 @@ export { toolVariants };
  *      children stay bare — matching the original descendant cascade exactly.
  *
  * Unlike the other tables, `previewVariants` IS exported on the `@maka/ui` barrel
- * (`index.ts`): the file-diff `diff` / `diff-body` / `diff-line` parts have a
- * SECOND, cross-package consumer — `apps/desktop`'s `artifact-preview.tsx`, whose
- * non-chat diff pane shared the retired `.maka-tool-diff*` shell and co-migrates
- * here. That second consumer is exactly the condition the off-barrel convention
- * named for promotion, so the export is the rule, not an exception.
+ * (`index.ts`) — its file-diff parts have a cross-package consumer
+ * (`apps/desktop`'s `artifact-preview.tsx`). The promotion rule lives in
+ * `packages/ui/README.md`.
  *
  * Preview card shells use the shared shadow-ring recipe instead of hard visual
  * borders. Dividers inside the cards remain real borders because they separate

From 66bcf23eb19278294e64e040ccede60905741e15 Mon Sep 17 00:00:00 2001
From: AstroHan 
Date: Sun, 12 Jul 2026 04:59:43 +0800
Subject: [PATCH 18/18] =?UTF-8?q?docs(ui):=20revert=20all=20source-comment?=
 =?UTF-8?q?=20edits=20=E2=80=94=20PR=20is=20docs-only=20(3=20READMEs)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

16 rounds of review showed cleaning inline source comments is a bottomless
local patch: chat.tsx has many historical consumer/call-site mentions (incl.
#712-retired streamVariants dead-code notes), and each fix surfaced an adjacent
one. Root correction per receiving-code-review: keep this PR docs-only (3 READMEs),
revert the index.ts/chat.tsx comment edits to main, and make packages/ui/README.md
the single owner of the barrel promotion rule. Source-comment cleanup and dead-
symbol removal are separate changes.
---
 packages/ui/src/index.ts            | 17 ++++++---
 packages/ui/src/primitives/chat.tsx | 55 ++++++++++++++++++-----------
 2 files changed, 46 insertions(+), 26 deletions(-)

diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index 840109c21b..529c3198e5 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -34,11 +34,18 @@ export * from './bot-brand-logo.js';
 export * from './primitives/alert.js';
 export * from './primitives/card.js';
 // `markerVariants` / `streamVariants` / `toolVariants` / `LiveIndicator` are
-// deliberately NOT re-exported here (internal styling tables / dots, kept
-// renamable/removable without a public-API break). `buttonVariants` and
-// `previewVariants` ARE re-exported (they meet the rule). The barrel promotion
-// rule lives in `packages/ui/README.md` — don't re-derive it or track consumers
-// here; that list drifts as symbols retire.
+// deliberately NOT re-exported here: they are internal styling tables / a
+// single-consumer dot that the chat call sites apply via relative import, so
+// keeping them off the package
+// barrel preserves the governance goal — they stay renamable/removable without a
+// public-API break. (Contrast `buttonVariants`, which IS public because it has
+// external consumers.) `LiveIndicator` is exported to public only when the
+// reasoning / composer / onboarding live dots actually migrate onto it — not
+// speculatively before a second consumer exists.
+//
+// `previewVariants` (#332 PR4) IS re-exported: its file-diff parts have a second,
+// cross-package consumer — `apps/desktop`'s `artifact-preview.tsx` — which is the
+// promotion condition the off-barrel convention named, so the export is the rule.
 export { Bubble, Marker, Message, previewVariants } from './primitives/chat.js';
 export { formatTurnDuration } from './chat-display-helpers.js';
 export type {
diff --git a/packages/ui/src/primitives/chat.tsx b/packages/ui/src/primitives/chat.tsx
index e65ade20f1..06f01f1bd4 100644
--- a/packages/ui/src/primitives/chat.tsx
+++ b/packages/ui/src/primitives/chat.tsx
@@ -128,9 +128,9 @@ export function Bubble({
  * so the lineage badge + footer action — which render as `UiButton` and can't
  * be wrapped — apply the shell via `className`; `Button` runs it through
  * `cn`/tailwind-merge last, so it wins over the button's own variant utilities.
- * Intentionally OFF the `@maka/ui` package barrel — an internal,
- * freely-removable styling detail. The barrel promotion rule lives in
- * `packages/ui/README.md`.
+ * It is intentionally kept OFF the `@maka/ui` package barrel (see `index.ts`):
+ * the only consumers import it by relative path, so the variant table stays an
+ * internal, freely-removable styling detail rather than public API.
  *
  * NOTE: `.maka-turn-thinking` (the committed-turn reasoning `
`) is * deliberately NOT migrated here. Its chrome lives in `summary::before` / @@ -262,9 +262,17 @@ export function Marker({ * replaces, so the cva source string IS the computed-style proof (the cascade * contract asserts the exact strings). * - * Off the package barrel — an internal, freely-removable styling detail. The - * barrel promotion rule lives in `packages/ui/README.md`; don't re-derive it or - * track consumers here (that list drifts as symbols retire). + * The single consumer (`ToolOutputStream`) keeps its semantic tags + * (`
` / `
` / ``) and applies these by `className` rather than
+ * through a wrapper component — there is one call site, the tags differ, and the
+ * literalize vehicle (this table) is what the test net asserts. `streamVariants`
+ * is kept OFF the package barrel for the same reason as `markerVariants`: the
+ * only consumer imports it by relative path, so the part set stays an internal,
+ * freely-removable styling detail.
+ *
+ * The live pulse dot is NOT a part here — it moves onto the governed
+ * `LiveIndicator` primitive below (animation can't be a leaf-literal, so it gets
+ * a primitive + a single canonical keyframe instead of a per-feature one).
  */
 const streamVariants = cva("", {
   variants: {
@@ -319,12 +327,14 @@ export { streamVariants };
  * shared motion home) plus the literal values here, verified by a keyframe
  * contract + before/after screenshots rather than the diff harness.
  *
- * Kept INTERNAL (off the package barrel) — an internal, freely-removable detail.
- * The barrel promotion rule lives in `packages/ui/README.md`; don't re-derive it
- * or track consumers here (that list drifts as symbols retire).
- * Reduced-motion suppression rides on the `motion-reduce:` utilities (real-OS
- * `prefers-reduced-motion: reduce`), mirroring the retired dot's `@media` rule;
- * the visual-smoke fixture freeze is handled by `base.css`.
+ * It is kept INTERNAL (off the package barrel, applied by relative import like
+ * `streamVariants`): the tool stream is its only consumer today. The duplicate
+ * reasoning / composer / onboarding live dots can adopt it in a follow-up motion
+ * pass — retiring their own `*-pulse` keyframes onto `maka-pulse` — and that is
+ * when it would be promoted to a public export, not speculatively before a second
+ * consumer exists. Reduced-motion suppression rides on the `motion-reduce:`
+ * utilities (real-OS `prefers-reduced-motion: reduce`), mirroring the retired
+ * dot's `@media` rule; the visual-smoke fixture freeze is handled by `base.css`.
  */
 export function LiveIndicator({
   className,
@@ -359,8 +369,8 @@ export function LiveIndicator({
  *
  * `active={false}` (or reduced-motion) renders just the base text — callers
  * pass `active` false for settled/snap states so the sweep never runs in a
- * deterministic capture. Kept INTERNAL (off the package barrel) — the barrel
- * promotion rule lives in `packages/ui/README.md`.
+ * deterministic capture. Kept INTERNAL (off the package barrel, imported by
+ * relative path) like `LiveIndicator` — its only consumers live in `@maka/ui`.
  *
  * `delayed` (#646 run→done seam) holds the sweep at its resting frame for
  * `--duration-emphasized` (~200ms) before it starts — a purely CSS de-flicker so
@@ -444,10 +454,11 @@ export function TextShimmer({
  * that carries its own `motion-reduce:` guards — the dot and card need no
  * per-element motion utilities; the same global rules cover them as before.)
  *
- * Applied by `ToolActivity` (renders a Base UI Collapsible, applies these by
- * `className`). `toolVariants` is kept OFF the package barrel — an internal,
- * freely-removable styling detail; the promotion rule lives in
- * `packages/ui/README.md`.
+ * The single consumer (`ToolActivity`) renders a Base UI Collapsible and applies
+ * these by `className`. `toolVariants` is kept OFF the package barrel for the
+ * same reason as `markerVariants` / `streamVariants`: the only consumer imports
+ * it by relative path, so the part set stays an internal, freely-removable
+ * styling detail.
  *
  * NOTE: the args `
` keeps the shared `.maka-code` inline-code base (used by
  * Markdown / artifact previews too — out of scope); the `args` part below is only
@@ -561,9 +572,11 @@ export { toolVariants };
  *      children stay bare — matching the original descendant cascade exactly.
  *
  * Unlike the other tables, `previewVariants` IS exported on the `@maka/ui` barrel
- * (`index.ts`) — its file-diff parts have a cross-package consumer
- * (`apps/desktop`'s `artifact-preview.tsx`). The promotion rule lives in
- * `packages/ui/README.md`.
+ * (`index.ts`): the file-diff `diff` / `diff-body` / `diff-line` parts have a
+ * SECOND, cross-package consumer — `apps/desktop`'s `artifact-preview.tsx`, whose
+ * non-chat diff pane shared the retired `.maka-tool-diff*` shell and co-migrates
+ * here. That second consumer is exactly the condition the off-barrel convention
+ * named for promotion, so the export is the rule, not an exception.
  *
  * Preview card shells use the shared shadow-ring recipe instead of hard visual
  * borders. Dividers inside the cards remain real borders because they separate