feat(theme): canonical theme/namespace lists + ship Summer Night (T-371, T-478)

The bundled-theme list and the Tier-0 i18n namespace list were each
hand-maintained in 3+ places that had drifted: the testmode harness loaded
8 themes (catppuccin silently unvalidated) while the app and contrast gate
loaded 10, and the i18n gate checked 4 of the shipped namespaces. Export
one canonical const each — kBundledThemePaths and kTier0Namespaces — and
have the app, the testmode harness, and the a11y gates iterate them.

Drift-proof meta-assertions:
- contrast gate fails if any theme YAML on disk is absent from
  kBundledThemePaths (so a theme can't sit unvalidated).
- i18n gate derives its subjects from assets/i18n/en_us/ at test time and
  asserts en/nl key parity for every shipped catalog (26, was 4), plus that
  every kTier0Namespaces entry has a shipped catalog.

Surfaced summer-night.yaml: a legacy-ported palette on disk, unbundled and
never contrast-checked. Per the maintainer's call, ship it: flesh the
palette to full token + syntax coverage (honouring the v1.2 colours, clide
derivation pattern for the new keys) and add a structurally identical
summer-night-hc sibling that clears the strict extended gate. Both pass.

No licenses.yaml change: summer-night is clide's own (ported from legacy
clide v1.2 under legacy/, MIT); the only third-party palette, Catppuccin,
is already acknowledged; no fonts added.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-24 15:31:16 +02:00
co-authored by Claude Opus 4.8
parent 1e49f3e1d9
commit b7db22924d
12 changed files with 246 additions and 102 deletions
+5 -31
View File
@@ -393,7 +393,7 @@ Future<void> main() async {
appDir: appDir,
bundledThemes: themes,
i18nLoader: AssetCatalogLoader(bundle: rootBundle),
preloadNamespaces: _tier0Namespaces,
preloadNamespaces: kTier0Namespaces,
// Languages the UI can switch to (Settings → Appearance, T-462). Each needs
// an assets/i18n/<locale>/ catalog folder; root_shell applies the persisted
// app.locale on boot.
@@ -537,39 +537,13 @@ Future<Directory> _resolveAppDir() async {
Future<List<ThemeDefinition>> _loadBundledThemes() async {
const loader = ThemeLoader();
const paths = [
'lib/kernel/src/theme/themes/clide.yaml',
'lib/kernel/src/theme/themes/midnight.yaml',
'lib/kernel/src/theme/themes/paper.yaml',
'lib/kernel/src/theme/themes/terminal.yaml',
'lib/kernel/src/theme/themes/clide-hc.yaml',
'lib/kernel/src/theme/themes/midnight-hc.yaml',
'lib/kernel/src/theme/themes/paper-hc.yaml',
'lib/kernel/src/theme/themes/terminal-hc.yaml',
'lib/kernel/src/theme/themes/catppuccin-mocha.yaml',
'lib/kernel/src/theme/themes/catppuccin-mocha-hc.yaml',
];
final out = <ThemeDefinition>[];
for (final p in paths) {
for (final p in kBundledThemePaths) {
out.add(await loader.fromAsset(rootBundle, p));
}
return out;
}
/// Every Tier-0 extension that ships an i18n catalog. Extensions
/// registered but not active (the 17 stubs) don't preload — their
/// catalogs load lazily on activate in later tiers.
const List<String> _tier0Namespaces = [
// Framework chrome that lives outside any extension (lib/widgets, lib/kernel,
// shared reader chrome) resolves under the 'core' namespace (T-469).
'core',
'builtin.default-layout',
'builtin.welcome',
'builtin.ipc-status',
'builtin.theme-picker',
'builtin.terminal',
'builtin.files',
'builtin.claude',
'builtin.editor',
];
// The Tier-0 i18n namespace list now lives in
// lib/kernel/src/i18n/tier0_namespaces.dart as `kTier0Namespaces`, the single
// source of truth shared with the i18n coverage gate (T-371).