Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7610894
fix: name the axe dependencies the way package.json does
stevenjj33 Oct 31, 2026
1a381af
fix: record the source paths the tree actually has
stevenjj33 Oct 31, 2026
bcaf04d
fix: let the source-sync gate compare the same path on every platform
stevenjj33 Oct 31, 2026
b87c8d6
fix: give the mini-app fixture room for its socket path
stevenjj33 Oct 31, 2026
7d857b2
fix: the WebUI owner assembles the channel bridge
stevenjj33 Oct 31, 2026
df4741b
test(webui): measure the shared dialog after its entrance has settled
stevenjj33 Oct 31, 2026
e3eb465
chore(webui): record the visual baselines the baseline's own styles c…
stevenjj33 Oct 31, 2026
f7a7cc4
docs(webui): record the coverage review the current inventory produces
stevenjj33 Oct 31, 2026
48db8d8
test(webui): measure the rewind dialog against the shared dialog's ow…
stevenjj33 Oct 31, 2026
10a1a85
fix(webui): write the acceptance records where the plan directory is
stevenjj33 Oct 31, 2026
b38d47e
test(webui): measure the rewind dialog against the viewport it is cen…
stevenjj33 Oct 31, 2026
cac4d57
test(webui): find the composer's file inputs by the class they carry now
stevenjj33 Oct 31, 2026
95037c8
test(webui): find the settings controls by the hooks they carry now
stevenjj33 Oct 31, 2026
a363f0f
test(webui): measure a surface the way a reader sees it
stevenjj33 Oct 31, 2026
7cc629e
fix(webui): let the tab strip and the diff stats pass the accessibili…
stevenjj33 Oct 31, 2026
b7e8e93
chore(webui): record the surfaces the shared dialog moved
stevenjj33 Oct 31, 2026
ce6d2f8
test(webui): find the workspace tabs by the buttons they now are
stevenjj33 Oct 31, 2026
19a6aa4
feat(webui): let the settings page switch between Chinese and English
stevenjj33 Oct 24, 2026
17d6ec2
chore(webui): record the visual baselines on the baseline's Linux env…
stevenjj33 Oct 31, 2026
0103f6e
chore(webui): record the settings dialog's reviewed presentation sign…
stevenjj33 Oct 31, 2026
8d5c880
chore(webui): record the visual baselines on the repaired baseline
stevenjj33 Oct 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe('a host declared as webui', () => {
);

it(
'reports cron native and the channel bridge unsupported in its diagnostics',
'reports cron native and the channel bridge native in its diagnostics',
async () => {
const host = await bootWebuiHost();
// Phase 2's correction, observed end to end: the surface table no longer claims
Expand All @@ -106,7 +106,11 @@ describe('a host declared as webui', () => {
| Record<string, { status?: string }>
| undefined;
expect(surfaces?.cron?.status).toBe('native');
expect(surfaces?.channelBridge?.status).toBe('unsupported');
// The WebUI row of the owner policy assembles the channel service: the
// browser client's remote page carries the channel tab, so a browser
// owner has a screen to bind and manage a channel with. The surface
// table reports the bridge it assembled, which is a native one.
expect(surfaces?.channelBridge?.status).toBe('native');
},
120_000,
);
Expand Down
6 changes: 5 additions & 1 deletion packages/webui/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ the actions that already have keyboard meaning are bindable. ADR 0027 records th
scheduled-task surface: the WebUI is a scheduler host. It reaches the v2 runtime's Cron
service, arms the persisted scheduled tasks and fires them, and when the Desktop runs at the
same time the shared database decides which host acts on a given slot — the Desktop needs no
change. A new decision takes the next free
change. ADR 0028 records the shared presentation layer: the client has one home for the
foundations, the interaction primitives, the compositions and the structural patterns, every
client UI module is classified against it, and the overlays coordinate through one owner.
ADR 0029 records the settings page's language as a pack inside the settings business,
and the boundary between that and the client-wide extraction PR #33 deferred. A new decision takes the next free
number.

| Retired | Was | Why it is not in the log | Where the live facts are |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# The settings page carries its own language pack

**Status:** Accepted.
**Date:** 2026-10-24.

The settings page's copy lives in **two packs** — Chinese and English — inside the settings business.
The language is read from `mavis-locale`, the key the page already read and wrote, and a row in the
通用 page switches it. No new dependency, no client-wide i18n framework, and no other surface
changes.

This covers the settings page only. The conversation, the file panel, the workspace and the rest of
the client keep their own copy.

## What was already there

`client/features/settings/ui/SettingsModal.public.tsx` read `mavis-locale` into a `useState` that had
**no setter**, and wrote it back on every mount. The key existed, the read existed, and nothing a
user could do ever wrote a different value. The page's words were Chinese literals in the component
that rendered them.

The settings search made the gap visible. `filterSettingsTabs` matched a tab's **name and key**, so
a setting that lives on a row was only findable if the row's words happened to be the tab's words.
Asking the settings search for 「语言」 answered 「没有匹配的设置」 — the one question a language
switch would exist to answer.

## Why a pack in the settings business, and not a client-wide framework

- **The layer matrix already says where it goes.** A pack is a derivation — read, never written — so
it is a **view** module. `client/features/<business>/projection/` is a view layer, and a view
module may import another view module while a binding module may import a view module. The pack
therefore reaches both the tab registry and the dialog without a new edge, and without the
platform layer reaching down into a business.
- **The client-wide extraction is a different change.** The client's Chinese copy is roughly 8,600
characters across 77 of its 109 client files. PR #33 measured this and deliberately deferred
「多语言 (P-3)」 for exactly that reason: a full extraction is its own work, with its own cost and
its own review. This decision does the part that is small enough to be correct — an entry point,
a minimal pack, and a page that actually switches — and leaves the extraction to that change.
- **A framework would have been a dependency.** The pack is data and a lookup. Anything more would
be a dependency the browser bundle carries, for two languages and one page.

## Why Chinese is the base language

The settings page was written in Chinese, and Chinese is what a reader who has the app open sees.
So Chinese is the base pack and a key the English pack does not carry **falls back to the Chinese
text** rather than to a raw key: a half-translated pack reads as Chinese instead of as a bug. The
English pack's type is the Chinese key set, so a key it is missing is a compile error, and the
unit suite asserts the same parity again for the case where the type is satisfied by a value the
pack does not really hold.

A stored value that is not a language at all resolves to Chinese rather than to English. A user
whose storage was written by a build that never had a setter has no opinion about the language, and
the page they know is the Chinese one.

## The search has to reach a row, not only a tab

A setting is found by the words **on its row** — 「语言」, 「外观」, 「文件预览自动换行」 — and a
name is not what a reader remembers about it. `filterSettingsTabs` therefore matches the row-level
copy of each tab's page as well as the tab's own name, and it searches **both packs**: a reader who
looks for 「语言」 in an English page, or for "Language" in a Chinese one, is looking for the same
setting, and a search that knew only the current language would answer 「没有匹配的设置」 to half of
its own users.

A search that matched a row also takes the reader to the page that owns it, and marks the row. A
tab match needs no help — the sidebar shows the tab and the reader clicks it — but a row match is
only half an answer without the page.

## Constraints this decision puts on later work

- **A second business that needs a language switch copies the shape; it does not import the
settings pack.** The settings pack is the settings business's copy. The moment another business
needs one, the shared part — the locale value, the lookup, the fallback rule — is what gets
lifted into a client-wide module, and that lift is the extraction PR #33 deferred.
- **A page's own copy stays in its own component until it is translated.** The packs cover the
settings page. The 浏览器, 用量与模型, 账户 and 工作树 pages keep their literals, and the search
does not promise a row it cannot name.
- **The search matches both packs, so a language switch does not narrow the search.** Adding a
language adds text to search, not text that hides.
- **`mavis-locale` stays the key.** A second key for the same choice would leave two answers to
one question, and the one already written would be the one nobody reads.
14 changes: 7 additions & 7 deletions packages/webui/docs/verification/coverage-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ not claim. A number in this document is a number some gate produced.

| Question | Count |
| --- | --- |
| Client UI modules classified | 131 |
| Client UI modules classified | 133 |
| Modules with a pending contract decision | 0 |
| Floating layers inventoried | 22 |
| Floating layers managed by the coordinator | 21 |
| Floating layers inventoried | 23 |
| Floating layers managed by the coordinator | 22 |
| Business-authored alerts left (all exempt) | 2 |
| Motion distinct effects with a recorded reason | 24 |
| Motion distinct effects with a recorded reason | 25 |
| Accessibility findings recorded as debt | 0 |
| Accessibility exceptions | 26 |
| Accessibility exceptions | 0 |
| Accessibility manual reviews open | 0 |

## Every entry still pending, and why
Expand Down Expand Up @@ -56,11 +56,11 @@ Not verified by any browser check:
- `features/account/ui/UserMenuUsage.tsx` — borrows the dialog role for labelling only: renders role=dialog for labelling only: it is a hover/focus fly-out inside the account menu's layer and is dismissed by that menu, not by itself. Owner: the account menu. Removal: when the fly-out becomes a layer of its own.
### The accessibility scan

- 26 reviewed exceptions, each carrying rule, surface, state, target, reason, owner and removal condition in `accessibility-exceptions.json`.
- 0 reviewed exceptions, each carrying rule, surface, state, target, reason, owner and removal condition in `accessibility-exceptions.json`.

### The motion inventory

- 24 durations and one keyframe are recorded as distinct effects, each with the reason it is not one of the four families (`docs/design/motion-inventory.json`).
- 25 durations and one keyframe are recorded as distinct effects, each with the reason it is not one of the four families (`docs/design/motion-inventory.json`).

## Families whose contract is still partly a business's own

Expand Down
Loading
Loading