@typecad/ui 1.0.0-alpha.13 → 1.0.0-alpha.15

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 (60) hide show
  1. package/README.md +1457 -1469
  2. package/dist/cli.js +22 -19
  3. package/dist/preview/host-ui-runtime.js +0 -1
  4. package/dist/ui-engine/model.js +7 -1
  5. package/dist/ui-engine/runtime-header/canvas-scrollbar.js +115 -115
  6. package/dist/ui-engine/runtime-header/dirty-scroll-mutators.js +182 -182
  7. package/dist/ui-engine/runtime-header/forward-decls.js +227 -216
  8. package/dist/ui-engine/runtime-header/init-press-input.js +138 -138
  9. package/dist/ui-engine/runtime-header/keyboard.js +676 -676
  10. package/dist/ui-engine/runtime-header/node-draw-body.js +1658 -1656
  11. package/dist/ui-engine/runtime-header/paint-order-coords.js +259 -259
  12. package/dist/ui-engine/runtime-header/paint-rects-repair.js +736 -736
  13. package/dist/ui-engine/runtime-header/scroll-physics.js +4 -4
  14. package/dist/ui-engine/runtime-header/structs.js +228 -228
  15. package/dist/ui-engine/runtime-header/text-rendering.js +888 -888
  16. package/dist/ui-engine/runtime-header/tick/bindings-phase.js +118 -118
  17. package/dist/ui-engine/runtime-header/tick/dirty-draw-phase.js +896 -896
  18. package/dist/ui-engine/runtime-header/tick/scroll-canvas-phase.js +24 -24
  19. package/dist/ui-engine/runtime-header/touch-keyboard-fwd.js +11 -11
  20. package/dist/ui-engine/runtime-header/types-defines.js +110 -110
  21. package/dist/ui-engine/shadcn-kit.d.ts +1 -1
  22. package/dist/ui-engine/shadcn-kit.js +4 -0
  23. package/dist/ui-engine/ui-lowering.d.ts +1 -1
  24. package/dist/ui-engine/ui-lowering.js +14 -5
  25. package/dist/ui-engine/ui-registry.js +9 -1
  26. package/dist/wizard/display-catalog.d.ts +0 -3
  27. package/dist/wizard/display-catalog.js +3 -12
  28. package/dist/wizard/index.d.ts +2 -0
  29. package/dist/wizard/index.js +1 -0
  30. package/dist/wizard/integration-wizard.d.ts +3 -0
  31. package/dist/wizard/integration-wizard.js +69 -20
  32. package/dist/wizard/package-writer.d.ts +20 -0
  33. package/dist/wizard/package-writer.js +79 -0
  34. package/package.json +4 -4
  35. package/src/cli.ts +90 -87
  36. package/src/preview/host-ui-runtime.ts +0 -1
  37. package/src/ui-engine/model.ts +6 -1
  38. package/src/ui-engine/runtime-header/canvas-scrollbar.ts +121 -121
  39. package/src/ui-engine/runtime-header/dirty-scroll-mutators.ts +188 -188
  40. package/src/ui-engine/runtime-header/forward-decls.ts +238 -227
  41. package/src/ui-engine/runtime-header/init-press-input.ts +144 -144
  42. package/src/ui-engine/runtime-header/keyboard.ts +689 -689
  43. package/src/ui-engine/runtime-header/node-draw-body.ts +1683 -1681
  44. package/src/ui-engine/runtime-header/paint-order-coords.ts +265 -265
  45. package/src/ui-engine/runtime-header/paint-rects-repair.ts +742 -742
  46. package/src/ui-engine/runtime-header/scroll-physics.ts +4 -4
  47. package/src/ui-engine/runtime-header/structs.ts +234 -234
  48. package/src/ui-engine/runtime-header/text-rendering.ts +894 -894
  49. package/src/ui-engine/runtime-header/tick/bindings-phase.ts +124 -124
  50. package/src/ui-engine/runtime-header/tick/dirty-draw-phase.ts +902 -902
  51. package/src/ui-engine/runtime-header/tick/scroll-canvas-phase.ts +30 -30
  52. package/src/ui-engine/runtime-header/touch-keyboard-fwd.ts +11 -11
  53. package/src/ui-engine/runtime-header/types-defines.ts +116 -116
  54. package/src/ui-engine/shadcn-kit.ts +4 -0
  55. package/src/ui-engine/ui-lowering.ts +12 -4
  56. package/src/ui-engine/ui-registry.ts +9 -1
  57. package/src/wizard/display-catalog.ts +261 -273
  58. package/src/wizard/index.ts +46 -38
  59. package/src/wizard/integration-wizard.ts +679 -619
  60. package/src/wizard/package-writer.ts +98 -0
@@ -1,8 +1,8 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Display + touch hardware catalog for the @typecad/ui integration wizard.
3
3
  //
4
- // Mirrors the built-in display profiles exported by @typecad/framework-arduino
5
- // (src/displays/*.ts — BUILT_IN_PROFILES) and the TouchLibrary union in
4
+ // Mirrors the built-in display profiles exported by the active framework
5
+ // (BUILT_IN_PROFILES) and the TouchLibrary union in
6
6
  // @typecad/cuttlefish's display-profile.ts. The wizard asks questions and
7
7
  // fills defaults from these entries; the resulting object literal is written
8
8
  // into the project's cuttlefish.config.ts `display` section.
@@ -32,7 +32,6 @@ export const DISPLAY_CATALOG = [
32
32
  colorOrder: "rgb",
33
33
  antialias: true,
34
34
  },
35
- arduinoLibraries: ["Adafruit GFX Library", "Adafruit ILI9341"],
36
35
  wiringNote: "Defaults assume ESP32 VSPI: SCK=18, MOSI=23, MISO=19.",
37
36
  },
38
37
  {
@@ -57,10 +56,9 @@ export const DISPLAY_CATALOG = [
57
56
  invert: false,
58
57
  antialias: true,
59
58
  },
60
- arduinoLibraries: ["Adafruit GFX Library", "Adafruit ST7735 and ST7789 Library"],
61
59
  wiringNote: "Defaults assume ESP32 VSPI: SCK=18, MOSI=23, MISO=19. Adafruit_ST7796S.h comes from the" +
62
60
  " Adafruit_ST7735_and_ST7789 fork bundled in the typecode demos (demos/demo-st/lib) —" +
63
- " copy it into your sketchbook libraries/ if your Library Manager copy lacks the class.",
61
+ " copy it into your libraries/ folder if your Library Manager copy lacks the class.",
64
62
  },
65
63
  {
66
64
  id: "ssd1309-i2c",
@@ -75,7 +73,6 @@ export const DISPLAY_CATALOG = [
75
73
  rotation: 0,
76
74
  antialias: false,
77
75
  },
78
- arduinoLibraries: ["Adafruit GFX Library", "Adafruit SSD1306"],
79
76
  },
80
77
  {
81
78
  id: "sdl",
@@ -116,7 +113,6 @@ export const TOUCH_CATALOG = [
116
113
  // Typical raw-ADC range for 320×240 modules; refine per panel.
117
114
  calibration: { xMin: 375, xMax: 3950, yMin: 200, yMax: 3750 },
118
115
  },
119
- arduinoLibrary: "XPT2046_Touchscreen",
120
116
  },
121
117
  {
122
118
  id: "FT6336U",
@@ -128,7 +124,6 @@ export const TOUCH_CATALOG = [
128
124
  i2cFrequency: 400_000,
129
125
  },
130
126
  // The framework's FT6336U adapter includes RAK14014_FT6336U.h.
131
- arduinoLibrary: "RAK14014-FT6336U",
132
127
  },
133
128
  {
134
129
  id: "GT911",
@@ -139,7 +134,6 @@ export const TOUCH_CATALOG = [
139
134
  i2cAddress: 0x5d,
140
135
  i2cFrequency: 400_000,
141
136
  },
142
- arduinoLibrary: "GT911",
143
137
  },
144
138
  {
145
139
  id: "CST816S",
@@ -150,7 +144,6 @@ export const TOUCH_CATALOG = [
150
144
  i2cAddress: 0x15,
151
145
  i2cFrequency: 400_000,
152
146
  },
153
- arduinoLibrary: "CST816S",
154
147
  },
155
148
  {
156
149
  id: "Adafruit_TouchScreen",
@@ -161,7 +154,6 @@ export const TOUCH_CATALOG = [
161
154
  analogPins: { xp: 24, yp: 25, xm: 26, ym: 27, rx: 300 },
162
155
  calibration: { xMin: 100, xMax: 900, yMin: 120, yMax: 900 },
163
156
  },
164
- arduinoLibrary: "Adafruit TouchScreen",
165
157
  },
166
158
  {
167
159
  id: "Adafruit_STMPE610",
@@ -172,7 +164,6 @@ export const TOUCH_CATALOG = [
172
164
  cs: 14,
173
165
  calibration: { xMin: 375, xMax: 3950, yMin: 200, yMax: 3750 },
174
166
  },
175
- arduinoLibrary: "Adafruit STMPE610",
176
167
  },
177
168
  {
178
169
  id: "adapter",
@@ -2,6 +2,8 @@ export { DISPLAY_CATALOG, TOUCH_CATALOG, findPinConflicts, } from "./display-cat
2
2
  export type { DisplayCatalogEntry, TouchCatalogEntry, WizardBus, WizardTouchKind, } from "./display-catalog.js";
3
3
  export { findCuttlefishConfig, findSyntaxError, readConfigSection, readEntryPath, renderDisplayBody, renderDisplayProperty, upsertDisplaySection, } from "./config-writer.js";
4
4
  export type { ConfigRecord, RenderDisplayOptions, UpsertDisplayResult, } from "./config-writer.js";
5
+ export { findPackageJson, previewScriptCommand, readPackageScript, upsertPackageScript, } from "./package-writer.js";
6
+ export type { UpsertScriptResult } from "./package-writer.js";
5
7
  export { renderStarterUi } from "./starter-ui.js";
6
8
  export { runIntegrationWizard } from "./integration-wizard.js";
7
9
  export type { WizardRunResult, WizardStreams } from "./integration-wizard.js";
@@ -6,5 +6,6 @@
6
6
  // ---------------------------------------------------------------------------
7
7
  export { DISPLAY_CATALOG, TOUCH_CATALOG, findPinConflicts, } from "./display-catalog.js";
8
8
  export { findCuttlefishConfig, findSyntaxError, readConfigSection, readEntryPath, renderDisplayBody, renderDisplayProperty, upsertDisplaySection, } from "./config-writer.js";
9
+ export { findPackageJson, previewScriptCommand, readPackageScript, upsertPackageScript, } from "./package-writer.js";
9
10
  export { renderStarterUi } from "./starter-ui.js";
10
11
  export { runIntegrationWizard } from "./integration-wizard.js";
@@ -2,6 +2,9 @@ export interface WizardRunResult {
2
2
  exitCode: number;
3
3
  /** Absolute path of the config that was written, when a write happened. */
4
4
  configPath?: string;
5
+ /** Absolute path of the package.json holding the preview script, when the
6
+ * script was added or already present. */
7
+ packageJsonPath?: string;
5
8
  }
6
9
  /** Optional stream overrides so tests can drive the interactive flow. */
7
10
  export interface WizardStreams {
@@ -5,8 +5,10 @@
5
5
  // into their project's cuttlefish.config.ts: pick a display, answer bus/pin/
6
6
  // speed questions with hardware-aware defaults, then splice the resulting
7
7
  // `display` section into the config without touching any other section.
8
- // Offers to create a starter .ui entry file when none exists and prints the
9
- // next build/flash/library steps at the end.
8
+ // Also adds a `preview` npm script to package.json so the desktop preview
9
+ // renderer starts via `npm run preview`. Offers to create a starter .ui entry
10
+ // file when none exists and prints the next build/flash/library steps at the
11
+ // end.
10
12
  // ---------------------------------------------------------------------------
11
13
  import fs from "node:fs";
12
14
  import path from "node:path";
@@ -14,6 +16,7 @@ import chalk from "chalk";
14
16
  import { createPromptInterface, promptConfirm, promptInt, promptSelect, promptText, } from "./prompts.js";
15
17
  import { DISPLAY_CATALOG, TOUCH_CATALOG, findPinConflicts, } from "./display-catalog.js";
16
18
  import { findCuttlefishConfig, findSyntaxError, readConfigSection, readEntryPath, renderDisplayProperty, upsertDisplaySection, } from "./config-writer.js";
19
+ import { findPackageJson, previewScriptCommand, readPackageScript, upsertPackageScript, } from "./package-writer.js";
17
20
  import { renderStarterUi } from "./starter-ui.js";
18
21
  /** Keys the wizard manages — anything else found in an existing display
19
22
  * section is carried over untouched instead of being silently dropped. */
@@ -405,7 +408,35 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
405
408
  return { exitCode: 1 };
406
409
  }
407
410
  }
408
- // --- 8. Summary + write -------------------------------------------------------
411
+ // --- 8. package.json preview script ------------------------------------------
412
+ // `npm run preview` should start the desktop preview renderer, pointing at
413
+ // the config the wizard just verified. An existing identical script is
414
+ // left untouched; a differing one is only replaced with consent.
415
+ let packageJsonPath;
416
+ let packageJsonText;
417
+ let previewCommand;
418
+ let existingPreview;
419
+ let replacePreview = false;
420
+ const foundPackageJson = findPackageJson(path.dirname(configPath));
421
+ if (foundPackageJson) {
422
+ try {
423
+ packageJsonText = fs.readFileSync(foundPackageJson, "utf-8");
424
+ existingPreview = readPackageScript(packageJsonText, "preview");
425
+ packageJsonPath = foundPackageJson;
426
+ previewCommand = previewScriptCommand(foundPackageJson, configPath);
427
+ }
428
+ catch (err) {
429
+ console.log(` ${chalk.yellow("!")} ${err instanceof Error ? err.message : String(err)} — skipping the npm preview script.`);
430
+ }
431
+ if (previewCommand && existingPreview !== undefined && existingPreview !== previewCommand) {
432
+ console.log(` ${chalk.yellow("!")} package.json already has a preview script: ${chalk.white(existingPreview)}`);
433
+ replacePreview = await promptConfirm(rl, "Replace it with the wizard's preview command?", true);
434
+ }
435
+ }
436
+ else {
437
+ console.log(`${chalk.yellow("!")} No package.json found next to ${path.relative(cwd, configPath)} — skipping the npm preview script.`);
438
+ }
439
+ // --- 9. Summary + write -------------------------------------------------------
409
440
  console.log();
410
441
  console.log(chalk.dim(" The following display section will be written to"));
411
442
  console.log(chalk.dim(` ${path.relative(cwd, configPath)}:`));
@@ -421,6 +452,17 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
421
452
  if (carried.length > 0) {
422
453
  console.log(` ${chalk.dim(`Kept existing display settings the wizard does not manage: ${carried.join(", ")}`)}`);
423
454
  }
455
+ if (previewCommand && packageJsonPath) {
456
+ if (existingPreview === previewCommand) {
457
+ console.log(` ${chalk.dim(`npm script already configured: preview — "${previewCommand}"`)}`);
458
+ }
459
+ else if (existingPreview === undefined || replacePreview) {
460
+ console.log(` ${chalk.dim("Also writes npm script:")} ${chalk.white("preview")}${chalk.dim(` — "${previewCommand}"`)}`);
461
+ }
462
+ else {
463
+ console.log(` ${chalk.dim(`Kept existing preview script: "${existingPreview}"`)}`);
464
+ }
465
+ }
424
466
  console.log();
425
467
  if (!(await promptConfirm(rl, "Write this configuration?", true))) {
426
468
  console.log(chalk.dim(" Aborted — nothing was written."));
@@ -434,7 +476,21 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
434
476
  }
435
477
  fs.writeFileSync(configPath, updated.text, "utf-8");
436
478
  console.log(`${chalk.green("✓")} ${updated.mode === "replaced" ? "Updated" : "Added"} the display section in ${chalk.white(path.relative(cwd, configPath))}`);
437
- // --- 9. Starter entry file ------------------------------------------------------
479
+ // The preview script rides along with the confirmed write: added when
480
+ // missing, confirmed verbatim when already the wizard's command, and only
481
+ // replaced when the user consented above.
482
+ if (packageJsonPath && packageJsonText !== undefined && previewCommand
483
+ && (existingPreview === undefined || existingPreview === previewCommand || replacePreview)) {
484
+ const scriptUpsert = upsertPackageScript(packageJsonText, "preview", previewCommand);
485
+ if (scriptUpsert.changed) {
486
+ fs.writeFileSync(packageJsonPath, scriptUpsert.text, "utf-8");
487
+ console.log(`${chalk.green("✓")} ${scriptUpsert.previous !== undefined ? "Updated" : "Added"} npm script ${chalk.white("preview")} in ${chalk.white(path.relative(cwd, packageJsonPath))}`);
488
+ }
489
+ else {
490
+ console.log(`${chalk.green("✓")} npm script ${chalk.white("preview")} already configured in ${chalk.white(path.relative(cwd, packageJsonPath))}`);
491
+ }
492
+ }
493
+ // --- 10. Starter entry file ------------------------------------------------------
438
494
  if (entryPath) {
439
495
  const entryAbs = path.resolve(path.dirname(configPath), entryPath);
440
496
  if (!fs.existsSync(entryAbs) && entryAbs.endsWith(".ui")) {
@@ -445,31 +501,20 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
445
501
  }
446
502
  }
447
503
  }
448
- // --- 10. Next steps ---------------------------------------------------------------
504
+ // --- 11. Next steps ---------------------------------------------------------------
449
505
  console.log();
450
506
  console.log(chalk.cyan("Next steps"));
451
507
  console.log();
452
- const libraries = [];
453
- if (displayEntry.arduinoLibraries)
454
- libraries.push(...displayEntry.arduinoLibraries);
455
- if (touchEntry?.arduinoLibrary && touchRecord?.library)
456
- libraries.push(touchEntry.arduinoLibrary);
457
- if (libraries.length > 0) {
458
- console.log(" 1. Install the display/touch Arduino libraries (once per machine):");
459
- console.log();
460
- console.log(` ${chalk.cyan(`arduino-cli lib install ${libraries.map((lib) => `"${lib}"`).join(" ")}`)}`);
461
- console.log();
462
- }
463
- const firstStep = libraries.length > 0 ? 2 : 1;
508
+ const firstStep = 1;
464
509
  console.log(` ${firstStep}. Preview your UI on the desktop:`);
465
510
  console.log();
466
- console.log(` ${chalk.cyan("npx @typecad/cuttlefish preview")}`);
511
+ console.log(` ${chalk.cyan(previewCommand ? "npm run preview" : "npx @typecad/cuttlefish preview")}`);
467
512
  console.log();
468
513
  console.log(` ${firstStep + 1}. Compile for your board:`);
469
514
  console.log();
470
515
  console.log(` ${chalk.cyan("npx @typecad/cuttlefish build --compile")}`);
471
516
  console.log();
472
- console.log(` ${firstStep + 2}. Flash it (set console.port in the config, or pass --port):`);
517
+ console.log(` ${firstStep + 2}. Flash it (pass --port, or set CUTTLEFISH_PORT):`);
473
518
  console.log();
474
519
  console.log(` ${chalk.cyan("npx @typecad/cuttlefish build --compile --upload")}`);
475
520
  console.log();
@@ -478,7 +523,11 @@ export async function runIntegrationWizard(cwd = process.cwd(), streams = {}) {
478
523
  console.log(chalk.dim(" off-target, calibrate your panel and update display.touch.calibration."));
479
524
  console.log();
480
525
  }
481
- return { exitCode: 0, configPath };
526
+ return {
527
+ exitCode: 0,
528
+ configPath,
529
+ ...(packageJsonPath && previewCommand ? { packageJsonPath } : {}),
530
+ };
482
531
  }
483
532
  finally {
484
533
  rl.close();
@@ -0,0 +1,20 @@
1
+ /** Walk up from `startDir` looking for package.json. */
2
+ export declare function findPackageJson(startDir: string): string | undefined;
3
+ /** Read a single npm script; undefined when missing. Throws on invalid JSON. */
4
+ export declare function readPackageScript(sourceText: string, name: string): string | undefined;
5
+ export interface UpsertScriptResult {
6
+ text: string;
7
+ /** false — the script already had this exact command; text is unchanged. */
8
+ changed: boolean;
9
+ /** The command that was replaced, when a different script existed. */
10
+ previous?: string;
11
+ }
12
+ /** Insert or update one npm script, preserving every other key and their order. */
13
+ export declare function upsertPackageScript(sourceText: string, name: string, command: string): UpsertScriptResult;
14
+ /**
15
+ * The `cuttlefish preview` command the wizard writes as the `preview` npm
16
+ * script. The --config path is made relative to the package.json directory
17
+ * (npm scripts run there), posix-separated, and explicitly ./-prefixed so
18
+ * sibling configs render as `./cuttlefish.config.ts`.
19
+ */
20
+ export declare function previewScriptCommand(packageJsonPath: string, configPath: string): string;
@@ -0,0 +1,79 @@
1
+ // ---------------------------------------------------------------------------
2
+ // package.json script writer for the @typecad/ui integration wizard.
3
+ //
4
+ // package.json is plain JSON (no comments, no expressions), so unlike
5
+ // cuttlefish.config.ts (see config-writer.ts) this needs no AST surgery —
6
+ // parse, compare, set, and re-serialize with the 2-space formatting npm and
7
+ // `cuttlefish create` both write. An unchanged script is a no-op: the
8
+ // original text is returned verbatim so a re-run never reformats the file.
9
+ // ---------------------------------------------------------------------------
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+ const PACKAGE_FILENAME = "package.json";
13
+ /** Walk up from `startDir` looking for package.json. */
14
+ export function findPackageJson(startDir) {
15
+ let dir = path.resolve(startDir);
16
+ for (;;) {
17
+ const candidate = path.join(dir, PACKAGE_FILENAME);
18
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
19
+ return candidate;
20
+ }
21
+ const parent = path.dirname(dir);
22
+ if (parent === dir)
23
+ return undefined;
24
+ dir = parent;
25
+ }
26
+ }
27
+ function parsePackageJson(sourceText) {
28
+ try {
29
+ return JSON.parse(sourceText);
30
+ }
31
+ catch (err) {
32
+ throw new Error(`package.json is not valid JSON: ${err instanceof Error ? err.message : String(err)}`);
33
+ }
34
+ }
35
+ function scriptsOf(pkg) {
36
+ const scripts = pkg.scripts;
37
+ return typeof scripts === "object" && scripts !== null && !Array.isArray(scripts)
38
+ ? scripts
39
+ : undefined;
40
+ }
41
+ /** Read a single npm script; undefined when missing. Throws on invalid JSON. */
42
+ export function readPackageScript(sourceText, name) {
43
+ const scripts = scriptsOf(parsePackageJson(sourceText));
44
+ const value = scripts?.[name];
45
+ return typeof value === "string" ? value : undefined;
46
+ }
47
+ /** Insert or update one npm script, preserving every other key and their order. */
48
+ export function upsertPackageScript(sourceText, name, command) {
49
+ const pkg = parsePackageJson(sourceText);
50
+ const scripts = scriptsOf(pkg);
51
+ const previous = scripts?.[name];
52
+ if (previous === command) {
53
+ return { text: sourceText, changed: false };
54
+ }
55
+ if (!scripts) {
56
+ pkg.scripts = {};
57
+ }
58
+ pkg.scripts[name] = command;
59
+ // Keep the file's trailing-newline convention (npm writes LF; a missing
60
+ // final newline stays missing so the diff is one line, not two).
61
+ const eol = sourceText.endsWith("\r\n") ? "\r\n" : sourceText.endsWith("\n") ? "\n" : "";
62
+ return {
63
+ text: `${JSON.stringify(pkg, null, 2)}${eol}`,
64
+ changed: true,
65
+ ...(previous !== undefined ? { previous } : {}),
66
+ };
67
+ }
68
+ /**
69
+ * The `cuttlefish preview` command the wizard writes as the `preview` npm
70
+ * script. The --config path is made relative to the package.json directory
71
+ * (npm scripts run there), posix-separated, and explicitly ./-prefixed so
72
+ * sibling configs render as `./cuttlefish.config.ts`.
73
+ */
74
+ export function previewScriptCommand(packageJsonPath, configPath) {
75
+ let rel = path.relative(path.dirname(packageJsonPath), configPath).split(path.sep).join("/");
76
+ if (!rel.startsWith("."))
77
+ rel = `./${rel}`;
78
+ return `cuttlefish preview --config ${rel}`;
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typecad/ui",
3
- "version": "1.0.0-alpha.13",
3
+ "version": "1.0.0-alpha.15",
4
4
  "description": "TypeCAD UI authoring library — HTML/CSS-driven graphics for microcontrollers",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -44,8 +44,8 @@
44
44
  "assets"
45
45
  ],
46
46
  "dependencies": {
47
- "@typecad/cuttlefish": "1.0.0-alpha.13",
48
- "chalk": "^4.1.2",
47
+ "@typecad/cuttlefish": "1.0.0-alpha.15",
48
+ "chalk": "^6.0.0",
49
49
  "css-tree": "^3.2.1",
50
50
  "decode-ico": "^0.4.1",
51
51
  "linkedom": "^0.18.12",
@@ -86,7 +86,7 @@
86
86
  "firmware"
87
87
  ],
88
88
  "engines": {
89
- "node": ">=18"
89
+ "node": ">=22.11.0"
90
90
  },
91
91
  "devDependencies": {
92
92
  "@types/node": "^22.10.7",
package/src/cli.ts CHANGED
@@ -1,87 +1,90 @@
1
- #!/usr/bin/env node
2
- // ---------------------------------------------------------------------------
3
- // @typecad/ui CLI — display integration wizard.
4
- //
5
- // npx @typecad/ui --config Run the interactive integration wizard
6
- // npx @typecad/ui --help Show usage
7
- // npx @typecad/ui --version Show the installed version
8
- // ---------------------------------------------------------------------------
9
-
10
- import { runIntegrationWizard } from "./wizard/integration-wizard.js";
11
-
12
- const USAGE = `
13
- @typecad/ui — HTML/CSS-driven graphics for microcontrollers
14
-
15
- Usage:
16
- npx @typecad/ui --config Configure a display (+ touch) for your
17
- cuttlefish project interactively. Writes the
18
- \`display\` section of cuttlefish.config.ts.
19
-
20
- Options:
21
- --config Run the integration wizard
22
- --help, -h Show this help
23
- --version, -v Print the installed @typecad/ui version
24
-
25
- The wizard asks which display module you are using (ILI9341 / ST7796S SPI TFT,
26
- SSD1309 I2C OLED, desktop simulator, or custom), then walks through bus pins,
27
- SPI/I2C speed, rotation, and touch controller wiring with hardware-aware
28
- defaults. It never touches the rest of your cuttlefish.config.ts.
29
-
30
- Docs: https://github.com/justind000/typecode/tree/main/packages/ui
31
- `.trim();
32
-
33
- function printUsage(): void {
34
- console.log(USAGE);
35
- }
36
-
37
- async function main(argv: string[]): Promise<number> {
38
- const args = argv.slice(2);
39
-
40
- if (args.includes("--help") || args.includes("-h")) {
41
- printUsage();
42
- return 0;
43
- }
44
-
45
- if (args.includes("--version") || args.includes("-v")) {
46
- // Lazily read the version so importing this module stays side-effect free.
47
- const { readFile } = await import("node:fs/promises");
48
- const { fileURLToPath } = await import("node:url");
49
- const { dirname, join } = await import("node:path");
50
- try {
51
- const packageJson = JSON.parse(
52
- await readFile(join(dirname(dirname(fileURLToPath(import.meta.url))), "package.json"), "utf-8"),
53
- ) as { version?: string };
54
- console.log(packageJson.version ?? "unknown");
55
- return 0;
56
- } catch {
57
- console.log("unknown");
58
- return 0;
59
- }
60
- }
61
-
62
- if (args.length === 0 || (args.length === 1 && (args[0] === "--config" || args[0] === "config"))) {
63
- if (!process.stdin.isTTY) {
64
- console.error("The integration wizard is interactive and needs a terminal.");
65
- console.error("Run `npx @typecad/ui --config` from your project directory, or configure");
66
- console.error("the `display` section of cuttlefish.config.ts manually:");
67
- console.error("https://github.com/justind000/typecode/tree/main/packages/ui#display-configuration");
68
- return 1;
69
- }
70
- const result = await runIntegrationWizard(process.cwd());
71
- return result.exitCode;
72
- }
73
-
74
- console.error(`Unknown option${args.length > 1 ? "s" : ""}: ${args.join(" ")}\n`);
75
- printUsage();
76
- return 2;
77
- }
78
-
79
- main(process.argv).then(
80
- (code) => {
81
- process.exitCode = code;
82
- },
83
- (error) => {
84
- console.error(error instanceof Error ? error.message : error);
85
- process.exitCode = 1;
86
- },
87
- );
1
+ #!/usr/bin/env node
2
+ // ---------------------------------------------------------------------------
3
+ // @typecad/ui CLI — display integration wizard.
4
+ //
5
+ // npx @typecad/ui --config Run the interactive integration wizard
6
+ // npx @typecad/ui --help Show usage
7
+ // npx @typecad/ui --version Show the installed version
8
+ // ---------------------------------------------------------------------------
9
+
10
+ import { runIntegrationWizard } from "./wizard/integration-wizard.js";
11
+
12
+ const USAGE = `
13
+ @typecad/ui — HTML/CSS-driven graphics for microcontrollers
14
+
15
+ Usage:
16
+ npx @typecad/ui --config Configure a display (+ touch) for your
17
+ cuttlefish project interactively. Writes the
18
+ \`display\` section of cuttlefish.config.ts
19
+ and a \`preview\` npm script.
20
+
21
+ Options:
22
+ --config Run the integration wizard
23
+ --help, -h Show this help
24
+ --version, -v Print the installed @typecad/ui version
25
+
26
+ The wizard asks which display module you are using (ILI9341 / ST7796S SPI TFT,
27
+ SSD1309 I2C OLED, desktop simulator, or custom), then walks through bus pins,
28
+ SPI/I2C speed, rotation, and touch controller wiring with hardware-aware
29
+ defaults. It never touches the rest of your cuttlefish.config.ts, and adds a
30
+ \`preview\` script to package.json so the desktop preview renderer starts with
31
+ \`npm run preview\`.
32
+
33
+ Docs: https://github.com/justind000/typecode/tree/main/packages/ui
34
+ `.trim();
35
+
36
+ function printUsage(): void {
37
+ console.log(USAGE);
38
+ }
39
+
40
+ async function main(argv: string[]): Promise<number> {
41
+ const args = argv.slice(2);
42
+
43
+ if (args.includes("--help") || args.includes("-h")) {
44
+ printUsage();
45
+ return 0;
46
+ }
47
+
48
+ if (args.includes("--version") || args.includes("-v")) {
49
+ // Lazily read the version so importing this module stays side-effect free.
50
+ const { readFile } = await import("node:fs/promises");
51
+ const { fileURLToPath } = await import("node:url");
52
+ const { dirname, join } = await import("node:path");
53
+ try {
54
+ const packageJson = JSON.parse(
55
+ await readFile(join(dirname(dirname(fileURLToPath(import.meta.url))), "package.json"), "utf-8"),
56
+ ) as { version?: string };
57
+ console.log(packageJson.version ?? "unknown");
58
+ return 0;
59
+ } catch {
60
+ console.log("unknown");
61
+ return 0;
62
+ }
63
+ }
64
+
65
+ if (args.length === 0 || (args.length === 1 && (args[0] === "--config" || args[0] === "config"))) {
66
+ if (!process.stdin.isTTY) {
67
+ console.error("The integration wizard is interactive and needs a terminal.");
68
+ console.error("Run `npx @typecad/ui --config` from your project directory, or configure");
69
+ console.error("the `display` section of cuttlefish.config.ts manually:");
70
+ console.error("https://github.com/justind000/typecode/tree/main/packages/ui#display-configuration");
71
+ return 1;
72
+ }
73
+ const result = await runIntegrationWizard(process.cwd());
74
+ return result.exitCode;
75
+ }
76
+
77
+ console.error(`Unknown option${args.length > 1 ? "s" : ""}: ${args.join(" ")}\n`);
78
+ printUsage();
79
+ return 2;
80
+ }
81
+
82
+ main(process.argv).then(
83
+ (code) => {
84
+ process.exitCode = code;
85
+ },
86
+ (error) => {
87
+ console.error(error instanceof Error ? error.message : error);
88
+ process.exitCode = 1;
89
+ },
90
+ );
@@ -665,7 +665,6 @@ export class PreviewUIRuntime {
665
665
  }
666
666
 
667
667
  private navigate(screenIdx: number): void {
668
- this.onDiagnostics?.(`NAV -> screen ${screenIdx}`);
669
668
  const next = Math.trunc(Number(screenIdx));
670
669
  if (!Number.isFinite(next) || next < 0 || next >= this.screenCount || next === this.activeScreen) return;
671
670
  this.activeScreen = next;
@@ -322,9 +322,14 @@ function borderStyle(style: CSSProperty): 0 | 1 | 2 {
322
322
  }
323
323
 
324
324
  function borderWidthOf(style: CSSProperty): number {
325
+ // A none-style border renders nothing and must consume no space either:
326
+ // the draw path subtracts borderWidth*2 from the text max width, so a
327
+ // residual UA width on a borderless kit button wrapped its last glyph
328
+ // ("Primar" / "y"). Style none wins over any declared width.
329
+ if (borderStyle(style) === 0) return 0;
325
330
  const px = cssPx(style.borderWidth);
326
331
  if (px > 0) return Math.max(1, Math.min(8, px));
327
- return borderStyle(style) === 0 ? 0 : 1;
332
+ return 1;
328
333
  }
329
334
 
330
335
  /** Per-side border width: the per-side field wins when set, else falls back to