The client tsdown presets found a package's own manifest by name: a glob of
every packages/*/*/package.json, parsed until the name matched. tsdown's
native config loader imports each package config without module cache, so
the module-level cache never hit and all 82 configs repeated the glob
(40-125 ms each) synchronously on tsdown's main thread, where it delayed
every other package build. It was 34.5% of the client pass's main thread.
- clientBundle, staticLinked and clientLibrary take the package directory
(import.meta.dirname at every call site) and read <dir>/package.json
once; the package name, loader id and externals come from that manifest.
- A missing, unreadable or nameless manifest throws an actionable error.
- The glob, the name search and the module-level caches are removed.
tsdown client pass 12.78 s -> 8.73 s (median of 3 interleaved runs each,
-31.7%); all 11,566 lib outputs byte-identical; all 82 configs resolve the
same externals and banners.
Refs #5852
TypeScript builds a synthetic signature for every JSX intrinsic element and
computes a module specifier for @types/react (index and jsx-runtime) per
importing file. computeModuleSpecifiers visits every symlinked location of
that file and scans all 491 tsconfig paths for each; pnpm gave 65 packages
their own node_modules/@types/react link. Client test-aggregate Check
followed 26.9 + 0.494 * N seconds for N locations.
- Remove @types/react and @types/react-dom from all 67 workspace manifests
and declare them once in the root devDependencies; packages keep declaring
the react and react-dom they import (ui-slots gains react).
- verify-package-dependencies rejects either package in any workspace
manifest and requires the root declaration, so a new package cannot add
the link back.
- packages/client/AGENTS.md states the rule.
Client aggregate Check 59.1 s -> 26.5 s; the 76 client-only package
projects 54-57 s -> ~44 s wall. File set (2,613) and diagnostics unchanged.
Refs #5852
Delete @deepseek-ai/dsh-invariants, every package's ./invariant companion
and its tests, the invariant gates and Vitest setup, the scoped-event
resolver generator, and the INVARIANT-coded listener rethrow paths.
Update docs, READMEs, Agent Notes, and add an upgrade guide.
Only the bilingual pairing records conflicted. Master moved Agent Note records to
per-heading hashes, so this branch's note record was re-recorded in that format;
the ui-primitives and ui-theme README records were re-recorded unchanged. The
stylesheet corpus spec still passes over master's new client sheets, so no ring
needed realigning this time.
* feat(i18n): key translation pairing records by heading section
Replace the whole-file blob hashes in *.i18n.yaml with one entry per
heading section that still holds language-specific content, keyed by the
English heading-slug path. Blocks identical across the pair (generated
table rows, code blocks, shared regions) stay out of the hash, so edits to
different sections and identical regenerations merge with Git's default
text merge, including on GitHub.
Remove the dsh-translation-pairing merge driver, its resolver command and
installer wiring; gen-translation-brief recovers last-confirmed text from
commit history. Migrate all active records, archive the superseded Agent
Note, and record the replayed conflict reduction in the new one.
* test(i18n): refresh translation prompt snapshot for the terminology note
* feat(i18n): exclude only code blocks and generated regions from pairing records
Replace byte-equality masking with an explicit rule: a record hash skips
fenced code blocks and BEGIN/END GENERATED regions, the two kinds of
content the pairing gate already requires to be identical on both sides.
Move generated data into regions written into both pages:
gen-config-catalog renders both languages and the record with per-package
regions labelled by code identifiers; gen-module-graph keeps its table in a
region; gen-doc-graphs splices the event matrix regions into the Chinese
page. Add a generic region splice beside the region grammar.
ui-theme/README.md, README.zh.md and README.i18n.yaml conflicted again: master
extended the `design-platform.css` sentence with the `--dsw-alias-file-diff-*`
palette. Kept that sentence together with this branch's `focus.css` paragraphs
and the seven-sheet entry list, then re-recorded the pairing hashes.
Conflicts and how they were resolved:
- ui-agent-preset/AgentPresetSection.module.css: master replaced the icon-only
tooltip actions (`.iconButton`, `.iconDanger`, `.revealedPath`) with
`.cardHelp` + `.helpButton`, which deleted the one declaration this branch
edited. Took master's structure and re-applied the branch's change to the two
`.cardMain:focus-visible` rings, which master kept unchanged.
- ui-theme/README.md and README.zh.md: master documented the separately exported
`brand-font.css`; this branch documented `focus.css`. Kept both, with the
dynamic entry's sheet list at seven sheets including `focus.css`.
`README.i18n.yaml` re-recorded with `verify-translation-pairing --write`.
Pointer suppression previously cleared `outline-color` on the focused element
alone, so a ring painted by a descendant, a pseudo-element, or a `box-shadow`
stayed visible, and editable text controls lost the focus feedback they need on
click.
Ring colour now flows through `--dsw-focus-ring-color`, which component outlines
and focus-ring shadows read alongside the brand token. Every ring that inherits
the variable is suppressed together, and `body :focus-visible:not(:read-write)`
limits the suppression to rings while editable controls keep their own feedback.
Keyboard modality returns on Tab, Home, End, PageUp, or PageDown, and on focus
reaching a different control after a non-composing key. IME composition keys,
refocusing the same control, and window blur leave rings silent. The desktop
preload menubar suppresses its own ring inside its shadow root, where the
document-level attribute does not reach.
The Plugins page spec's configuration bodies take the view positionally
as master's fixture does, the companion and section specs read a slot
entry's injected face through a Pick of the members they assert instead
of a double assertion, and the no-unknown-casts baseline drops the
entries retired with the moved settings tests.
Every ring that used 2px now reads --dsw-focus-ring-width. The dense tables and
toolbars keep their 1px ring on purpose: at 2px they visibly outweigh the rows
they sit between, so the standard value and the dense exception stay distinct
rather than converging on a number nobody measured.
Focus rings named eight different colours across 54 client stylesheets, and a
control that declared no ring fell back to the browser default, whose colour
follows the operating-system accent — so the same build showed orange on one
machine and blue on another.
Every keyboard focus ring now uses --dsw-alias-brand-primary. A global ui-theme
focus.css sheet reaches the controls that declare no ring of their own, and 49
declarations name the same token. Only the colour changed: each declaration
keeps its width, style and offset.
Each official settings page now lives in a client package of its own:
`ui-settings-shell`, `ui-settings-agent-loop`, `ui-settings-subagent`
and `ui-settings-web-search` register into `plugins.item` while the Host
serves their namespaces, through the new `settingsScope.whileServed`
watch, each with its own dictionary and styles. The form model, the
fields and the form move to `ui-primitives` as the shared settings kit,
so a community package's browser half builds a page the same way.
`ui-settings-plugins` keeps only the Built-in plugins section shell
around the inventory tab.
The companions hold Loader rows in `web-app` behind the section; the
client catalog, config catalog and module graph regenerate; the
settings-card cookbook walks through a companion package.