Desktop Material

The Material Design 3 shell

The application chrome, rewritten against design/History MD3.dc.html and assembled as one component. Md3Shell (app/src/ui/md3/md3-shell.tsx) is what App.renderApp() renders: a 56px application header, the repository tab strip band, a navigation drawer beside a 16px surface-container-low content pane, the pane header, the active destination, and four overlays that share one "only one open at a time" rule.

The shell reads nothing. It imports neither the dispatcher nor the app store — every value it renders and every action it can take arrives as a prop, which is what lets the same code path serve app.tsx and a screenshot harness with no application behind it.

Nothing the old chrome carried was deleted. The repository tab strip stays and is shown by default, because multi-repository tabs are a real capability of this fork that the contract's single-repository prototype simply never drew. The classic toolbar stays too, behind a persisted setting that also ships enabled. See Legacy chrome.

Behavior

Aspect Behavior
Destinations Eight: Changes, History, Branches, Actions, Inbox, Terminal, Agents, Repositories
Opening state History, drawer expanded, no overlay, no progress
Search fields Eleven, each with its own query and its own regex mode
Overlays Menu, regex builder, or compose dialog — one at a time
Menu kinds 23, every one filterable and every one carrying a regex builder
Toasts Rendered by Md3ToastHost, which is always mounted
State One exported shape, one exported pure reducer
Network None. The shell performs no I/O of its own

The eight destinations

Md3DestinationIds enumerates them in the drawer's order, and md3PaneDestination translates a drawer id into the pane header's own capitalized name. Each destination renders either its MD3 view — supplied through the views prop as IMd3ShellViews — or, when that entry is null, whatever renderLegacyDestination returns.

That fallback is not a placeholder. It is the application's real existing repository workspace and build runner, rendered inside the MD3 chrome, and it is what keeps every capability reachable while the eight views are wired one at a time. md3NoViews is the shape a host starts from, with all eight unhandled.

Switching destination closes whatever overlay was open: the contract's menus act on the destination they were opened from, so leaving one up over a different pane would offer commands for a surface that is no longer showing.

The navigation drawer

Md3NavigationDrawer renders the destinations built by md3Destinations, which takes the per-destination badge counts and the active id. The drawer expands and collapses, carries the active repository's name, owns the compose entry point, and right-click opens drawerMenu.

Its labels are text, not icons alone. The tabs point at the pane through aria-controls, and the pane owns a visually hidden <h1> (Md3ShellHeadingId) that takes focus on every destination change — the visible pane title is a <span> inside the pane header, so without it a keyboard user who switched destination would be stranded on a control that no longer exists.

The application header

Md3AppHeader carries the brand, the commit-and-push pill, the global search field, and the action row: command palette, notifications with an unread badge that caps at 99+, theme toggle, settings, and the account avatar.

Two deliberate departures from the literal contract:

The pane header

Md3PaneHeader shows the destination's glyph and title, the repository and branch breadcrumbs, the fetch and push controls, the progress bar, and the pane menu button. Two contract predicates decide what appears:

Progress is number | null. null removes the bar entirely rather than rendering an empty track, and progressLabel names the operation — "Fetching origin", "Pushing 3 commits" — because that label is what a screen reader announces when the bar appears. Reported percentages are clamped into the track.

The eleven search fields

Md3SearchFieldKeys names them: global, history, changes, branches, actions, logs, inbox, terminal, agents, repositories, diffSearch. The list is written out by hand rather than derived from the state object, because a derived list validates whichever fields happened to survive and says nothing about one that went missing.

Each field is genuinely independent: its own value, its own regexEnabled, and its own regex-builder target. md3SearchBinding(state, dispatch, field) returns one field's six props in the exact shape the views ask for — IMd3ActionsSearch and IMd3TerminalSearch are structurally this — so a host binds a view's search by spreading rather than writing six closures per field.

The menu system

getMenuSpec(kind, context, handlers) builds any of the 23 menu kinds: palette, settings, account, repoMenu, branchMenu, paneMenu, listMenu, diffOptions, fileMenu, rowMenu, changesMenu, changeRowMenu, branchRowMenu, runMenu, repoRowMenu, compose, agentAccess, inboxRowMenu, agentRowMenu, terminalMenu, drawerMenu, searchMenu and guide.

Md3MenuOverlay renders one: a scrim, a panel with the menu's glyph, title and close button, a filter row carrying the .* regex toggle and the anchored builder launcher, and a scrolling list of actions. Filtering is a case-insensitive substring match by default and a case-insensitive regular expression when regex mode is on. An uncompilable pattern leaves the list whole rather than emptying it while somebody is halfway through typing (foo, and the field additionally says why nothing is being filtered.

The shell wraps two things around every spec:

Four of the eight menu handlers are overridden by the shell, because they are shell state a host cannot reach: onNavigate, onToggle('drawer'), onOpenMenu and onOpenRegexBuilder. The host's handler still runs after, so it can record, notify or act on the same event.

The regex builder, everywhere

Md3RegexBuilderDialog opens from every search field and from every menu's own filter row. It offers the four contract token groups verbatim — anchors (^, $, \b), classes (\w, \d, \s, [a-z], [^x], .), quantifiers (+, *, ?, {2,4}) and groups ((…), (?:…), a|b, (?=…), (?<=…)) — a raw pattern editor, the six flags, sample text, and a live result in three states: idle, matched, failed.

This is a presentation of the builder the application already has, not a second one. Every pattern compiles through compileSafeRegex, the vetted RE2 adapter in lib/safe-regex.ts, which is linear-time by construction. The contract evaluated with new RegExp, which is exactly the native engine this repository forbids for user-authored patterns, so the six flags are mapped onto RE2 instead: i is RE2's own case-insensitivity, m and s become the zero-width inline groups (?m) and (?s), y is applied as JavaScript's match-at-lastIndex rule, g is ignored by the tester because it never changes whether a string matches (it is still carried into the applied /pattern/flags string), and u is a no-op because RE2 is unconditionally Unicode-aware.

Two of the contract's tokens describe constructs RE2 does not implement: (?=…) lookahead and (?<=…) lookbehind. They ship exactly as the contract lists them, and inserting one produces an honest Invalid pattern: … from the live tester rather than a silently missing button.

Where a built pattern goes is the part that has to be exactly right. Md3BuilderTarget is either {kind:'search', field} or {kind:'menu', menu}, and applying a pattern:

The dialog is keyed per target, so one builder's pattern, flags and test string cannot bleed into the next.

The compose dialog

Md3ComposeDialog is the commit composer: summary, description, the included and total file counts, the branch it will land on, and the commit and commit-and-push actions. The shell owns whether it is open and supplies onDismissed; everything else is the host's compose prop, which in the application reads and writes the real changesState.commitMessage through setCommitMessage and commits through commitIncludedChanges or oneClickCommitAndPush.

The toast

Md3ToastHost is always mounted. notify(message, options) raises one — info, success, warning or error — and dismissToast(id) removes it. The default duration is 3000ms. These are the shell's non-blocking notifications; a modal is reserved for a decision the user must make before continuing.

What a menu item actually does

app/src/ui/md3/md3-menu-bindings.ts binds every Md3MenuCommand the contract can emit to a real application route. The design prototype's items call toast() and stop, because nothing was behind the prototype; each one here reaches the surface the app already owns.

The bindings are a Record over the command union rather than a switch with a default, so a command added to the union with no binding is a compile error rather than a menu row that silently does nothing. A second Record, Md3MenuCommandRoutes, records how each command is bound and why:

Route Meaning
menu Runs the application's own main-menu command, which is what opens the existing Create branch, Create tag, Rename branch, Merge, Rebase, Clone, Add local repository and Repository settings dialogs. A second dialog is never built for a command that already has one.
direct A dispatcher call or an existing popup, opened from the binding itself.
reveal The command acts on a row — a notification, an agent session, a workflow run, a terminal buffer — that only the destination view owning that list can identify. Until that view supplies the row, the command takes the user to the surface that owns it.

IMd3MenuRowContext is where a row-scoped command gets its identity: App fills in what the app store genuinely knows and leaves the rest undefined, because the notification centre and the agent-session panel own their selections privately. A destination view supplies its own row as it is wired, and the command starts acting on the row instead of revealing the list.

Three commands never reach a host at all. toggleSearchRegexMode, clearSearchField and showRegexGuideEntry act on the global search field's value, its regex mode and which overlay is open — all shell state — so Md3Shell performs them before the host's onCommand runs, exactly as it already does for navigation, the drawer toggle, opening another menu and opening the builder.

Presentation preferences

Six of the contract's menu rows flip a value that decides how a destination draws itself, and each shows its live value as the row's hint. None of the six had an owner in the app store, so they live in app/src/lib/md3-view-preferences.ts: persisted through the same local-storage boolean/number store every other UI preference uses, read back by the menu context so each hint states the real value, and broadcast on a window event so every mounted surface updates together.

They are the commit sort order, grouping commits by day, the commit graph column, wrapping long diff lines, the diff context-line count (1–20, stepping by three and wrapping at the maximum, because the contract offers an increase with no matching decrease) and grouping the changes list by folder.

In the command palette

Ctrl+Shift+F reaches every surface the shell introduced. Each of the eight destinations teleports to its own drawer tab rather than to the drawer: landing on the rail and leaving the reader to find the row is the "general page" outcome a teleport exists to avoid. The header's search, palette chip, bell, theme, settings and account controls, the drawer's compose button and repository chip, and the pane header's repository, branch, fetch, push and overflow controls each have a teleport target of their own.

The teleport hooks are the class names and data-destination-id attributes the shell already renders for its own layout and roving tab list, rather than anchors added for the palette — a hook with another reason to exist cannot rot into a dead selector without something else breaking first. Accessible names are deliberately not used as selectors: they are localized, so a selector built on one stops matching the moment the language mode changes.

Every value the shell owns renders its live control inline in the palette row — the global search's regex mode, the drawer's expanded state, and all six presentation preferences — reading and writing the same place the menu hint does, so the two cannot disagree.

Legacy chrome

This is a deliberate product decision, not a transitional state.

The repository tab strip

The existing RepositoryTabStrip is rendered unchanged, between the header and the shell body, and is shown whenever showRepositoryTabStrip is not false. Multi-repository tabs are a real feature of this fork; the design contract prototypes one repository, which is a limit of the prototype rather than a decision about the product.

"Show the classic toolbar"

Aspect Behavior
Default On (ShowClassicToolbarDefault = true)
Storage show-classic-toolbar in local storage, via the shared getBoolean/setBoolean helpers
Where Settings → Appearance, beside the dialog-emoji row
Live ShowClassicToolbarChangedEvent on window; the shell updates without a restart
Provenance Stated beside the control, naming the real value

The MD3 shell moves the repository, worktree, branch, sync and build-run controls into the pane header and the pane menu. The band that carried them is kept rather than retired, and the setting ships enabled: a user who has learned where those controls are does not have to relearn them because the chrome around them changed.

Turning it off loses nothing, and that is the condition this setting is allowed to exist under. Every action the band offered is also on the pane header's fetch and push controls or in the pane menu. A toggle that hid the only route to a capability would be a feature removal wearing a checkbox.

app/src/lib/classic-toolbar.ts owns the preference. It uses the same local-storage boolean store every other UI preference uses — not a second store — and getShowClassicToolbarProvenance() reports whether the live value is a recorded choice ('stored') or the compiled-in fallback ('default'), which the settings row states in words rather than printing the opaque label "default".

The forty-four carried-over capabilities

The eight destination views are faithful to the contract, and the contract is a prototype of one repository with one of everything. The surfaces this fork already shipped carry more: a compare-to-branch picker, an unreachable-commits dialog, four Actions manager tabs, the full native file context menu, eleven further branch row actions, the repository list menu, the new-agent-session form. A rewrite that simply followed the contract would drop every one of them.

app/src/ui/md3/md3-shell-carryover.ts enumerates all forty-four by hand, each with the menu kind it now lives in, its icon, its localized label and whether it is destructive. A catalogue derived from whatever a host happened to wire would validate the entries that were there and say nothing about the ones that had gone.

Destination Lands in
History: compare-to-branch listMenu
History: unreachable commits rowMenu
Actions: workflow manager, workflow catalog, cache manager, self-hosted runner manager, refresh, run count, pane divider paneMenu
Actions: jump to attempt, log match navigation, log group collapse runMenu
Changes: discard, permanently discard, stash, ignore folder, copy relative path, copy selected paths, open with default program, Cheap LFS pin, include/exclude selected files changeRowMenu
Changes: discard all, permanently discard all, stash all changesMenu
Branches: merge and delete, compare, copy name, pin, hide, solo, restore visibility, checkout in new worktree, switch to worktree, view on forge, view pull request on forge branchRowMenu
Branches: sort by name, sort by recent, show pull requests, fetch remote branches, restore all, bulk delete listMenu
Repositories: repository list context menu repoRowMenu
Agents: new session paneMenu

buildMd3CarryOverExtensions(handlers, hints) turns supplied handlers into the shell's menuExtensions. A command with no handler produces no item at all rather than a row that does nothing, and md3UnplacedCarryOverCommands(handlers) names those omissions so a host or a test can fail on a gap instead of discovering it in use.

Seven items are flagged destructive — discard, permanently discard, discard all, permanently discard all, merge-and-delete and bulk delete among them — so a host routes them through the shared destructive-action gate rather than deciding per call site.

Configuration

The shell is controllable or self-owning. Supply state and onStateChange to own the state — which a host must do to build the destination views' search props from the same eleven fields — or supply neither and let the shell keep its own, seeded by initialState.

createMd3ShellState(overrides?) builds a fresh state. md3ShellReducer(state, action) is pure: every Md3ShellAction produces a new state and touches nothing else, so a test can replay a sequence and assert the result without rendering anything.

const [state, setState] = React.useState(createMd3ShellState())
const next = md3ShellReducer(state, {
  type: 'open-builder',
  target: { kind: 'search', field: 'history' },
})

The actions are select-destination, toggle-drawer, set-drawer, set-search, clear-search, toggle-search-regex, set-search-regex, open-menu, open-builder, apply-builder, open-compose, close-overlay and set-progress.

Styling

Layout lives in app/styles/ui/_md3-shell-layout.scss; everything shared with the views is in _md3-shell.scss and the per-component partials. Three constraints the contract could not simply be copied into:

Languages and tone

Every string the shell renders comes from app/src/lib/i18n-resources.ts under the md3. namespace, so all three language modes and both funny-level sliders reach it like any other copy. Facts stay exact at every level: the destination name, the branch, the ahead count, the progress label and the regex pattern are never restyled into ambiguity.

New keys are namespaced md3.<surface>.<thing> deliberately. A key that collides with an existing surface silently renders the other surface's words and nothing fails, so a collision check is part of adding one.

Failure modes

Situation Behavior
No repository selected The pane header renders with empty repository and branch names; fetch and push do nothing rather than throwing
Destination id not recognized isDestinationId rejects it and no navigation happens
A view is null renderLegacyDestination supplies the real existing surface
Regex pattern will not compile The menu list stays whole and the field says why
Progress reported as NaN or out of range Clamped to 0–100; null removes the bar
apply-builder with no builder open The reducer returns the state unchanged
Local storage unavailable The classic toolbar falls back to its shipped default and reports provenance 'default'
A carried-over command has no handler No menu row is rendered, and md3UnplacedCarryOverCommands names it

Security considerations

The shell holds no credentials, opens no network connection and reads no file. Regex evaluation in the builder runs through the renderer-safe RE2 evaluator rather than a raw RegExp on user input, so a catastrophically backtracking pattern cannot hang the renderer. Menu filtering does use new RegExp on the filter text, but only against the menu's own short in-memory labels, and an uncompilable pattern is caught and reported rather than thrown.

The classic-toolbar preference is a single boolean in local storage. It touches no path, token or user content, and no value derived from it ever reaches a command line.

Verification

Known gap

The forty-four carried-over capabilities are catalogued and their menus are decided, but no handler is wired from app.tsx yet, so buildMd3CarryOverExtensions is not called and none of them renders in a menu today. That is not a capability loss: views is md3NoViews, so every destination renders the real repository workspace and build runner, and the classic toolbar and repository tab strip are both on by default — every one of those capabilities is reachable exactly where it always was. They become unreachable only when a destination's MD3 view replaces the legacy surface, which is why the catalogue is a required-shape module rather than prose: the change that wires a view supplies that view's handlers, and md3UnplacedCarryOverCommands fails loudly on anything still missing.

Two entries additionally need a decision nobody has made: log match navigation and log group collapse. The contract filters the log rather than dimming it, which may supersede match-stepping. Both are catalogued into runMenu so they survive; if the Actions view concludes the filter genuinely replaces match-stepping, that needs a retired record in the ledger with that reason. Neither has been retired.

Suggested articles


Material Design 3 外殼

成個應用程式嘅框架,照住 design/History MD3.dc.html 重寫,砌成一個組件。 Md3Shellapp/src/ui/md3/md3-shell.tsx)就係 App.renderApp() render 嗰嚿嘢: 56px 頂部列、儲存庫分頁列、側邊導航加一個 16px surface-container-low 內容窗、 內容標題列、目前嘅目的地,再加四個「一次淨係開一個」嘅浮層。

個外殼自己乜都唔讀:唔 import dispatcher,亦都唔 import app store。佢 render 嘅每 一個值、做得到嘅每一個動作,全部由 prop 入嚟 — 所以同一段程式碼,app.tsx 同一個 完全冇應用程式喺後面嘅截圖工具都用得。

舊嘅框架一樣都冇拆。 儲存庫分頁列照留、預設照顯示,因為多儲存庫分頁係呢個 fork 真有嘅功能,個設計原型淨係畫咗一個儲存庫,係原型嘅限制,唔係對產品嘅決定。經典工具 列都照留,收喺一個會記住、而且預設係開嘅設定後面。

行為

項目 行為
目的地 八個:Changes、History、Branches、Actions、Inbox、Terminal、Agents、Repositories
開頭狀態 History、側邊導航展開、冇浮層、冇進度
搜尋欄 十一個,每個有自己嘅字同埋自己嘅 regex 掣
浮層 選單、regex builder、撰寫對話框 — 一次一個
選單種類 23 種,每種都篩得,每種都有 regex builder
Toast Md3ToastHost 長開
狀態 一個匯出嘅形狀,一個匯出嘅純 reducer
網絡 冇。外殼本身唔做任何 I/O

八個目的地各自 render 自己嘅 MD3 檢視,或者當嗰格係 null 就 render renderLegacyDestination。嗰個唔係佔位符 — 係應用程式本身真嘅儲存庫工作區同 build runner,包喺 MD3 框架入面,就係佢令到八個檢視逐個接嘅時候,冇一樣功能揾唔返。

十一個搜尋欄各有各嘅字同各有各嘅 regex 掣。喺邊個欄開 builder,套用個 pattern 就只 寫返嗰個欄、只開嗰個欄嘅 regex:淨係寫字唔開掣,就會變成搵嗰堆符號本身,而呢個正正 係佢要防嘅嘢。選單嗰邊就會連個 pattern 一齊開返嗰個選單,唔使人手抄多次。

選單item真係做嘢

md3-menu-bindings.ts 幫合約入面每一個 Md3MenuCommand 接返應用程式真有嘅路。原 型嗰邊每個 item 都係 toast() 完就算,因為原型後面根本冇嘢;呢度每一個都去返應用程 式本身嗰個介面。

佢係一個蓋住成個 union 嘅 Record,唔係有 defaultswitch — 加咗個新指令又 唔接線,係編譯行唔到,而唔係個選單靜靜雞乜都唔做。另一個 RecordMd3MenuCommandRoutes)會寫低每個指令行邊條路、點解:menu 係行應用程式本身嗰個 主選單指令(開返原有嘅新增分支、新增 tag、改名、合併、rebase、clone、加本機儲存庫、 儲存庫設定對話框,唔會再整多個);direct 係直接叫 dispatcher 或者開返原有嘅 popup;reveal 係嗰個指令要做嘅嘢綁住一行資料(一個通知、一個 agent 對話、一個 workflow run、一個終端機畫面),而淨係擁有嗰個清單嘅目的地檢視先知係邊行 — 所以喺 嗰個檢視接線之前,個指令會帶你去擁有嗰個清單嘅介面。

有三個指令永遠去唔到 host:toggleSearchRegexModeclearSearchFieldshowRegexGuideEntry 改嘅係全域搜尋格嘅字、佢個 regex 掣、同而家開緊邊個浮層 — 全部 都係外殼自己嘅狀態,所以 Md3Shell 喺 host 之前自己做咗,同佢一路以嚟處理導航、側邊 導航開關、開另一個選單、開 builder 嘅做法一樣。

呈現設定

合約有六行選單係改「點畫」而唔係「做乜」,每行仲要用而家嘅值做提示。六個喺 store 度 都冇主人,所以佢哋住喺 md3-view-preferences.ts:同其他 UI 設定用同一個 local storage(唔係第二個 store),選單 context 讀返嚟做提示,改動會廣播一個 window event,令每個掛住嘅介面一齊更新。六個係:commit 排序、按日子分組、commit 圖、長行自動 換行、diff 上下文行數(1 至 20,一步三行,去到頂就轉返最細,因為合約淨係有「加」冇 「減」),同埋改動清單按資料夾分組。

喺指令板入面

Ctrl+Shift+F 去得到外殼新加嘅每一個介面。八個目的地各自跳去自己嗰個側邊導航 分頁,唔係跳去成條側邊導航 — 跌落一條 rail 度叫人自己揾,正正就係「跳轉」要避開嗰件 事。頂欄嘅搜尋、指令板貼士、通知鐘、主題、設定、帳戶,側邊導航嘅撰寫掣同儲存庫牌, 版面頂欄嘅儲存庫、分支、fetch、push、overflow,全部各有自己嘅跳轉目標。

啲跳轉靠嘅係外殼本身為咗排版同 roving tab list 已經 render 咗嘅 class 同 data-destination-id,唔係為咗指令板另外加嘅錨 — 一個本身就有用途嘅鈎,冇可能靜靜雞 爛咗都冇嘢出聲。無障礙名就刻意唔攞嚟做 selector:嗰啲名係會翻譯嘅,語言一轉就即刻揾 唔到嘢。

外殼擁有嘅每一個值,喺指令板嗰行直接畫返個真控制項出嚟 — 全域搜尋嘅 regex 掣、側邊 導航開合,同六個呈現設定 — 讀寫嘅位同選單提示一模一樣,所以兩邊唔會講唔同嘅嘢。

舊框架

「顯示經典工具列」喺設定 → 外觀,預設係開,存喺 local storage 嘅 show-classic-toolbar,改完會即刻生效(唔使重開)。閂咗都唔會少咗嘢:嗰條列上面每 一個動作,內容標題列或者選單都做得到 — 一個會收埋唯一入口嘅開關,其實就係扮成 checkbox 嘅功能刪除。開關側邊嗰句會照直講清楚而家個值係喺呢部電腦記錄過,定係用緊 出廠設定,而且會講返真正嘅值。

設計冇畫過、但係呢個 fork 本身有嘅四十四樣功能,逐樣手寫喺 app/src/ui/md3/md3-shell-carryover.ts,寫明將來住邊個選單、用邊個圖示、叫咩名、 係咪破壞性。冇 handler 嘅命令唔會 render 出一行做唔到嘢嘅嘢,而係由 md3UnplacedCarryOverCommands 點名,等人或者測試可以直接紅,唔使等用家撳落去先發現。

設定

外殼可以受控(同時畀 stateonStateChange)或者自己揸自己嘅狀態。 md3ShellReducer 係純函數,所以測試可以直接播一連串動作再對答案,唔使 render 任何 嘢。

樣式方面有三個唔可以照抄嘅位:合約用 dmUp / dmSheet 呢兩個 keyframe,呢度四十 個 partial 都用緊同名但唔同幾何嘅版本,照抄會靜靜雞改晒全部;合約用 [data-theme] 分主題,呢度係 :rootbody.theme-dark,照抄會得出一份完全正確、行得通、但係 邊度都唔生效嘅樣式;合約用 title 屬性做提示,而 title 係被 lint 禁咗嘅,所以全 部改成真 tooltip,每粒淨圖示嘅掣都有自己嘅無障礙名。

所有文字都喺 md3. 開頭嘅 i18n key 入面,三種語言模式同兩支搞笑程度滑桿照樣覆蓋到; 但係目的地名、分支、領先數目、進度說明同 regex pattern 呢啲事實,任何等級都唔會被 語氣改到含糊。新 key 一定要 md3.<surface>.<thing> 咁改名 — key 撞名嘅話會靜靜雞 render 咗另一個畫面嘅字出嚟,而且一滴紅都冇。

失敗情況

冇揀儲存庫:標題列 render 空白名,fetch/push 咩都唔做而唔係拋錯。認唔到嘅目的地 id:直接唔navigate。Regex 砌唔成:個清單保持完整,同時講返點解冇篩到。進度值超範圍 或者 NaN:夾返落 0–100,null 就直接冇咗條進度條。冇 local storage:經典工具列 用返出廠值,來源報 'default'

保安考量

外殼冇拎住任何憑證、唔開網絡、唔讀檔案。Builder 嘅 regex 係行 RE2 評估器,唔係對用 家輸入直接 new RegExp,所以災難性回溯搞唔死 renderer。選單篩選係有用 new RegExp, 但淨係對住選單自己嗰啲好短嘅記憶體標籤,而且砌唔成會接住報返,唔會拋出嚟。

驗證

app/test/unit/md3-contract-conformance-test.ts 由設計檔行落去質問實作, app/test/unit/feature-ledger-test.ts 由凍結咗嘅盤數質問棵樹 — 兩邊都係由清單指住 程式碼,唔係掉轉頭問「你有嘅嘢啱唔啱格式」,因為後者連乜都冇都會照過。八個檢視各自 有自己嘅測試。Typecheck、ESLint、Prettier 全部乾淨。

已知未做

四十四樣接手功能已經編好目錄同揀好選單,但係 app.tsx 一個 handler 都未接,所以今 日一個都唔會喺選單度出現。呢個唔係少咗功能:viewsmd3NoViews,即係每個目的地 都仲係 render 緊真嘅儲存庫工作區同 build runner,經典工具列同儲存庫分頁列亦都預設開 住 — 每一樣都仲喺原本嗰個位。要到某個目的地嘅 MD3 檢視真係取代咗舊畫面,先至會唔見; 所以份目錄係一個有形狀要求嘅模組而唔係一段文字。另外「log 前/後一個符合」同 「log 群組摺埋」兩樣需要一個仲未有人做嘅決定,兩樣都收咗喺 runMenu 度保住,一樣都 冇退役。