@typecad/cuttlefish 1.0.0-alpha.11 → 1.0.0-alpha.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/add-preset.d.ts +4 -0
  2. package/dist/add-preset.js +74 -0
  3. package/dist/api/config.d.ts +5 -5
  4. package/dist/api/shared/display-adapters/sdl.js +1 -1
  5. package/dist/api/shared/display-profile.d.ts +11 -0
  6. package/dist/api/shared/display-profile.js +3 -0
  7. package/dist/api/shared/framework-manifest.d.ts +85 -78
  8. package/dist/api/shared/framework-manifest.js +1 -0
  9. package/dist/api/shared/hal-op-ir.d.ts +19 -0
  10. package/dist/api/shared/toolchain-types.d.ts +0 -1
  11. package/dist/cli.js +21 -4
  12. package/dist/config-loader.d.ts +8 -2
  13. package/dist/config-loader.js +202 -53
  14. package/dist/config-schema.d.ts +7 -7
  15. package/dist/config-schema.js +3 -3
  16. package/dist/create/board-spec.d.ts +122 -122
  17. package/dist/create/debug-artifacts.d.ts +20 -0
  18. package/dist/create/debug-artifacts.js +69 -0
  19. package/dist/create/eslint-rules-template.js +6 -3
  20. package/dist/create/index.d.ts +2 -0
  21. package/dist/create/index.js +1 -0
  22. package/dist/create/init-scaffold.d.ts +1 -0
  23. package/dist/create/init-scaffold.js +5 -0
  24. package/dist/create/init-templates.js +0 -2
  25. package/dist/emit/compliance/rules.js +52 -4
  26. package/dist/emit/emitters/function-emitter-impl.js +7 -1
  27. package/dist/emit/emitters/line-appender.js +6 -0
  28. package/dist/emit/emitters/setup.js +15 -2
  29. package/dist/emit/emitters/ui-emitter.js +40 -15
  30. package/dist/emit/route-hal-op.js +55 -1
  31. package/dist/emit/statement-renderer.js +5 -2
  32. package/dist/ir/build-ir.js +22 -1
  33. package/dist/ir/expression-to-ir.js +17 -0
  34. package/dist/ir/feature-registry.js +22 -6
  35. package/dist/ir/hal/hal-emitter.js +23 -5
  36. package/dist/ir/hal/hal-plugins.js +11 -0
  37. package/dist/ir/pin-mode-validation.js +32 -9
  38. package/dist/ir/pin-state-tracking.d.ts +58 -0
  39. package/dist/ir/pin-state-tracking.js +182 -0
  40. package/dist/ir/program-analysis.d.ts +10 -2
  41. package/dist/ir/program-analysis.js +40 -4
  42. package/dist/ir/statement-to-ir.js +14 -0
  43. package/dist/ir/transformers/control-flow.js +29 -0
  44. package/dist/ir/transformers/ui-call-resolver.js +105 -1
  45. package/dist/ir/ui-element-auto-wire.js +7 -4
  46. package/dist/orchestrator/graph-builder.d.ts +4 -1
  47. package/dist/orchestrator/graph-builder.js +7 -1
  48. package/dist/platform/async-runtime.d.ts +1 -1
  49. package/dist/platform/async-runtime.js +12 -3
  50. package/dist/platform/generic-strategy.js +1 -1
  51. package/dist/preview/api-shared-shim.d.ts +1 -0
  52. package/dist/preview/api-shared-shim.js +7 -0
  53. package/dist/preview/client.js +220 -1
  54. package/dist/preview/server.js +154 -62
  55. package/dist/theme-tokens.d.ts +22 -0
  56. package/dist/theme-tokens.js +172 -0
  57. package/dist/transpile.js +90 -12
  58. package/dist/types.d.ts +5 -0
  59. package/dist/ui-hook.d.ts +7 -0
  60. package/dist/utils/cli.js +9 -0
  61. package/dist/utils/fs.d.ts +2 -0
  62. package/dist/utils/fs.js +16 -0
  63. package/dist/utils/ui.d.ts +5 -0
  64. package/dist/utils/ui.js +7 -0
  65. package/package.json +7 -5
@@ -0,0 +1,172 @@
1
+ // ---------------------------------------------------------------------------
2
+ // shadcn kit themes — the included token sets under assets/shadcn/themes/.
3
+ //
4
+ // A theme file is two CSS blocks (:root = light tokens, .dark = dark tokens)
5
+ // plus an optional header comment. Selecting a theme MERGES each block into
6
+ // the kit's stylesheet: declarations the theme defines override the kit's,
7
+ // and kit-specific extras the theme lacks (notably --destructive-background,
8
+ // which stock shadcn themes don't carry) keep their current values — so any
9
+ // stock theme from ui.shadcn.com or tweakcn pastes cleanly.
10
+ //
11
+ // Used by `cuttlefish add shadcn --theme <name>` (scaffold time) and
12
+ // `cuttlefish theme <name>` (swap an existing project's kit stylesheet).
13
+ // ---------------------------------------------------------------------------
14
+ import fs from "node:fs";
15
+ import path from "node:path";
16
+ import { fileURLToPath } from "node:url";
17
+ const THEMES_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../assets/shadcn/themes");
18
+ /** The tokens the kit's recipes reference; every merged theme must cover them. */
19
+ const REQUIRED_TOKENS = [
20
+ "--background", "--foreground",
21
+ "--card", "--card-foreground",
22
+ "--primary", "--primary-foreground",
23
+ "--secondary", "--secondary-foreground",
24
+ "--muted", "--muted-foreground",
25
+ "--accent", "--accent-foreground",
26
+ "--destructive", "--destructive-foreground", "--destructive-background",
27
+ "--border", "--input", "--radius",
28
+ ];
29
+ /** Blank out /* … *​/ comment interiors (same length) so selector searches
30
+ * can't match inside a comment while byte offsets stay valid against the
31
+ * original text. The kit's header comment literally contains `.dark { ... }`
32
+ * as documentation — an unmasked search spliced tokens into the comment and
33
+ * left the real block untouched. */
34
+ function maskComments(css) {
35
+ return css.replace(/\/\*[\s\S]*?\*\//g, (m) => " ".repeat(m.length));
36
+ }
37
+ /** Locate `selector {` in css, skipping comment spans. Custom-property values
38
+ * contain no braces, so the block ends at the first `}` after the opening
39
+ * one. */
40
+ function blockRange(css, selector) {
41
+ const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
42
+ const m = new RegExp(escaped + "\\s*\\{").exec(maskComments(css));
43
+ if (!m)
44
+ return null;
45
+ const open = m.index + m[0].length;
46
+ const close = css.indexOf("}", open);
47
+ if (close < 0)
48
+ return null;
49
+ return { selStart: m.index, open, close };
50
+ }
51
+ /** Parse a token block's declarations (line-oriented; one decl per line, the
52
+ * format both the kit and stock shadcn themes use). */
53
+ function parseDecls(block) {
54
+ const out = [];
55
+ for (const line of block.split("\n")) {
56
+ const m = /^\s*(--[\w-]+)\s*:\s*([^;]+);/.exec(line);
57
+ if (m)
58
+ out.push([m[1], m[2].trim()]);
59
+ }
60
+ return out;
61
+ }
62
+ function emitBlock(selector, decls) {
63
+ const body = decls.map(([k, v]) => ` ${k}: ${v};`).join("\n");
64
+ return `${selector} {\n${body}\n}`;
65
+ }
66
+ /** Order-preserving override merge: base order, overrides in place, theme-only
67
+ * declarations appended. */
68
+ function mergeDecls(base, override) {
69
+ const out = base.map(([k, v]) => [k, v]);
70
+ const indexByName = new Map(out.map(([k], i) => [k, i]));
71
+ for (const [k, v] of override) {
72
+ const existing = indexByName.get(k);
73
+ if (existing !== undefined)
74
+ out[existing][1] = v;
75
+ else {
76
+ indexByName.set(k, out.length);
77
+ out.push([k, v]);
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+ /** Where a project keeps its own themes: src/styles/themes/*.css next to
83
+ * the kit stylesheet. Project themes win over same-named package ones —
84
+ * pasting a theme is dropping a file into YOUR project, never node_modules. */
85
+ export function projectThemesDir(projectRoot) {
86
+ return path.resolve(projectRoot, "src/styles/themes");
87
+ }
88
+ function themeNamesIn(dir) {
89
+ if (!fs.existsSync(dir))
90
+ return [];
91
+ return fs.readdirSync(dir)
92
+ .filter((f) => f.endsWith(".css") && !f.startsWith("_"))
93
+ .map((f) => f.replace(/\.css$/, ""))
94
+ .sort();
95
+ }
96
+ /** Theme names, project-local (src/styles/themes/) first, then the ones
97
+ * shipped in the package (assets/shadcn/themes/). */
98
+ export function listShadcnThemes(projectRoot) {
99
+ const local = projectRoot ? themeNamesIn(projectThemesDir(projectRoot)) : [];
100
+ const included = themeNamesIn(THEMES_DIR);
101
+ return [...new Set([...local, ...included])].sort();
102
+ }
103
+ function formatThemeList(projectRoot) {
104
+ const local = projectRoot ? themeNamesIn(projectThemesDir(projectRoot)) : [];
105
+ const included = themeNamesIn(THEMES_DIR);
106
+ const lines = [];
107
+ if (local.length > 0) {
108
+ lines.push("project (src/styles/themes/):");
109
+ for (const n of local)
110
+ lines.push(` ${n}`);
111
+ }
112
+ lines.push("included with cuttlefish:");
113
+ for (const n of included)
114
+ lines.push(` ${n}`);
115
+ lines.push("(paste your own: src/styles/themes/<name>.css in this project)");
116
+ return lines.join("\n");
117
+ }
118
+ /** Load a theme by name — project-local directory first, then the package
119
+ * included set. Throws with the available names when missing. */
120
+ export function loadShadcnTheme(name, projectRoot) {
121
+ if (!/^[a-z0-9-]+$/.test(name)) {
122
+ throw new Error(`Invalid theme name "${name}" — use letters, digits, and dashes.`);
123
+ }
124
+ const candidates = projectRoot
125
+ ? [path.join(projectThemesDir(projectRoot), `${name}.css`), path.join(THEMES_DIR, `${name}.css`)]
126
+ : [path.join(THEMES_DIR, `${name}.css`)];
127
+ const file = candidates.find((c) => fs.existsSync(c));
128
+ if (!file) {
129
+ throw new Error(`Unknown theme "${name}". Available themes:\n${formatThemeList(projectRoot)}`);
130
+ }
131
+ const text = fs.readFileSync(file, "utf-8");
132
+ const rootRange = blockRange(text, ":root");
133
+ const darkRange = blockRange(text, ".dark");
134
+ if (!rootRange || !darkRange) {
135
+ throw new Error(`Theme "${name}" must define both a :root and a .dark token block.`);
136
+ }
137
+ const light = parseDecls(text.slice(rootRange.open, rootRange.close));
138
+ const dark = parseDecls(text.slice(darkRange.open, darkRange.close));
139
+ if (light.length === 0 || dark.length === 0) {
140
+ throw new Error(`Theme "${name}" has empty token blocks — paste the theme's :root and .dark declarations.`);
141
+ }
142
+ return { name, light, dark };
143
+ }
144
+ /** Merge a theme's tokens into a kit stylesheet's :root/.dark blocks and
145
+ * return the rewritten text. Throws when a merged block is missing any
146
+ * token the kit's recipes reference. */
147
+ export function applyShadcnTheme(cssText, theme) {
148
+ let out = cssText;
149
+ for (const [selector, themeDecls] of [[".dark", theme.dark], [":root", theme.light]]) {
150
+ const range = blockRange(out, selector);
151
+ if (!range) {
152
+ throw new Error(`The kit stylesheet has no ${selector} token block to replace.`);
153
+ }
154
+ const merged = mergeDecls(parseDecls(out.slice(range.open, range.close)), themeDecls);
155
+ out = out.slice(0, range.selStart) + emitBlock(selector, merged) + out.slice(range.close + 1);
156
+ }
157
+ // Validate AFTER both merges (a token can live in either block).
158
+ const rootRange = blockRange(out, ":root");
159
+ const darkRange = blockRange(out, ".dark");
160
+ const names = new Set();
161
+ if (rootRange)
162
+ for (const [k] of parseDecls(out.slice(rootRange.open, rootRange.close)))
163
+ names.add(k);
164
+ if (darkRange)
165
+ for (const [k] of parseDecls(out.slice(darkRange.open, darkRange.close)))
166
+ names.add(k);
167
+ const missing = REQUIRED_TOKENS.filter((t) => !names.has(t));
168
+ if (missing.length > 0) {
169
+ throw new Error(`Theme "${theme.name}" leaves required tokens unset: ${missing.join(", ")}`);
170
+ }
171
+ return out;
172
+ }
package/dist/transpile.js CHANGED
@@ -26,11 +26,12 @@ import { requireUIHook, hasUIHook } from "./ui-hook.js";
26
26
  import { loadUIEngine } from "./ui/ui-bridge.js";
27
27
  import { hasSafetyHook, requireSafetyHook } from "./safety-hook.js";
28
28
  import { loadSafetyEngine } from "./safety/safety-bridge.js";
29
- import { setDisplayProfile, resetDisplayProfile } from "./stores/display-profile-store.js";
29
+ import { setDisplayProfile, resetDisplayProfile, getDisplayProfile } from "./stores/display-profile-store.js";
30
30
  import { setThemeCss, resetThemeCss, setThemeClass } from "./stores/theme-store.js";
31
31
  import { emitCpp, registerAllEnumNames } from "./emit/cpp-emitter.js";
32
- import { readText, writeText } from "./utils/fs.js";
32
+ import { readText, writeText, resetWrittenFiles, wasWrittenThisRun } from "./utils/fs.js";
33
33
  import { debug as logDebug, info } from "./utils/logger.js";
34
+ import { printDebugStrategy } from "./utils/ui.js";
34
35
  import { loadLibraryDefinitions, generateLibdefStubs } from "./libdef/registry.js";
35
36
  import { buildCallGraph } from "./ir/call-graph.js";
36
37
  import { clearCaches, } from "./cache.js";
@@ -66,12 +67,53 @@ function loadExpectPreprocessor() {
66
67
  }
67
68
  }
68
69
  function cleanOutput(_entryDir, outDir) {
69
- // Preserved for incremental-build support: writeText now skips writing when
70
- // content is identical, so keeping the existing output dir intact lets
71
- // downstream build tools (idf.py/ninja, arduino-cli) reuse their build
72
- // caches. Stale files from removed source modules are harmless — they're
73
- // not referenced by the current entry file and won't be compiled.
74
- // The output dir is still created (via writeText ensureDir) on first run.
70
+ // The out dir is NOT wiped: writeText skips writing when content is
71
+ // identical, so keeping it lets downstream build tools (idf.py/ninja,
72
+ // arduino-cli) reuse their build caches. Stale generated SOURCES are handled
73
+ // precisely instead after emission, sweepStaleGeneratedSources() removes
74
+ // compiled-source files in the out dir that this run did not write (e.g. a
75
+ // main.cpp left behind by the old emit naming next to the current src.cpp;
76
+ // Zephyr's CMakeLists globs src/*.cpp, so a leftover compiles into
77
+ // duplicate-symbol link errors). The output dir is still created (via
78
+ // writeText → ensureDir) on first run.
79
+ void outDir;
80
+ resetWrittenFiles();
81
+ }
82
+ const GENERATED_SOURCE_EXTENSIONS = [".cpp", ".cc", ".c", ".h", ".ino"];
83
+ /**
84
+ * Remove stale generated source files from the out dir: compiled-source files
85
+ * that THIS transpile run did not write. Sweeps only the source layouts the
86
+ * emit pipeline uses (out dir root, src/, main/) and never recurses — build
87
+ * trees (e.g. Zephyr's out/build with its own generated .c files) are
88
+ * untouched, and neither are sidecar JSONs.
89
+ */
90
+ function sweepStaleGeneratedSources(outDir) {
91
+ for (const sub of ["", "src", "main"]) {
92
+ const dir = sub ? path.join(outDir, sub) : outDir;
93
+ let entries;
94
+ try {
95
+ entries = fs.readdirSync(dir, { withFileTypes: true });
96
+ }
97
+ catch {
98
+ continue; // layout subdir not used by this framework
99
+ }
100
+ for (const entry of entries) {
101
+ if (!entry.isFile())
102
+ continue;
103
+ if (!GENERATED_SOURCE_EXTENSIONS.some((ext) => entry.name.toLowerCase().endsWith(ext)))
104
+ continue;
105
+ const full = path.join(dir, entry.name);
106
+ if (wasWrittenThisRun(full))
107
+ continue;
108
+ try {
109
+ fs.unlinkSync(full);
110
+ info(`Removed stale generated source: ${path.relative(process.cwd(), full)}`);
111
+ }
112
+ catch {
113
+ // Locked/read-only file — leave it; best-effort cleanup.
114
+ }
115
+ }
116
+ }
75
117
  }
76
118
  /**
77
119
  * Auto-generates .d.ts files for C++ modules that are missing declarations.
@@ -160,6 +202,15 @@ import { loadFrameworkPackage } from "./framework-package.js";
160
202
  export { loadFrameworkPackage };
161
203
  export { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
162
204
  import { getLoadedFramework, hasLoadedFramework } from "./framework-registry.js";
205
+ /** Under --strict-css, UI CSS-compatibility warnings (code css-*: ignored
206
+ * alpha, quantized font sizes, unsupported display/position values, ...) are
207
+ * upgraded to errors so the build fails instead of approximating silently. */
208
+ function upgradeStrictCss(d, strict) {
209
+ if (strict && d.severity === "warning" && typeof d.code === "string" && d.code.startsWith("css-")) {
210
+ return { ...d, severity: "error" };
211
+ }
212
+ return d;
213
+ }
163
214
  function formatFatalDiagnostics(entries) {
164
215
  const errors = entries.filter(({ diagnostic }) => diagnostic.severity === "error");
165
216
  const lines = [
@@ -219,7 +270,11 @@ function loadPlatformStrategy(frameworkPackage, _boardPackage, fromDir, debug) {
219
270
  if (hasLoadedFramework()) {
220
271
  const { strategy } = getLoadedFramework();
221
272
  if (debug) {
222
- logDebug(`Loaded FrameworkStrategy from ${frameworkPackage}`, true);
273
+ // Styled like the other step lines (cyan ⇉) says what the debug
274
+ // build is actually doing, in user terms. Falls back to the package
275
+ // name when the strategy carries no id.
276
+ const frameworkName = strategy.id || frameworkPackage.replace(/^@typecad\/framework-/, "");
277
+ printDebugStrategy(frameworkName);
223
278
  }
224
279
  return strategy;
225
280
  }
@@ -310,7 +365,19 @@ export async function transpileFile(options) {
310
365
  // see incremental-cache.ts — so we always transpile the full graph.)
311
366
  cleanOutput(entryDir, outDir);
312
367
  profiler.startTimer("graph:collect");
313
- const graphResult = collectTranspileGraph(entryFile, options.boardPackage);
368
+ // Image-conversion cap: never decode larger than the physical panel —
369
+ // converted <img> assets downscale to fit (no 24MB C arrays from photos).
370
+ const imageDecodeMax = await (async () => {
371
+ try {
372
+ const { effectiveDisplaySize } = await import("./api/shared/display-profile.js");
373
+ const size = effectiveDisplaySize(getDisplayProfile());
374
+ return { maxW: size.width, maxH: size.height };
375
+ }
376
+ catch {
377
+ return {};
378
+ }
379
+ })();
380
+ const graphResult = await collectTranspileGraph(entryFile, options.boardPackage, imageDecodeMax);
314
381
  profiler.endTimer("graph:collect");
315
382
  const transpileFiles = graphResult.files;
316
383
  // ── Type-check all files before transpiling ────────────────────────────────
@@ -408,10 +475,12 @@ export async function transpileFile(options) {
408
475
  // parser warnings (unknown CSS properties, unknown HTML tags) here so the
409
476
  // author sees typos and unsupported features instead of silent drops.
410
477
  // Guarded: @typecad/ui is optional, so there may be no UI engine loaded.
478
+ // --strict-css upgrades the css-* compatibility warnings to errors.
411
479
  if (hasUIHook()) {
412
480
  for (const mod of requireUIHook().allUIModules()) {
413
481
  for (const d of mod.diagnostics) {
414
- diagnostics.push({ ...d, filePath: d.filePath ?? path.basename(mod.htmlPath) });
482
+ const upgraded = upgradeStrictCss(d, options.strictCss);
483
+ diagnostics.push({ ...upgraded, filePath: d.filePath ?? path.basename(mod.htmlPath) });
415
484
  }
416
485
  }
417
486
  }
@@ -541,10 +610,12 @@ export async function transpileFile(options) {
541
610
  profiler.endTimer("ir:build-all");
542
611
  // ── UI mount-time warnings (scroll memory budget, etc.) ─────────────────
543
612
  // Guarded: @typecad/ui is optional; no engine means no UI modules.
613
+ // --strict-css upgrades the css-* compatibility warnings to errors.
544
614
  if (hasUIHook()) {
545
615
  for (const mod of requireUIHook().allUIModules()) {
546
616
  for (const d of mod.mountDiagnostics) {
547
- diagnostics.push({ ...d, filePath: d.filePath ?? path.basename(mod.htmlPath) });
617
+ const upgraded = upgradeStrictCss(d, options.strictCss);
618
+ diagnostics.push({ ...upgraded, filePath: d.filePath ?? path.basename(mod.htmlPath) });
548
619
  }
549
620
  }
550
621
  }
@@ -822,6 +893,13 @@ export async function transpileFile(options) {
822
893
  }
823
894
  }
824
895
  profiler.endTimer("post:flatten");
896
+ // Remove stale generated sources (renamed entries, removed modules) so
897
+ // downstream globs (Zephyr's CMakeLists src/*.cpp) don't compile leftovers
898
+ // into duplicate-symbol link errors. Runs after every write of this run,
899
+ // including the toolchain prepare hook above.
900
+ profiler.startTimer("post:sweep-stale");
901
+ sweepStaleGeneratedSources(outDir);
902
+ profiler.endTimer("post:sweep-stale");
825
903
  // Profiler session ends (profiling disabled - no report generation)
826
904
  // ── Generate diagnostics report if enabled ──────────────────────────────
827
905
  let diagnosticsReportPath;
package/dist/types.d.ts CHANGED
@@ -100,6 +100,8 @@ export interface TranspileOptions {
100
100
  autosar?: ComplianceMode;
101
101
  /** When true (and autosar is warn/strict), also emit the .autosar-deviations.arxml sidecar. */
102
102
  autosarArxml?: boolean;
103
+ /** Upgrade UI CSS-compatibility warnings (css-* diagnostics) to errors. */
104
+ strictCss?: boolean;
103
105
  }
104
106
  export interface LibraryDefinitionCondition {
105
107
  target?: TargetProfile;
@@ -179,6 +181,8 @@ export interface CommandLineOptions {
179
181
  autosar?: ComplianceMode;
180
182
  /** When true (and autosar is warn/strict), also emit the .autosar-deviations.arxml sidecar. */
181
183
  autosarArxml?: boolean;
184
+ /** Upgrade UI CSS-compatibility warnings (css-* diagnostics) to errors. */
185
+ strictCss?: boolean;
182
186
  /** Config file for preview command */
183
187
  configPath?: string;
184
188
  /** Project root (dir of cuttlefish.config.ts); passed to transpileFile for the ESLint gate. */
@@ -188,6 +192,7 @@ export interface GenerateLibdefOptions {
188
192
  inputFile: string;
189
193
  outDir: string;
190
194
  }
195
+ /** `cuttlefish add <preset>` — scaffold a copy-and-own asset into the project. */
191
196
  export interface GeneratedOutputs {
192
197
  headerPath?: string;
193
198
  sourcePath: string;
package/dist/ui-hook.d.ts CHANGED
@@ -75,6 +75,13 @@ export interface TranspilerUIHook {
75
75
  nativeDisplayActive?: boolean;
76
76
  }): string;
77
77
  splitUiFile(src: string): UiFileParts;
78
+ /** Pre-decode src="…" image references (png/jpg/ico/…) into the RGB565
79
+ * asset cache. Must be awaited BEFORE loadUIModule/loadUIModuleFromText —
80
+ * the synchronous asset reader and natural-size layout read the cache. */
81
+ warmUpImageDecoding(sourceText: string, baseDir: string, opts?: {
82
+ maxW?: number;
83
+ maxH?: number;
84
+ }): Promise<void>;
78
85
  generateProjectUITypeDeclarations(projectRoot: string): {
79
86
  written: string[];
80
87
  errors: Array<{
package/dist/utils/cli.js CHANGED
@@ -49,6 +49,10 @@ export function printHelp() {
49
49
  console.log(` --autosar-arxml Also write <name>.autosar-deviations.arxml (Artop/DaVinci).`);
50
50
  console.log(` No-op unless --autosar is warn or strict.`);
51
51
  console.log();
52
+ console.log(` --strict-css Treat UI CSS-compatibility warnings as errors (css-*`);
53
+ console.log(` diagnostics: ignored alpha, quantized font sizes,`);
54
+ console.log(` unsupported display/position values, viewport-hogging sizes).`);
55
+ console.log();
52
56
  console.log(chalk.cyan(`BUILD COMMANDS`) + chalk.gray(` (chain in order: --compile → --upload → --monitor)`));
53
57
  console.log();
54
58
  console.log(` --compile Compile the generated output using the framework toolchain.`);
@@ -277,6 +281,10 @@ function parsePipelineCommand(argv, command, inputFile) {
277
281
  // --autosar-arxml: also write the .autosar-deviations.arxml sidecar
278
282
  // (Artop/DaVinci tooling). No-op unless --autosar is warn or strict.
279
283
  const autosarArxml = readBooleanFlag(argv, ["--autosar-arxml"]);
284
+ // --strict-css: upgrade UI CSS-compatibility warnings (css-* diagnostics,
285
+ // e.g. ignored alpha, quantized font sizes, unsupported display values) to
286
+ // errors so builds fail instead of silently approximating.
287
+ const strictCss = readBooleanFlag(argv, ["--strict-css"]);
280
288
  const emitMode = emitFlag === "cpp" || emitFlag === "split" ? emitFlag : "split";
281
289
  const emitMaps = emitMapsFlag === undefined ? true : emitMapsFlag !== "false";
282
290
  // Accept any target string — the framework package registers its own strategy id.
@@ -329,6 +337,7 @@ function parsePipelineCommand(argv, command, inputFile) {
329
337
  frameworkPackage: frameworkFlag,
330
338
  autosar,
331
339
  autosarArxml,
340
+ strictCss,
332
341
  };
333
342
  }
334
343
  export function parseCommandLine(argv) {
@@ -1,5 +1,7 @@
1
1
  export declare function ensureDir(dirPath: string): void;
2
2
  export declare function readText(filePath: string): string;
3
+ export declare function resetWrittenFiles(): void;
4
+ export declare function wasWrittenThisRun(filePath: string): boolean;
3
5
  export declare function writeText(filePath: string, content: string): void;
4
6
  export declare function listFiles(dirPath: string, extension: string): string[];
5
7
  /**
package/dist/utils/fs.js CHANGED
@@ -8,10 +8,26 @@ export function ensureDir(dirPath) {
8
8
  export function readText(filePath) {
9
9
  return fs.readFileSync(filePath, "utf8");
10
10
  }
11
+ // Paths written (or confirmed identical) via writeText since the last
12
+ // resetWrittenFiles() call. The transpiler uses this to sweep stale generated
13
+ // sources from the out dir — files left behind by renamed entries or removed
14
+ // source modules that a downstream glob (e.g. Zephyr's CMakeLists
15
+ // `file(GLOB src/*.cpp)`) would otherwise compile, producing duplicate-symbol
16
+ // link errors.
17
+ const writtenFiles = new Set();
18
+ export function resetWrittenFiles() {
19
+ writtenFiles.clear();
20
+ }
21
+ export function wasWrittenThisRun(filePath) {
22
+ return writtenFiles.has(path.resolve(filePath));
23
+ }
11
24
  export function writeText(filePath, content) {
12
25
  ensureDir(path.dirname(filePath));
26
+ const resolved = path.resolve(filePath);
13
27
  // Skip writing when content is identical — preserves mtime so downstream
14
28
  // build tools (idf.py/ninja, arduino-cli, make) can skip recompilation.
29
+ // The file still counts as "written this run" (it is current output).
30
+ writtenFiles.add(resolved);
15
31
  try {
16
32
  if (fs.readFileSync(filePath, "utf8") === content)
17
33
  return;
@@ -27,6 +27,11 @@ export declare function printTranspiling(): void;
27
27
  * Print compiling step
28
28
  */
29
29
  export declare function printCompiling(target: string): void;
30
+ /**
31
+ * Print the debug-session strategy notice (debug builds only): which
32
+ * framework's code-generation strategy is preparing the debug build.
33
+ */
34
+ export declare function printDebugStrategy(framework: string): void;
30
35
  /**
31
36
  * Print uploading step
32
37
  */
package/dist/utils/ui.js CHANGED
@@ -69,6 +69,13 @@ export function printTranspiling() {
69
69
  export function printCompiling(target) {
70
70
  console.log(chalk.cyan(`${ICON_COMPILE} Compiling for `) + chalk.white(target));
71
71
  }
72
+ /**
73
+ * Print the debug-session strategy notice (debug builds only): which
74
+ * framework's code-generation strategy is preparing the debug build.
75
+ */
76
+ export function printDebugStrategy(framework) {
77
+ console.log(chalk.cyan(`${ICON_COMPILE} Preparing to debug using `) + chalk.white(framework));
78
+ }
72
79
  /**
73
80
  * Print uploading step
74
81
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typecad/cuttlefish",
3
- "version": "1.0.0-alpha.11",
3
+ "version": "1.0.0-alpha.13",
4
4
  "description": "TypeScript to C++ transpiler — native, Arduino, and bare-metal targets",
5
5
  "type": "module",
6
6
  "main": "./dist/transpile.js",
@@ -83,7 +83,8 @@
83
83
  }
84
84
  },
85
85
  "files": [
86
- "dist"
86
+ "dist",
87
+ "assets"
87
88
  ],
88
89
  "publishConfig": {
89
90
  "access": "public"
@@ -99,8 +100,8 @@
99
100
  "zod": "^3.24.0"
100
101
  },
101
102
  "peerDependencies": {
102
- "@typecad/ui": "1.0.0-alpha.11",
103
- "@typecad/safety": "1.0.0-alpha.11"
103
+ "@typecad/ui": "1.0.0-alpha.13",
104
+ "@typecad/safety": "1.0.0-alpha.13"
104
105
  },
105
106
  "peerDependenciesMeta": {
106
107
  "@typecad/ui": {
@@ -111,7 +112,8 @@
111
112
  }
112
113
  },
113
114
  "optionalDependencies": {
114
- "@typecad/framework-native": "1.0.0-alpha.11"
115
+ "@typecad/expect": "1.0.0-alpha.13",
116
+ "@typecad/framework-native": "1.0.0-alpha.13"
115
117
  },
116
118
  "devDependencies": {
117
119
  "@types/node": "^22.10.7"