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:
@@ -45,6 +45,7 @@ export 'src/focus.dart';
|
||||
export 'src/i18n/catalog_loader.dart';
|
||||
export 'src/i18n/fallback_chain.dart';
|
||||
export 'src/i18n/i18n.dart';
|
||||
export 'src/i18n/tier0_namespaces.dart';
|
||||
export 'src/net.dart';
|
||||
export 'src/notify.dart';
|
||||
export 'src/os.dart';
|
||||
@@ -63,6 +64,7 @@ export 'src/panels/layout_preset.dart';
|
||||
export 'src/panels/registry.dart';
|
||||
export 'src/panels/slot_id.dart';
|
||||
export 'src/panels/view_pane_snapshot.dart';
|
||||
export 'src/theme/bundled_themes.dart';
|
||||
export 'src/theme/contrast.dart';
|
||||
export 'src/theme/controller.dart';
|
||||
export 'src/theme/loader.dart';
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/// Canonical list of Tier-0 i18n namespaces preloaded at boot — the single
|
||||
/// source of truth (T-371).
|
||||
///
|
||||
/// `lib/main.dart` preloads exactly these (framework chrome under `core`, plus
|
||||
/// the catalogs of the built-ins that activate at Tier 0); the i18n coverage
|
||||
/// gate validates every one of them. The other ~17 catalogs that ship belong
|
||||
/// to built-ins that activate lazily in later tiers — their catalogs load on
|
||||
/// activation, and the gate validates them through the assets-dir sweep rather
|
||||
/// than this preload list.
|
||||
library;
|
||||
|
||||
/// Namespaces whose catalogs are loaded at boot, before any extension that
|
||||
/// owns them has activated. `core` holds framework chrome that lives outside
|
||||
/// any extension (lib/widgets, lib/kernel, shared reader chrome — T-469).
|
||||
const List<String> kTier0Namespaces = [
|
||||
'core',
|
||||
'builtin.default-layout',
|
||||
'builtin.welcome',
|
||||
'builtin.ipc-status',
|
||||
'builtin.theme-picker',
|
||||
'builtin.terminal',
|
||||
'builtin.files',
|
||||
'builtin.claude',
|
||||
'builtin.editor',
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
/// Canonical list of the bundled theme asset paths — the single source of
|
||||
/// truth for which themes ship (T-371).
|
||||
///
|
||||
/// Three consumers used to hand-maintain their own copy and drifted apart
|
||||
/// (main.dart loaded 10, the testmode harness loaded 8 — catppuccin was
|
||||
/// silently unvalidated, and the WCAG contrast gate had a third copy):
|
||||
///
|
||||
/// - `lib/main.dart` `_loadBundledThemes()` — what the running app loads.
|
||||
/// - `lib/test_app.dart` — the testmode platform-integration harness.
|
||||
/// - `test/a11y/contrast_test.dart` — the WCAG-AA contrast gate.
|
||||
///
|
||||
/// They now all iterate this list, so adding a theme here loads it, validates
|
||||
/// it, and ships it everywhere at once. The contrast gate additionally asserts
|
||||
/// every `.yaml` under [kThemesDir] is in this list, so a new theme cannot sit
|
||||
/// on disk (or ship) unvalidated.
|
||||
library;
|
||||
|
||||
/// Directory holding the theme YAML sources, relative to the repo root.
|
||||
const String kThemesDir = 'lib/kernel/src/theme/themes';
|
||||
|
||||
/// Theme YAML asset paths bundled into the app, in display order: the base
|
||||
/// themes, then their `-hc` high-contrast siblings, then third-party / ported
|
||||
/// palettes paired with their own `-hc` siblings. Every base theme has a
|
||||
/// structurally identical `-hc` sibling that clears the strict contrast gate.
|
||||
const List<String> kBundledThemePaths = [
|
||||
'$kThemesDir/clide.yaml',
|
||||
'$kThemesDir/midnight.yaml',
|
||||
'$kThemesDir/paper.yaml',
|
||||
'$kThemesDir/terminal.yaml',
|
||||
'$kThemesDir/clide-hc.yaml',
|
||||
'$kThemesDir/midnight-hc.yaml',
|
||||
'$kThemesDir/paper-hc.yaml',
|
||||
'$kThemesDir/terminal-hc.yaml',
|
||||
'$kThemesDir/catppuccin-mocha.yaml',
|
||||
'$kThemesDir/catppuccin-mocha-hc.yaml',
|
||||
'$kThemesDir/summer-night.yaml',
|
||||
'$kThemesDir/summer-night-hc.yaml',
|
||||
];
|
||||
@@ -0,0 +1,44 @@
|
||||
# summer-night-hc — high-contrast sibling of `summer-night`.
|
||||
#
|
||||
# Same cyan/teal/pink silhouette and identical schema; muted text, status
|
||||
# chips, syntax tokens, and the focus border are brightened to clear the
|
||||
# strict contrast gate (D-22 + D-69, `extendedPairs` in
|
||||
# lib/kernel/src/theme/contrast.dart). Backgrounds/surface/foreground are
|
||||
# unchanged from the base so the two read as the same theme.
|
||||
|
||||
name: summer-night-hc
|
||||
display_name: Summer Night (high contrast)
|
||||
dark: true
|
||||
|
||||
palette:
|
||||
primary: "#5cc8ec"
|
||||
secondary: "#5fd6cb"
|
||||
accent: "#ff84a6"
|
||||
|
||||
background: "#21262f"
|
||||
surface: "#393e48"
|
||||
panel: "#292e38"
|
||||
|
||||
foreground: "#e2e8f5"
|
||||
muted: "#c4cad6"
|
||||
|
||||
success: "#3fd6c0"
|
||||
warning: "#e6b072"
|
||||
error: "#ff9b99"
|
||||
info: "#6cc8ec"
|
||||
|
||||
surfaceHi: "#434a57"
|
||||
border: "#3a414d"
|
||||
borderHi: "#5cc8ec"
|
||||
textDim: "#c4cad6"
|
||||
textMute: "#9aa2b2"
|
||||
accentSoft: "#21ff84a6"
|
||||
|
||||
syntax:
|
||||
keyword: "#ffa0c0" # pink
|
||||
type: "#6cd4f0" # cyan
|
||||
string: "#9fe0c0" # green
|
||||
number: "#f0c888" # amber
|
||||
comment: "#9aa2b2" # muted
|
||||
method: "#80d0e8" # teal-cyan
|
||||
punct: "#c4cad6" # dim
|
||||
@@ -1,24 +1,27 @@
|
||||
# Summer Night — ported from legacy clide v1.2.0.
|
||||
# Palette-only; the three-tier resolver fills semantic + surface from
|
||||
# defaults. Override sections land here as the token surface grows.
|
||||
# Summer Night — cyan/teal/pink on blue-grey (ported from legacy clide v1.2.0).
|
||||
#
|
||||
# The 12 original palette colors are kept as the v1.2 designer chose them. The
|
||||
# expanded keys (surfaceHi, border/borderHi, textDim/textMute, accentSoft) and
|
||||
# the syntax set follow the clide derivation pattern, re-tinted to this theme's
|
||||
# identity. Base honours the design and clears the canonical contrast gate;
|
||||
# the high-contrast sibling (summer-night-hc) clears the strict extended gate.
|
||||
|
||||
name: summer-night
|
||||
display_name: Summer Night
|
||||
dark: true
|
||||
|
||||
palette:
|
||||
# Accents (legacy names: primary/secondary/accent)
|
||||
primary: "#00a3d2" # cyan
|
||||
secondary: "#00a9b9" # teal
|
||||
accent: "#fa5f8b" # pink
|
||||
# Accents (legacy: primary cyan / secondary teal / accent pink)
|
||||
primary: "#00a3d2"
|
||||
secondary: "#00a9b9"
|
||||
accent: "#fa5f8b"
|
||||
|
||||
# Backgrounds
|
||||
background: "#21262f"
|
||||
surface: "#393e48"
|
||||
panel: "#292e38"
|
||||
|
||||
# Text. `muted` is WCAG-AA-calibrated against `panel` — don't darken
|
||||
# without re-running the a11y/contrast suite.
|
||||
# Text. `muted` is WCAG-AA-calibrated against `panel`.
|
||||
foreground: "#e2e8f5"
|
||||
muted: "#a6adbb"
|
||||
|
||||
@@ -27,3 +30,20 @@ palette:
|
||||
warning: "#d08447"
|
||||
error: "#f06c6f"
|
||||
info: "#00a3d2"
|
||||
|
||||
# Expanded keys (clide pattern, summer-night tint)
|
||||
surfaceHi: "#434a57"
|
||||
border: "#3a414d"
|
||||
borderHi: "#4a5260"
|
||||
textDim: "#9aa2b2"
|
||||
textMute: "#727a8a"
|
||||
accentSoft: "#21fa5f8b"
|
||||
|
||||
syntax:
|
||||
keyword: "#fa8fb0" # pink
|
||||
type: "#5ec8e6" # cyan
|
||||
string: "#7fd3a8" # green
|
||||
number: "#e0b070" # amber
|
||||
comment: "#727a8a" # muted
|
||||
method: "#6fc6e0" # teal-cyan
|
||||
punct: "#9aa2b2" # dim
|
||||
|
||||
+5
-31
@@ -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).
|
||||
|
||||
+2
-12
@@ -247,17 +247,7 @@ class _ClideTestAppState extends State<ClideTestApp> {
|
||||
// Theme loading
|
||||
try {
|
||||
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',
|
||||
];
|
||||
for (final p in paths) {
|
||||
for (final p in kBundledThemePaths) {
|
||||
final name = p.split('/').last.replaceAll('.yaml', '');
|
||||
try {
|
||||
final theme = await loader.fromAsset(rootBundle, p);
|
||||
@@ -277,7 +267,7 @@ class _ClideTestAppState extends State<ClideTestApp> {
|
||||
final themes = <ThemeDefinition>[];
|
||||
try {
|
||||
const loader = ThemeLoader();
|
||||
themes.add(await loader.fromAsset(rootBundle, 'lib/kernel/src/theme/themes/clide.yaml'));
|
||||
themes.add(await loader.fromAsset(rootBundle, kBundledThemePaths.first));
|
||||
} catch (_) {}
|
||||
|
||||
final services = await KernelServices.boot(
|
||||
|
||||
Reference in New Issue
Block a user