From b1f62a0733d4d92be3e9248b5300e3a8cd875329 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sat, 9 May 2026 15:45:40 -0400 Subject: [PATCH] feat(flutter/theme): codegen emits dark + light + flat token classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gen_tokens.dart now produces FabledSwordDarkTokens (dark surface), FabledSwordLightTokens (light surface), and FabledSwordFlatTokens (colors that don't change + radii + fonts). The original FabledSwordTokens class stays as a back-compat alias re-exporting the dark surface + flat values; any code that reads FabledSwordTokens.obsidian etc. continues to work during migration. Source: shared/fabledsword.tokens.json (already had dark/light/flat splits — this was the generator catching up). Co-Authored-By: Claude Opus 4.7 (1M context) --- flutter_client/lib/theme/tokens.dart | 40 ++++++++++++++++++ flutter_client/tool/gen_tokens.dart | 61 +++++++++++++++++++++++----- 2 files changed, 90 insertions(+), 11 deletions(-) diff --git a/flutter_client/lib/theme/tokens.dart b/flutter_client/lib/theme/tokens.dart index 455e13ca..e603ad50 100644 --- a/flutter_client/lib/theme/tokens.dart +++ b/flutter_client/lib/theme/tokens.dart @@ -2,6 +2,46 @@ // Run `dart run tool/gen_tokens.dart` to regenerate. import 'package:flutter/material.dart'; +class FabledSwordDarkTokens { + static const Color obsidian = Color(0xFF14171A); + static const Color iron = Color(0xFF1E2228); + static const Color slate = Color(0xFF2C313A); + static const Color pewter = Color(0xFF3F4651); + static const Color parchment = Color(0xFFE8E4D8); + static const Color vellum = Color(0xFFC2BFB4); + static const Color ash = Color(0xFF9C9A92); +} + +class FabledSwordLightTokens { + static const Color obsidian = Color(0xFFF8F5EE); + static const Color iron = Color(0xFFECE6D5); + static const Color slate = Color(0xFFDCD3BD); + static const Color pewter = Color(0xFFB8AE94); + static const Color parchment = Color(0xFF14171A); + static const Color vellum = Color(0xFF2C313A); + static const Color ash = Color(0xFF5C6068); +} + +class FabledSwordFlatTokens { + static const Color moss = Color(0xFF4A5D3F); + static const Color bronze = Color(0xFF8B7355); + static const Color oxblood = Color(0xFF6B2118); + static const Color warning = Color(0xFF8B6F1E); + static const Color error = Color(0xFFC04A1F); + static const Color info = Color(0xFF3D5A6E); + static const Color accent = Color(0xFF4A6B5C); + static const Color onAction = Color(0xFFE8E4D8); + static const double radiusSm = 4; + static const double radiusMd = 8; + static const double radiusLg = 12; + static const double radiusXl = 16; + static const String fontDisplay = "Fraunces"; + static const String fontBody = "Inter"; + static const String fontMono = "JetBrains Mono"; +} + +/// Back-compat alias — dark surface tokens + flat. Prefer the explicit +/// FabledSwordDarkTokens / FabledSwordLightTokens / FabledSwordFlatTokens. class FabledSwordTokens { static const Color obsidian = Color(0xFF14171A); static const Color iron = Color(0xFF1E2228); diff --git a/flutter_client/tool/gen_tokens.dart b/flutter_client/tool/gen_tokens.dart index 9383fa82..7540da63 100644 --- a/flutter_client/tool/gen_tokens.dart +++ b/flutter_client/tool/gen_tokens.dart @@ -2,10 +2,16 @@ // Run via `dart run tool/gen_tokens.dart`. The generated file is // committed (CI validates it matches the JSON to catch drift). // -// Web tokens.json splits colors into `dark`, `light`, and `flat` blocks -// for the SPA's theme toggle. Flutter v1 ships dark-only, so we merge -// dark + flat into a single flat namespace. Light variants will be -// emitted alongside as part of the Flutter theme-toggle parity work. +// Emits three flat classes mirroring the source JSON: +// - FabledSwordDarkTokens (surface colors for dark mode) +// - FabledSwordLightTokens (surface colors for light mode) +// - FabledSwordFlatTokens (colors that don't change between modes, +// plus radii + font families) +// +// Plus a back-compat FabledSwordTokens alias that re-exports the dark +// surface colors + flat — kept so any code that still reads +// FabledSwordTokens.obsidian (etc.) continues to work during the +// migration. Safe to delete once theme_extension.dart switches over. import 'dart:convert'; import 'dart:io'; @@ -15,9 +21,8 @@ void main() { final colorsRoot = (tokens['colors'] as Map).cast(); final dark = (colorsRoot['dark'] as Map).cast(); + final light = (colorsRoot['light'] as Map).cast(); final flat = (colorsRoot['flat'] as Map).cast(); - final colors = {...dark, ...flat}; - final radii = (tokens['radii'] as Map).cast(); final fonts = (tokens['fonts'] as Map).cast(); @@ -25,23 +30,57 @@ void main() { ..writeln('// GENERATED — do not edit. Source: shared/fabledsword.tokens.json') ..writeln('// Run `dart run tool/gen_tokens.dart` to regenerate.') ..writeln("import 'package:flutter/material.dart';") - ..writeln() - ..writeln('class FabledSwordTokens {'); + ..writeln(); - colors.forEach((name, hex) { + void writeColorClass(String className, Map colors) { + out.writeln('class $className {'); + colors.forEach((name, hex) { + final value = hex.replaceFirst('#', '0xFF'); + out.writeln(' static const Color ${_camel(name)} = Color($value);'); + }); + out.writeln('}'); + out.writeln(); + } + + writeColorClass('FabledSwordDarkTokens', dark); + writeColorClass('FabledSwordLightTokens', light); + + out.writeln('class FabledSwordFlatTokens {'); + flat.forEach((name, hex) { final value = hex.replaceFirst('#', '0xFF'); out.writeln(' static const Color ${_camel(name)} = Color($value);'); }); - radii.forEach((name, px) { final v = px.replaceAll('px', ''); out.writeln(' static const double radius${_pascal(name)} = $v;'); }); - fonts.forEach((name, family) { out.writeln(' static const String font${_pascal(name)} = ${jsonEncode(family)};'); }); + out.writeln('}'); + out.writeln(); + // Back-compat alias: dark surface colors + flat. Lets existing + // call sites that read FabledSwordTokens.obsidian etc. keep working + // until they're migrated to the explicit dark/light/flat classes. + out.writeln('/// Back-compat alias — dark surface tokens + flat. Prefer the explicit'); + out.writeln('/// FabledSwordDarkTokens / FabledSwordLightTokens / FabledSwordFlatTokens.'); + out.writeln('class FabledSwordTokens {'); + dark.forEach((name, hex) { + final value = hex.replaceFirst('#', '0xFF'); + out.writeln(' static const Color ${_camel(name)} = Color($value);'); + }); + flat.forEach((name, hex) { + final value = hex.replaceFirst('#', '0xFF'); + out.writeln(' static const Color ${_camel(name)} = Color($value);'); + }); + radii.forEach((name, px) { + final v = px.replaceAll('px', ''); + out.writeln(' static const double radius${_pascal(name)} = $v;'); + }); + fonts.forEach((name, family) { + out.writeln(' static const String font${_pascal(name)} = ${jsonEncode(family)};'); + }); out.writeln('}'); File('lib/theme/tokens.dart').writeAsStringSync(out.toString());