@neta-art/cohub-cli 10.8.2 → 10.10.0

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.
package/README.md CHANGED
@@ -57,12 +57,15 @@ Sign in once for each custom server, or supply `COHUB_EXECUTION_TOKEN`.
57
57
 
58
58
  ## Board export resources
59
59
 
60
- Board exports keep the requested resolution and source image quality. Before rendering,
60
+ Board exports keep the requested resolution. Previews are fetched and decoded at the size
61
+ the export needs: Cohub-hosted media (Space files included) provide resized images and
62
+ video first frames, and larger sources are scaled down before decoding. Before rendering,
61
63
  `boards export` prints the output dimensions and frame count (except with `--json`).
62
64
  Decoded image textures, including background and script output images, share a 256 MiB
63
- budget. Oversized images fail before native decoding rather than silently becoming
64
- placeholders. Use `--image-memory <MiB>` to raise the budget when the environment has
65
- sufficient memory; it is an image budget, not a limit on total process memory.
65
+ budget. Previews beyond it are drawn as placeholders with a warning; a background image
66
+ that does not fit fails the export before native decoding. Use `--image-memory <MiB>` to raise the budget
67
+ when the environment has sufficient memory; it is an image budget, not a limit on total
68
+ process memory.
66
69
  `--size` controls the viewport only with `--camera`.
67
70
 
68
71
  ```bash
@@ -602,6 +605,17 @@ cohub cron-jobs delete <cronJobId>
602
605
 
603
606
  Confirm before enabling, disabling, or deleting recurring scheduled prompts.
604
607
 
608
+ ## Feedback
609
+
610
+ ```bash
611
+ cohub feedback "runtime up hangs after sign-in"
612
+ cohub feedback "Board export is blurry" -f board.png -f export.log
613
+ cohub feedback --runtime-logs "Codex chats stopped syncing"
614
+ cat report.md | cohub feedback
615
+ ```
616
+
617
+ Sends feedback to the Cohub team. CLI version, OS, environment, account, and `COHUB_*` Space/Chat context are attached; the working directory is not. `--runtime-logs` attaches recent local Runtime diagnostics for the target Space.
618
+
605
619
  ## Safety
606
620
 
607
621
  Confirm before:
@@ -2,7 +2,9 @@ import type { BoardNodeRenderer } from "@neta-art/cohub/board/export/node";
2
2
  export declare const BOARD_EXPORT_IMAGE_MEMORY_MIB = 256;
3
3
  export declare class BoardExportResourceError extends Error {
4
4
  }
5
+ export type BoardExportDecodeImage = (bytes: Uint8Array, mimeType?: string, maxEdge?: number) => ReturnType<BoardNodeRenderer["decodeImage"]>;
6
+ /** Decodes within `maxBytes`, first scaling images down to `maxEdge` on their long edge. */
5
7
  export declare function createBoardExportImageBudget(renderer: BoardNodeRenderer, maxBytes: number): {
6
- decodeImage: (bytes: Uint8Array, mimeType?: string) => Promise<import("@neta-art/cohub/board/export/node").BoardNodeTexture>;
8
+ decodeImage: BoardExportDecodeImage;
7
9
  readonly usedBytes: number;
8
10
  };
@@ -1,13 +1,24 @@
1
1
  export const BOARD_EXPORT_IMAGE_MEMORY_MIB = 256;
2
2
  export class BoardExportResourceError extends Error {
3
3
  }
4
+ /** Decodes within `maxBytes`, first scaling images down to `maxEdge` on their long edge. */
4
5
  export function createBoardExportImageBudget(renderer, maxBytes) {
5
6
  if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0)
6
7
  throw new Error("Image memory budget must be a positive integer.");
7
8
  let usedBytes = 0;
8
- const decodeImage = async (bytes, mimeType) => {
9
+ const decodeImage = async (input, mimeType, maxEdge = Number.POSITIVE_INFINITY) => {
9
10
  const { default: sharp } = await import("sharp");
10
- const { width, height } = await sharp(bytes, { limitInputPixels: false }).metadata();
11
+ let bytes = input;
12
+ let { width, height } = await sharp(bytes, { limitInputPixels: false }).metadata();
13
+ if (Math.max(width ?? 0, height ?? 0) > maxEdge) {
14
+ const resized = await sharp(bytes, { limitInputPixels: false })
15
+ .resize({ width: maxEdge, height: maxEdge, fit: "inside" })
16
+ .png({ compressionLevel: 0 })
17
+ .toBuffer({ resolveWithObject: true });
18
+ ({ width, height } = resized.info);
19
+ bytes = new Uint8Array(resized.data);
20
+ mimeType = "image/png";
21
+ }
11
22
  const required = (width ?? 0) * (height ?? 0) * 4;
12
23
  if (!Number.isSafeInteger(required) || required <= 0)
13
24
  throw new Error("Image dimensions could not be read.");
@@ -1,5 +1,6 @@
1
- import type { BoardDocument, BoardExportRegion, BoardSceneItem } from "@neta-art/cohub/board";
2
- import type { BoardNodeExportFormat, BoardNodeFont, BoardNodeRenderer, BoardNodeTexture } from "@neta-art/cohub/board/export/node";
1
+ import type { BoardDocument, BoardExportRegion, BoardSceneItem, BoardThemeId } from "@neta-art/cohub/board";
2
+ import type { BoardNodeExportFormat, BoardNodeFont, BoardNodeTexture } from "@neta-art/cohub/board/export/node";
3
+ import { type BoardExportDecodeImage } from "./board-export-resources.js";
3
4
  export declare function resolveBundledFonts(): BoardNodeFont[];
4
5
  export type BoardExportSource = {
5
6
  document: BoardDocument;
@@ -7,18 +8,31 @@ export type BoardExportSource = {
7
8
  title: string | null;
8
9
  };
9
10
  export declare function loadBoardDocument(spaceId: string, target: string): Promise<BoardExportSource>;
11
+ type TextureMiss = "error" | "unavailable" | "omitted";
10
12
  export type BoardExportTextureCache = {
11
13
  textures: Map<string, BoardNodeTexture>;
12
- failed: Set<string>;
14
+ /** Long edge each texture was decoded for; Infinity once it is the whole source. */
15
+ edges: Map<string, number>;
16
+ /** Keys without a texture: failed to load, no picture Node can draw, or over the image budget. */
17
+ missed: Map<string, TextureMiss>;
13
18
  };
14
- export declare function loadBoardTextures(renderer: BoardNodeRenderer, spaceId: string, items: readonly BoardSceneItem[], options?: {
19
+ /** Texture keys Node can draw; 3D model stills need a browser. */
20
+ export declare function boardExportAssetKey(item: BoardSceneItem): string | null;
21
+ /**
22
+ * Previews for `items` within what the image budget leaves after other images,
23
+ * such as the background. A preview that still does not fit is omitted.
24
+ */
25
+ export declare function loadBoardTextures(images: {
26
+ decodeImage: BoardExportDecodeImage;
27
+ usedBytes: number;
28
+ }, spaceId: string, items: readonly BoardSceneItem[], options: {
29
+ scale: number;
30
+ maxBytes: number;
15
31
  concurrency?: number;
16
32
  cache?: BoardExportTextureCache;
17
33
  }): Promise<{
18
34
  textures: Map<string, BoardNodeTexture>;
19
- failed: string[];
20
- omitted: string[];
21
- }>;
35
+ } & Record<"failed" | "unavailable" | "omitted", string[]>>;
22
36
  export type BoardExportRunOptions = {
23
37
  spaceId: string;
24
38
  target: string;
@@ -27,7 +41,7 @@ export type BoardExportRunOptions = {
27
41
  animation?: string;
28
42
  scale: number;
29
43
  padding?: number;
30
- colorScheme: "dark" | "light";
44
+ theme: BoardThemeId;
31
45
  background: "paper" | "transparent";
32
46
  format: BoardNodeExportFormat;
33
47
  quality?: number;
@@ -74,3 +88,4 @@ export declare function runBoardVideoExport(options: BoardVideoExportOptions): P
74
88
  warnings: string[];
75
89
  }>;
76
90
  export declare function runBoardExport(options: BoardExportRunOptions): Promise<BoardExportRunResult | null>;
91
+ export {};
@@ -10,12 +10,14 @@ import { fileURLToPath } from "node:url";
10
10
  import { tmpdir } from "node:os";
11
11
  const execFile = promisify(execFileCallback);
12
12
  import { parseBoardMediaSource } from "@neta-art/cohub/board";
13
- import { hasBoardScripts, boardDocumentAt, createBoardRenderSampler, boardImageKeySource, buildBoardScene, createNodeBoardRenderer, createNodeBoardScriptHost, exportBoardImageBytes, imageAssetKey, parseBoardDocument, planBoardExport, prepareBoardExport, selectBoardExportAssets, } from "./board-kernel.js";
13
+ import { imageVariantUrl, MEDIA_VARIANT_MAX, mediaVariantSize, videoFrameUrl } from "@neta-art/cohub/media";
14
+ import { hasBoardScripts, boardAssetKey, boardAssetKeySource, boardDocumentAt, boardRenderTheme, boardTextureEdge, boardTexturePixels, createBoardRenderSampler, buildBoardScene, createNodeBoardRenderer, createNodeBoardScriptHost, exportBoardImageBytes, parseBoardDocument, planBoardExport, prepareBoardExport, selectBoardExportAssets, } from "./board-kernel.js";
14
15
  import { resolveBoardId } from "./board-command-support.js";
16
+ import { settleBoardDocument } from "./board-media.js";
15
17
  import { BOARD_EXPORT_FONTS } from "./board-fonts.js";
16
18
  import { createClient } from "./client.js";
17
19
  import { downloadPublicAudio, downloadPublicImage } from "./safe-remote-image.js";
18
- import { BOARD_EXPORT_IMAGE_MEMORY_MIB, BoardExportResourceError, createBoardExportImageBudget } from "./board-export-resources.js";
20
+ import { BOARD_EXPORT_IMAGE_MEMORY_MIB, BoardExportResourceError, createBoardExportImageBudget, } from "./board-export-resources.js";
19
21
  const debugExport = debuglog("cohub:board-export");
20
22
  export function resolveBundledFonts() {
21
23
  const require = createRequire(import.meta.url);
@@ -53,55 +55,105 @@ export async function loadBoardDocument(spaceId, target) {
53
55
  throw new Error(`Board ${result.id} could not be read: ${parsed.diagnostics[0]?.path}: ${parsed.diagnostics[0]?.message}`);
54
56
  return { document: parsed.document, boardId: result.id, title: result.title };
55
57
  }
56
- export async function loadBoardTextures(renderer, spaceId, items, options = {}) {
57
- const selection = selectBoardExportAssets([...items], imageAssetKey);
58
- const cache = options.cache ?? { textures: new Map(), failed: new Set() };
59
- const { textures, failed } = cache;
58
+ /** Texture keys Node can draw; 3D model stills need a browser. */
59
+ export function boardExportAssetKey(item) {
60
+ const key = boardAssetKey(item);
61
+ return key && boardAssetKeySource(key)?.media !== "model" ? key : null;
62
+ }
63
+ function textureEdge(item, scale) {
64
+ const edge = boardTextureEdge(item, scale);
65
+ return Number.isFinite(edge) ? edge : Math.ceil(boardTexturePixels(item, scale));
66
+ }
67
+ const textureBytes = (texture) => texture.width * texture.height * 4;
68
+ /**
69
+ * Previews for `items` within what the image budget leaves after other images,
70
+ * such as the background. A preview that still does not fit is omitted.
71
+ */
72
+ export async function loadBoardTextures(images, spaceId, items, options) {
73
+ const cache = options.cache ?? { textures: new Map(), edges: new Map(), missed: new Map() };
74
+ const { textures, edges, missed } = cache;
75
+ let previewBytes = 0;
76
+ for (const texture of textures.values())
77
+ previewBytes += textureBytes(texture);
78
+ const maxBytes = options.maxBytes - (images.usedBytes - previewBytes);
79
+ const selection = selectBoardExportAssets([...items], boardExportAssetKey, { scale: options.scale, maxBytes });
60
80
  const wanted = new Set(selection.keys);
61
81
  for (const [key, texture] of textures) {
62
82
  if (wanted.has(key))
63
83
  continue;
64
84
  texture.destroy(true);
65
85
  textures.delete(key);
86
+ edges.delete(key);
66
87
  }
67
- for (const key of failed)
88
+ for (const key of missed.keys())
68
89
  if (!wanted.has(key))
69
- failed.delete(key);
70
- const pending = selection.keys.filter((key) => !textures.has(key) && !failed.has(key));
90
+ missed.delete(key);
91
+ const pending = selection.items.flatMap((item, index) => {
92
+ const key = selection.keys[index];
93
+ const edge = textureEdge(item, options.scale);
94
+ return missed.has(key) || (edges.get(key) ?? 0) >= edge ? [] : [{ key, item, edge }];
95
+ });
71
96
  const concurrency = Math.max(1, Math.min(options.concurrency ?? 2, 16));
72
97
  const client = createClient();
73
- let resourceError;
74
98
  async function worker() {
75
- for (;;) {
76
- if (resourceError)
77
- return;
78
- const key = pending.shift();
79
- if (!key)
80
- return;
81
- const source = boardImageKeySource(key);
82
- if (!source) {
83
- failed.add(key);
84
- continue;
85
- }
99
+ for (let task = pending.shift(); task; task = pending.shift()) {
100
+ const { key, item, edge } = task;
101
+ let miss = "unavailable";
86
102
  try {
87
- const { bytes, mimeType } = source.kind === "file"
88
- ? await readSpaceFileBytes(client, spaceId, source.value)
89
- : await downloadPublicImage(source.value);
90
- const texture = await renderer.decodeImage(bytes, mimeType);
91
- textures.set(key, texture);
103
+ const source = boardAssetKeySource(key);
104
+ const picture = source && await fetchBoardPicture(client, spaceId, source, item, edge);
105
+ if (picture) {
106
+ const texture = await images.decodeImage(picture.bytes, picture.mimeType, edge);
107
+ textures.get(key)?.destroy(true);
108
+ textures.set(key, texture);
109
+ edges.set(key, Math.max(texture.width, texture.height) < edge ? Number.POSITIVE_INFINITY : edge);
110
+ miss = null;
111
+ }
92
112
  }
93
113
  catch (error) {
94
- if (error instanceof BoardExportResourceError)
95
- resourceError = error;
96
- else
97
- failed.add(key);
114
+ miss = error instanceof BoardExportResourceError ? "omitted" : "error";
98
115
  }
116
+ // A preview already shown stays, at the sharpness it has.
117
+ if (miss && textures.has(key))
118
+ edges.set(key, Number.POSITIVE_INFINITY);
119
+ else if (miss)
120
+ missed.set(key, miss);
99
121
  }
100
122
  }
101
123
  await Promise.all(Array.from({ length: Math.min(concurrency, pending.length) }, worker));
102
- if (resourceError)
103
- throw resourceError;
104
- return { textures, failed: [...failed], omitted: selection.omittedKeys };
124
+ const keys = (reason) => [...missed].flatMap(([key, value]) => (value === reason ? [key] : []));
125
+ return { textures, failed: keys("error"), unavailable: keys("unavailable"), omitted: [...selection.omittedKeys, ...keys("omitted")] };
126
+ }
127
+ /**
128
+ * The picture for a texture, `edge` pixels on its long edge where the host can
129
+ * resize: a CDN variant of an image, or the first frame of a video. Null when
130
+ * there is none, as for videos outside Cohub's media hosts.
131
+ */
132
+ async function fetchBoardPicture(client, spaceId, source, item, edge) {
133
+ // Thumbnail edges below the smallest variant fetch that variant; decoding scales it down.
134
+ const variantEdge = Number.isFinite(edge) && edge <= MEDIA_VARIANT_MAX ? mediaVariantSize(edge) : null;
135
+ const video = source.media === "video";
136
+ const url = source.kind === "url"
137
+ ? source.value
138
+ : video || variantEdge
139
+ ? await client.space(spaceId).files.resolveUrl(source.value, { purpose: "playback" }).catch(() => null)
140
+ : null;
141
+ if (video) {
142
+ const { naturalWidth: w, naturalHeight: h } = (item.type === "video" && item.props.snapshot) || {};
143
+ const width = w && h ? Math.min(w, (edge * w) / Math.max(w, h)) : edge;
144
+ const frame = url && videoFrameUrl(url, mediaVariantSize(width));
145
+ return frame ? downloadPublicImage(frame) : null;
146
+ }
147
+ const variant = url && variantEdge && imageVariantUrl(url, variantEdge);
148
+ if (variant) {
149
+ try {
150
+ return await downloadPublicImage(variant);
151
+ }
152
+ catch {
153
+ // Fall back to the original below.
154
+ }
155
+ }
156
+ return source.kind === "url" ? downloadPublicImage(source.value) : readSpaceFileBytes(client, spaceId, source.value);
105
157
  }
106
158
  async function readSpaceFileBytes(client, spaceId, path) {
107
159
  const { blob, mimeType } = await client.space(spaceId).files.download(path);
@@ -122,7 +174,7 @@ export async function runBoardVideoExport(options) {
122
174
  if (times.some((time, index) => index > 0 && Math.abs(time - times[index - 1] - step) > 0.001))
123
175
  throw new Error("Video export needs evenly spaced times.");
124
176
  await execFile("ffmpeg", ["-version"], { signal: options.signal }).catch(() => { throw new Error("Video export needs ffmpeg. Install ffmpeg and try again."); });
125
- const document = options.document ?? (await loadBoardDocument(options.spaceId, options.target)).document;
177
+ const document = options.document ?? await settleBoardDocument(options.spaceId, (await loadBoardDocument(options.spaceId, options.target)).document);
126
178
  const tempDir = await mkdtemp(join(tmpdir(), "cohub-board-export-"));
127
179
  try {
128
180
  let count = 0;
@@ -208,7 +260,7 @@ export async function runBoardVideoExport(options) {
208
260
  }
209
261
  export async function runBoardExport(options) {
210
262
  options.signal?.throwIfAborted();
211
- const document = options.document ?? (await loadBoardDocument(options.spaceId, options.target)).document;
263
+ const document = options.document ?? await settleBoardDocument(options.spaceId, (await loadBoardDocument(options.spaceId, options.target)).document);
212
264
  if (options.animation && !document.animations[options.animation]) {
213
265
  throw new Error(`Animation ${options.animation} does not exist.`);
214
266
  }
@@ -230,10 +282,11 @@ export async function runBoardExport(options) {
230
282
  });
231
283
  };
232
284
  let scriptHost = null;
233
- const textureCache = { textures: new Map(), failed: new Set() };
285
+ const textureCache = { textures: new Map(), edges: new Map(), missed: new Map() };
234
286
  let backgroundTexture;
235
287
  try {
236
- const sampler = createBoardRenderSampler(document, options.animation ?? null, { colorScheme: options.colorScheme });
288
+ const theme = boardRenderTheme(options.theme);
289
+ const sampler = createBoardRenderSampler(document, options.animation ?? null, theme);
237
290
  const documentAt = (time) => time === null ? document : sampler.at(time).document;
238
291
  const plan = planBoardExport({
239
292
  scene: buildBoardScene(documentAt(times[0] ?? null)),
@@ -272,7 +325,6 @@ export async function runBoardExport(options) {
272
325
  warnings.push("The board background image could not be loaded; the fallback color was exported.");
273
326
  }
274
327
  }
275
- const videoCount = plan.items.filter((item) => item.type === "video").length;
276
328
  if (hasBoardScripts(document))
277
329
  scriptHost = createNodeBoardScriptHost(renderer, {
278
330
  concurrency: 2,
@@ -282,18 +334,13 @@ export async function runBoardExport(options) {
282
334
  },
283
335
  });
284
336
  const host = scriptHost;
285
- if (videoCount > 0) {
286
- warnings.push(`${videoCount} video preview${videoCount === 1 ? " was" : "s were"} drawn as placeholders; video decoding is unavailable in Node.`);
287
- }
288
337
  const backgroundImage = backgroundTexture
289
338
  ? { texture: backgroundTexture, fit: declaredBackground.fit ?? "cover", position: "center", opacity: declaredBackground.opacity ?? 1 }
290
339
  : undefined;
291
- const assetKey = options.withImages
292
- ? (item) => {
293
- const key = imageAssetKey(item);
294
- return key && omittedKeys.has(key) ? null : key;
295
- }
296
- : undefined;
340
+ const assetKey = (item) => {
341
+ const key = boardExportAssetKey(item);
342
+ return key && omittedKeys.has(key) ? null : key;
343
+ };
297
344
  const frames = [];
298
345
  const reported = new Set();
299
346
  let completed = 0;
@@ -320,11 +367,15 @@ export async function runBoardExport(options) {
320
367
  }
321
368
  trace("images", completed, prepared.plan);
322
369
  if (options.withImages) {
323
- const loaded = await loadBoardTextures(renderer, options.spaceId, prepared.plan.items.filter((item) => prepared.scene.opacity(item.id) > 0), { cache: textureCache });
370
+ const visible = prepared.plan.items.filter((item) => prepared.scene.opacity(item.id) > 0);
371
+ const loaded = await loadBoardTextures(images, options.spaceId, visible, { scale: prepared.plan.scale, maxBytes: imageMemoryBytes, cache: textureCache });
324
372
  omittedKeys = new Set(loaded.omitted);
373
+ const sources = (keys) => keys.slice(0, 3).map((key) => boardAssetKeySource(key)?.value ?? key).join(", ");
374
+ const plural = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
325
375
  const assetWarnings = [
326
- loaded.failed.length ? `${loaded.failed.length} image(s) could not be loaded: ${loaded.failed.slice(0, 3).join(", ")}` : null,
327
- loaded.omitted.length ? `${loaded.omitted.length} previews were drawn as placeholders to stay within the export texture limit.` : null,
376
+ loaded.failed.length ? `${plural(loaded.failed.length, "preview")} could not be loaded: ${sources(loaded.failed)}` : null,
377
+ loaded.unavailable.length ? `${plural(loaded.unavailable.length, "video")} drawn without a cover; Node exports take covers from Cohub-hosted media only.` : null,
378
+ loaded.omitted.length ? `${plural(loaded.omitted.length, "preview")} drawn as placeholders to stay within the ${imageMemoryBytes / 1024 ** 2} MiB image budget; raise --image-memory to include them.` : null,
328
379
  ];
329
380
  for (const warning of assetWarnings)
330
381
  if (warning && !reported.has(warning)) {
@@ -335,12 +386,12 @@ export async function runBoardExport(options) {
335
386
  trace("render", completed, prepared.plan);
336
387
  const result = exportBoardImageBytes(renderer, document, {
337
388
  ...frameOptions,
338
- colorScheme: options.colorScheme,
389
+ ...theme,
339
390
  background: options.background,
340
391
  textures,
341
392
  scripts: scriptHost ?? undefined,
342
393
  backgroundImage,
343
- ...(assetKey ? { assetKey } : {}),
394
+ assetKey,
344
395
  format: options.format,
345
396
  quality: options.quality,
346
397
  });
@@ -387,7 +438,7 @@ function describeWarning(warning) {
387
438
  }
388
439
  if (warning.kind === "images-missing") {
389
440
  const keys = warning.keys;
390
- return `${keys.length} image${keys.length === 1 ? "" : "s"} drawn as placeholders (use --images to fetch them).`;
441
+ return `${keys.length} preview${keys.length === 1 ? "" : "s"} drawn as placeholders; omit --no-images to fetch them.`;
391
442
  }
392
443
  if (warning.kind === "many-items") {
393
444
  return `${warning.count} items exported.`;
@@ -1,4 +1,8 @@
1
- /** Geist files `boards export` registers, copied into `dist/fonts` at build time. */
1
+ /**
2
+ * Fonts `boards export` registers, copied into `dist/fonts` at build time. The
3
+ * variable files and family names are the ones the Web loads, so every weight
4
+ * draws with the same outlines; Figtree is the Neta Pixel theme's font.
5
+ */
2
6
  export type BoardExportFont = {
3
7
  pkg: string;
4
8
  file: string;
@@ -1,10 +1,10 @@
1
- /** Geist files `boards export` registers, copied into `dist/fonts` at build time. */
2
- const subset = (pkg, family, weights) => weights.map((weight) => ({
3
- pkg,
4
- family,
5
- file: `${pkg.split("/").pop()}-latin-${weight}-normal.woff2`,
6
- }));
1
+ /**
2
+ * Fonts `boards export` registers, copied into `dist/fonts` at build time. The
3
+ * variable files and family names are the ones the Web loads, so every weight
4
+ * draws with the same outlines; Figtree is the Neta Pixel theme's font.
5
+ */
7
6
  export const BOARD_EXPORT_FONTS = [
8
- ...subset("@fontsource/geist", "Geist", [400, 500, 600, 700]),
9
- ...subset("@fontsource/geist-mono", "Geist Mono", [500, 600, 700]),
7
+ { pkg: "@fontsource-variable/geist", file: "geist-latin-wght-normal.woff2", family: "Geist Variable" },
8
+ { pkg: "@fontsource-variable/geist-mono", file: "geist-mono-latin-wght-normal.woff2", family: "Geist Mono Variable" },
9
+ { pkg: "@fontsource-variable/figtree", file: "figtree-latin-wght-normal.woff2", family: "Figtree Variable" },
10
10
  ];
@@ -1,4 +1,4 @@
1
- export { BOARD_SCRIPT_DRAWING_EXAMPLE, hasBoardScripts, boardDocumentAt, createBoardAnimationSampler, boardSequence, boardSequencePatch, boardPathTrack, boardImageKeySource, buildBoardScene, imageAssetKey, parseBoardDocument, planBoardExport, selectBoardExportAssets, } from "@neta-art/cohub/board";
1
+ export { BOARD_SCRIPT_DRAWING_EXAMPLE, hasBoardScripts, boardAssetKey, boardAssetKeySource, boardDocumentAt, createBoardAnimationSampler, boardSequence, boardSequencePatch, boardPathTrack, boardTextureEdge, boardTexturePixels, buildBoardScene, parseBoardDocument, planBoardExport, selectBoardExportAssets, } from "@neta-art/cohub/board";
2
2
  export { prepareBoardExport } from "@neta-art/cohub/board/export";
3
- export { createBoardRenderSampler, inspectBoardScene, inspectBoardItems, measureBoardText } from "@neta-art/cohub/board/render";
3
+ export { boardRenderTheme, createBoardRenderSampler, inspectBoardScene, inspectBoardItems, measureBoardText } from "@neta-art/cohub/board/render";
4
4
  export { createNodeBoardRenderer, createNodeBoardScriptHost, exportBoardImageBytes } from "@neta-art/cohub/board/export/node";