@neta-art/cohub-cli 10.0.0 → 10.2.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.
@@ -5953,8 +5953,16 @@ const fileProps$1 = {
5953
5953
  coverUrl: string().optional()
5954
5954
  }).strict().optional()
5955
5955
  };
5956
- const timedFileProps$1 = {
5956
+ const BoardMediaOriginSchema$1 = object({
5957
+ taskRunId: string().min(1).max(160),
5958
+ artifactId: string().min(1).max(240)
5959
+ }).strict();
5960
+ const mediaProps$1 = {
5957
5961
  ...fileProps$1,
5962
+ origin: BoardMediaOriginSchema$1.optional()
5963
+ };
5964
+ const timedFileProps$1 = {
5965
+ ...mediaProps$1,
5958
5966
  time: nonNegative$1.optional()
5959
5967
  };
5960
5968
  const BoardCropSchema$1 = object({
@@ -5967,7 +5975,7 @@ const BoardImageItemSchema$1 = object({
5967
5975
  ...boxed$1(640, 360),
5968
5976
  type: literal("image"),
5969
5977
  props: object({
5970
- ...fileProps$1,
5978
+ ...mediaProps$1,
5971
5979
  crop: BoardCropSchema$1.optional()
5972
5980
  }).strict()
5973
5981
  }).strict();
@@ -6032,8 +6040,10 @@ const BoardTaskSnapshotSchema$1 = object({
6032
6040
  title: string().min(1).max(240),
6033
6041
  model: string().max(160).optional(),
6034
6042
  promptExcerpt: string().max(480).optional(),
6043
+ errorExcerpt: string().max(240).optional(),
6035
6044
  artifactCount: number$1().int().nonnegative(),
6036
6045
  artifacts: array(BoardTaskArtifactSchema$1).max(6).default([]),
6046
+ startedAt: string().max(64).optional(),
6037
6047
  updatedAt: string().optional()
6038
6048
  }).strict();
6039
6049
  const BoardTaskItemSchema$1 = object({
@@ -6041,7 +6051,7 @@ const BoardTaskItemSchema$1 = object({
6041
6051
  type: literal("task"),
6042
6052
  props: object({
6043
6053
  taskRunId: string().min(1).max(160),
6044
- snapshot: BoardTaskSnapshotSchema$1
6054
+ snapshot: BoardTaskSnapshotSchema$1.describe("Maintained by the server for tasks in the Board's Space; writers may omit it.")
6045
6055
  }).strict()
6046
6056
  }).strict();
6047
6057
  const BOARD_EFFECT_KINDS$1 = [
@@ -7328,6 +7338,42 @@ function parseBoardDocument(input) {
7328
7338
  } : result;
7329
7339
  }
7330
7340
  //#endregion
7341
+ //#region ../protocol/src/board-task.ts
7342
+ function artifactScore(artifact) {
7343
+ const kind = {
7344
+ text: 1,
7345
+ image: 2,
7346
+ audio: 3,
7347
+ video: 4
7348
+ }[artifact.type];
7349
+ if (artifact.type === "text") return [kind, artifact.textExcerpt.length];
7350
+ if (artifact.type === "image") return [kind, (artifact.naturalWidth ?? 0) * (artifact.naturalHeight ?? 0)];
7351
+ return [
7352
+ kind,
7353
+ artifact.previewUrl ? 1 : 0,
7354
+ artifact.durationMs ? 1 : 0,
7355
+ artifact.durationMs ?? 0
7356
+ ];
7357
+ }
7358
+ function compareArtifacts(a, b) {
7359
+ const left = artifactScore(a);
7360
+ const right = artifactScore(b);
7361
+ for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
7362
+ const difference = (right[index] ?? 0) - (left[index] ?? 0);
7363
+ if (difference !== 0) return difference;
7364
+ }
7365
+ return 0;
7366
+ }
7367
+ function featuredTaskArtifact(artifacts) {
7368
+ let featured;
7369
+ for (const artifact of artifacts) if (!featured || compareArtifacts(artifact, featured) < 0) featured = artifact;
7370
+ return featured;
7371
+ }
7372
+ function taskArtifactPreviewUrl(artifact) {
7373
+ if (artifact?.type === "image") return artifact.url;
7374
+ if (artifact?.type === "video" || artifact?.type === "audio") return artifact.previewUrl;
7375
+ }
7376
+ //#endregion
7331
7377
  //#region ../sdk/src/board/model/script-index.ts
7332
7378
  const scriptsByItems = /* @__PURE__ */ new WeakMap();
7333
7379
  function boardScriptItems(document) {
@@ -7490,6 +7536,10 @@ function validateBoardScriptOutput(output, scope) {
7490
7536
  if (!output || typeof output !== "object" || !output.patches || !output.items || !output.hits || !Array.isArray(output.listens)) throw new Error("Invalid script output.");
7491
7537
  if (JSON.stringify(output).length > 1e6) throw new Error("Script output exceeds the 1 MB limit.");
7492
7538
  if (output.space !== void 0 && output.space !== "world" && output.space !== "screen") throw new Error("Invalid script scene space.");
7539
+ if (output.anchor) {
7540
+ const { item, point } = output.anchor;
7541
+ if (output.space !== "screen" || typeof item !== "string" || !item || point && ![point.x, point.y].every((n) => Number.isFinite(n) && n >= 0 && n <= 1)) throw new Error("Invalid screen scene anchor.");
7542
+ }
7493
7543
  if (output.parameters) {
7494
7544
  if (Object.keys(output.parameters).length > 64) throw new Error("A script may declare at most 64 parameters.");
7495
7545
  for (const spec of Object.values(output.parameters)) {
@@ -7513,6 +7563,7 @@ function validateBoardScriptOutput(output, scope) {
7513
7563
  if (!patch || typeof patch !== "object" || Object.keys(patch).some((key) => !DISPLAY_FIELDS.has(key))) throw new Error("Scripts may only present display properties.");
7514
7564
  }
7515
7565
  for (const hit of Object.values(output.hits)) {
7566
+ if (!hit || typeof hit !== "object") throw new Error("Invalid script hit region.");
7516
7567
  if (hit.cursor && ![
7517
7568
  "default",
7518
7569
  "pointer",
@@ -7525,7 +7576,11 @@ function validateBoardScriptOutput(output, scope) {
7525
7576
  "ns-resize",
7526
7577
  "not-allowed"
7527
7578
  ].includes(hit.cursor)) throw new Error("Invalid script cursor.");
7528
- if (![
7579
+ if ("target" in hit) {
7580
+ if (hit.target !== "self" || "item" in hit || "x" in hit) throw new Error("Invalid script hit target.");
7581
+ } else if ("item" in hit) {
7582
+ if (typeof hit.item !== "string" || !Object.hasOwn(output.items, hit.item) || "x" in hit) throw new Error("Hit items must belong to the temporary scene.");
7583
+ } else if (![
7529
7584
  hit.x,
7530
7585
  hit.y,
7531
7586
  hit.width,
@@ -7540,6 +7595,15 @@ function scriptTemporaryPrefix(owner) {
7540
7595
  }
7541
7596
  const preparedOutputs = /* @__PURE__ */ new WeakMap();
7542
7597
  const preparedScopes = /* @__PURE__ */ new WeakMap();
7598
+ const itemOrders = /* @__PURE__ */ new WeakMap();
7599
+ function orderedScriptOwners(document, outputs) {
7600
+ let order = itemOrders.get(document.items);
7601
+ if (!order) {
7602
+ order = new Map(Object.keys(document.items).map((id, index) => [id, index]));
7603
+ itemOrders.set(document.items, order);
7604
+ }
7605
+ return [...outputs.keys()].filter((id) => order.has(id)).sort((a, b) => (order.get(a) ?? 0) - (order.get(b) ?? 0));
7606
+ }
7543
7607
  function prepareBoardScriptOutput(document, owner, output) {
7544
7608
  const previous = preparedOutputs.get(output);
7545
7609
  if (previous?.document === document && previous.owner === owner) return previous;
@@ -7553,6 +7617,7 @@ function prepareBoardScriptOutput(document, owner, output) {
7553
7617
  scope = boardScriptScope(document, owner);
7554
7618
  scopes.set(owner, scope);
7555
7619
  }
7620
+ if (output.anchor && document.items[output.anchor.item] && !scope.has(output.anchor.item)) throw new Error("Scene anchor is outside the script scope.");
7556
7621
  validateBoardScriptOutput(output, scope);
7557
7622
  const presented = /* @__PURE__ */ new Map();
7558
7623
  for (const [id, patch] of Object.entries(output.patches)) {
@@ -7617,11 +7682,11 @@ function prepareBoardScriptOutput(document, owner, output) {
7617
7682
  preparedOutputs.set(output, prepared);
7618
7683
  return prepared;
7619
7684
  }
7620
- function composeBoardScriptItems(document, scene, outputs, incoming = /* @__PURE__ */ new Map(), screen, onError) {
7685
+ function composeBoardScriptItems(document, scene, outputs, incoming = /* @__PURE__ */ new Map(), screen, onError, gestureLayout) {
7621
7686
  const result = new Map(incoming);
7622
7687
  const screenAnchors = /* @__PURE__ */ new Map();
7623
7688
  if (outputs.size === 0) return result;
7624
- for (const owner of Object.keys(document.items)) {
7689
+ for (const owner of orderedScriptOwners(document, outputs)) {
7625
7690
  const output = outputs.get(owner);
7626
7691
  if (!output) continue;
7627
7692
  try {
@@ -7689,15 +7754,23 @@ function composeBoardScriptItems(document, scene, outputs, incoming = /* @__PURE
7689
7754
  }
7690
7755
  }
7691
7756
  if (screenAnchors.size) {
7692
- const layout = createBoardLayout((id) => result.get(id) ?? document.items[id]);
7757
+ const layout = gestureLayout ?? createBoardLayout((id) => result.get(id) ?? document.items[id]);
7693
7758
  for (const [owner, rootId] of screenAnchors) {
7694
7759
  const root = result.get(rootId);
7760
+ const anchor = outputs.get(owner)?.anchor;
7761
+ const target = anchor?.item ?? owner;
7762
+ const exists = Boolean(document.items[target]);
7763
+ const box = exists ? layout.box(target) : {
7764
+ width: 0,
7765
+ height: 0
7766
+ };
7695
7767
  if (root) result.set(rootId, {
7696
7768
  ...root,
7697
- position: applyMatrix$1(layout.matrix(owner), {
7698
- x: 0,
7699
- y: 0
7700
- })
7769
+ opacity: exists ? root.opacity : 0,
7770
+ position: exists ? applyMatrix$1(layout.matrix(target), {
7771
+ x: (anchor?.point?.x ?? 0) * box.width,
7772
+ y: (anchor?.point?.y ?? 0) * box.height
7773
+ }) : root.position
7701
7774
  });
7702
7775
  }
7703
7776
  }
@@ -9268,13 +9341,47 @@ function filePreviewKind(snapshot) {
9268
9341
  if (snapshot.excerpt) return "text";
9269
9342
  return "blank";
9270
9343
  }
9271
- /** Short uppercase type label for a file card (`MD`, `JSON`, `FILE`). */
9272
9344
  function fileTypeLabel(path) {
9345
+ if (isWebPageUrl(path)) return "LINK";
9273
9346
  const name = fileBaseName(path);
9274
9347
  const dot = name.lastIndexOf(".");
9275
9348
  if (dot <= 0) return (name.replace(/^\./, "") || "file").toUpperCase();
9276
9349
  return name.slice(dot + 1).toUpperCase();
9277
9350
  }
9351
+ function isWebPageUrl(path) {
9352
+ if (!/^https?:\/\//i.test(path)) return false;
9353
+ try {
9354
+ const last = new URL(path).pathname.split("/").filter(Boolean).pop() ?? "";
9355
+ return !/\.[a-z\d]{1,8}$/i.test(last);
9356
+ } catch {
9357
+ return false;
9358
+ }
9359
+ }
9360
+ /** Human-readable byte size. Kept as a public helper for custom card renderers. */
9361
+ function formatFileSize(bytes) {
9362
+ if (bytes === void 0 || !Number.isFinite(bytes) || bytes < 0) return "";
9363
+ if (bytes < 1024) return `${bytes} B`;
9364
+ const units = [
9365
+ "KB",
9366
+ "MB",
9367
+ "GB",
9368
+ "TB"
9369
+ ];
9370
+ let value = bytes / 1024;
9371
+ let unit = 0;
9372
+ while (value >= 1024 && unit < units.length - 1) {
9373
+ value /= 1024;
9374
+ unit += 1;
9375
+ }
9376
+ return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;
9377
+ }
9378
+ /** `MD · 12 KB` — empty when neither fact is available. */
9379
+ function fileMetaLine(path, size) {
9380
+ const type = fileTypeLabel(path);
9381
+ const formatted = formatFileSize(size);
9382
+ if (type && formatted) return `${type} · ${formatted}`;
9383
+ return formatted || type;
9384
+ }
9278
9385
  /** Map a file category onto an existing palette token. */
9279
9386
  function fileCategoryAccent(category, palette) {
9280
9387
  switch (category) {
@@ -9449,42 +9556,6 @@ function boardFontFamily(font, stacks) {
9449
9556
  return `"${font.replaceAll("\"", "")}", ${stacks.sans}`;
9450
9557
  }
9451
9558
  //#endregion
9452
- //#region ../sdk/src/board/model/task.ts
9453
- function artifactScore(artifact) {
9454
- const kind = {
9455
- text: 1,
9456
- image: 2,
9457
- audio: 3,
9458
- video: 4
9459
- }[artifact.type];
9460
- if (artifact.type === "text") return [kind, artifact.textExcerpt.length];
9461
- if (artifact.type === "image") return [kind, (artifact.naturalWidth ?? 0) * (artifact.naturalHeight ?? 0)];
9462
- return [
9463
- kind,
9464
- artifact.previewUrl ? 1 : 0,
9465
- artifact.durationMs ? 1 : 0,
9466
- artifact.durationMs ?? 0
9467
- ];
9468
- }
9469
- function compareArtifacts(a, b) {
9470
- const left = artifactScore(a);
9471
- const right = artifactScore(b);
9472
- for (let index = 0; index < Math.max(left.length, right.length); index += 1) {
9473
- const difference = (right[index] ?? 0) - (left[index] ?? 0);
9474
- if (difference !== 0) return difference;
9475
- }
9476
- return 0;
9477
- }
9478
- function featuredTaskArtifact(artifacts) {
9479
- let featured;
9480
- for (const artifact of artifacts) if (!featured || compareArtifacts(artifact, featured) < 0) featured = artifact;
9481
- return featured;
9482
- }
9483
- function taskArtifactPreviewUrl(artifact) {
9484
- if (artifact?.type === "image") return artifact.url;
9485
- if (artifact?.type === "video" || artifact?.type === "audio") return artifact.previewUrl;
9486
- }
9487
- //#endregion
9488
9559
  //#region ../sdk/src/board/model/image-key.ts
9489
9560
  function imageAssetKey(item) {
9490
9561
  if (item.type === "image") {
@@ -26974,8 +27045,8 @@ const DARK = {
26974
27045
  legendary: 16096779
26975
27046
  };
26976
27047
  const LIGHT = {
26977
- bg: 16777215,
26978
- surface: 16053493,
27048
+ bg: 16251129,
27049
+ surface: 16777215,
26979
27050
  hover: 15329772,
26980
27051
  border: 14079706,
26981
27052
  brand: 15222030,
@@ -27158,6 +27229,298 @@ function inspectBoardScene(scene, limit = 100) {
27158
27229
  return diagnostics;
27159
27230
  }
27160
27231
  //#endregion
27232
+ //#region ../sdk/src/board/render/text-fit.ts
27233
+ function fitLineWithEllipsis(line, wrapWidth, measureWidth) {
27234
+ const trimmed = line.replace(/\s+$/u, "");
27235
+ if (!trimmed) return "…";
27236
+ const full = `${trimmed}…`;
27237
+ if (measureWidth(full) <= wrapWidth) return full;
27238
+ let lo = 0;
27239
+ let hi = trimmed.length;
27240
+ let best = "…";
27241
+ while (lo <= hi) {
27242
+ const mid = lo + hi >> 1;
27243
+ const candidate = mid > 0 ? `${trimmed.slice(0, mid)}…` : "…";
27244
+ if (measureWidth(candidate) <= wrapWidth) {
27245
+ best = candidate;
27246
+ lo = mid + 1;
27247
+ } else hi = mid - 1;
27248
+ }
27249
+ return best;
27250
+ }
27251
+ function fitTextToLines(text, value, maxLines, wrapWidth) {
27252
+ const width = Math.max(1, wrapWidth);
27253
+ text.style.wordWrapWidth = width;
27254
+ if (!value || maxLines <= 0) {
27255
+ if (text.text !== "") text.text = "";
27256
+ return;
27257
+ }
27258
+ const charCap = Math.max(maxLines * Math.ceil(width), maxLines * 4);
27259
+ const sample = value.length > charCap ? value.slice(0, charCap) : value;
27260
+ const metrics = CanvasTextMetrics.measureText(sample, text.style);
27261
+ if (!(metrics.lines.length > maxLines || sample !== value)) {
27262
+ if (text.text !== value) text.text = value;
27263
+ return;
27264
+ }
27265
+ const keptCount = Math.min(maxLines, metrics.lines.length);
27266
+ const kept = metrics.lines.slice(0, keptCount);
27267
+ const lastIndex = kept.length - 1;
27268
+ const probe = text.style.clone();
27269
+ probe.wordWrap = false;
27270
+ kept[lastIndex] = fitLineWithEllipsis(kept[lastIndex] ?? "", width, (candidate) => CanvasTextMetrics.measureText(candidate, probe).width);
27271
+ const fitted = kept.join("\n");
27272
+ if (text.text !== fitted) text.text = fitted;
27273
+ }
27274
+ function fitTextToWidth(text, value, width) {
27275
+ text.style.wordWrap = false;
27276
+ const measure = (candidate) => CanvasTextMetrics.measureText(candidate, text.style).width;
27277
+ const fitted = !value || measure(value) <= width ? value : fitLineWithEllipsis(value, width, measure);
27278
+ if (text.text !== fitted) text.text = fitted;
27279
+ }
27280
+ //#endregion
27281
+ //#region ../sdk/src/board/render/text-resolution.ts
27282
+ const MAX_BOARD_RESOLUTION = 2;
27283
+ function getBoardResolution() {
27284
+ return Math.min(globalThis.devicePixelRatio || 1, MAX_BOARD_RESOLUTION);
27285
+ }
27286
+ /**
27287
+ * Zoom buckets for text re-rasterisation. Pixi Text is a bitmap: at high zoom it
27288
+ * looks soft unless we raise its resolution. We quantise zoom into a few
27289
+ * buckets so small zoom changes don't thrash texture regeneration.
27290
+ */
27291
+ const TEXT_ZOOM_BUCKETS = [
27292
+ .5,
27293
+ 1,
27294
+ 1.5,
27295
+ 2,
27296
+ 3,
27297
+ 4
27298
+ ];
27299
+ function textZoomBucket(zoom) {
27300
+ const clamped = Math.max(.1, Math.min(4, zoom));
27301
+ for (const bucket of TEXT_ZOOM_BUCKETS) if (clamped <= bucket) return bucket;
27302
+ return TEXT_ZOOM_BUCKETS[TEXT_ZOOM_BUCKETS.length - 1] ?? 4;
27303
+ }
27304
+ function boardCardDetailed(zoom) {
27305
+ return textZoomBucket(zoom) >= 1;
27306
+ }
27307
+ /** Effective text resolution for a given camera zoom. */
27308
+ function textResolutionForZoom(zoom) {
27309
+ const bucket = textZoomBucket(zoom);
27310
+ return Math.min(getBoardResolution() * Math.max(1, bucket), 6);
27311
+ }
27312
+ /** Update a Pixi text texture only when zoom crosses a resolution bucket. */
27313
+ function syncTextResolution(text, state, zoom) {
27314
+ const resolution = textResolutionForZoom(zoom);
27315
+ if (resolution === state.resolution) return;
27316
+ text.resolution = resolution;
27317
+ state.resolution = resolution;
27318
+ }
27319
+ /** Defer expensive Pixi word-wrap rasterisation during a live resize. */
27320
+ function syncTextWrapWidth(text, state, width, defer) {
27321
+ if (defer || width === state.wrapWidth) return;
27322
+ text.style.wordWrapWidth = width;
27323
+ state.wrapWidth = width;
27324
+ }
27325
+ const ICON = 12;
27326
+ const GAP$2 = 5;
27327
+ const MAX_GROWTH = 1.25;
27328
+ const MIN_READABLE = 7;
27329
+ const SECOND$1 = 1e3;
27330
+ function formatBoardDuration(ms) {
27331
+ const total = Math.max(0, Math.floor(ms / SECOND$1));
27332
+ const hours = Math.floor(total / 3600);
27333
+ const minutes = Math.floor(total % 3600 / 60);
27334
+ const seconds = String(total % 60).padStart(2, "0");
27335
+ return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}` : `${minutes}:${seconds}`;
27336
+ }
27337
+ const PIXEL_GLYPHS = {
27338
+ image: [
27339
+ "######",
27340
+ "#....#",
27341
+ "#.#..#",
27342
+ "#.##.#",
27343
+ "######",
27344
+ "......"
27345
+ ],
27346
+ video: [
27347
+ "######",
27348
+ "#.#..#",
27349
+ "#.##.#",
27350
+ "#.#..#",
27351
+ "######",
27352
+ "......"
27353
+ ],
27354
+ audio: [
27355
+ "...#..",
27356
+ "...##.",
27357
+ "...#.#",
27358
+ "...#..",
27359
+ ".###..",
27360
+ ".###.."
27361
+ ],
27362
+ file: [
27363
+ "####..",
27364
+ "#..##.",
27365
+ "#...##",
27366
+ "#....#",
27367
+ "#....#",
27368
+ "######"
27369
+ ],
27370
+ task: [
27371
+ "..#...",
27372
+ ".###..",
27373
+ "#####.",
27374
+ ".###..",
27375
+ "..#...",
27376
+ "......"
27377
+ ]
27378
+ };
27379
+ function drawLineGlyph(graphics, glyph, color) {
27380
+ const line = {
27381
+ color,
27382
+ width: 1.25,
27383
+ alpha: 1,
27384
+ cap: "round",
27385
+ join: "round"
27386
+ };
27387
+ switch (glyph) {
27388
+ case "image":
27389
+ graphics.roundRect(1, 2, 10, 8, 1.5).stroke(line);
27390
+ graphics.moveTo(2.5, 9).lineTo(5, 6).lineTo(7, 8).lineTo(8.5, 6.5).lineTo(10, 8).stroke(line);
27391
+ return;
27392
+ case "video":
27393
+ graphics.roundRect(1, 2.5, 10, 7, 1.5).stroke(line);
27394
+ graphics.poly([
27395
+ 5,
27396
+ 4.5,
27397
+ 5,
27398
+ 7.5,
27399
+ 7.5,
27400
+ 6
27401
+ ]).fill({ color });
27402
+ return;
27403
+ case "audio":
27404
+ for (const [x, height] of [
27405
+ [2.5, 3],
27406
+ [5, 7],
27407
+ [7.5, 9],
27408
+ [10, 5]
27409
+ ]) graphics.moveTo(x, 6 - height / 2).lineTo(x, 6 + height / 2);
27410
+ graphics.stroke(line);
27411
+ return;
27412
+ case "file":
27413
+ graphics.poly([
27414
+ 2.5,
27415
+ 1,
27416
+ 7.5,
27417
+ 1,
27418
+ 9.5,
27419
+ 3,
27420
+ 9.5,
27421
+ 11,
27422
+ 2.5,
27423
+ 11
27424
+ ]).stroke(line);
27425
+ graphics.moveTo(7.5, 1).lineTo(7.5, 3).lineTo(9.5, 3).stroke(line);
27426
+ return;
27427
+ case "task":
27428
+ graphics.poly([
27429
+ 6,
27430
+ 1,
27431
+ 7.3,
27432
+ 4.7,
27433
+ 11,
27434
+ 6,
27435
+ 7.3,
27436
+ 7.3,
27437
+ 6,
27438
+ 11,
27439
+ 4.7,
27440
+ 7.3,
27441
+ 1,
27442
+ 6,
27443
+ 4.7,
27444
+ 4.7
27445
+ ]).fill({ color });
27446
+ return;
27447
+ }
27448
+ }
27449
+ function drawCardGlyph(graphics, glyph, color, pixel) {
27450
+ if (!pixel) {
27451
+ drawLineGlyph(graphics, glyph, color);
27452
+ return;
27453
+ }
27454
+ PIXEL_GLYPHS[glyph].forEach((row, y) => {
27455
+ for (let x = 0; x < row.length; x += 1) if (row[x] === "#") graphics.rect(x * 2, y * 2, 2, 2);
27456
+ });
27457
+ graphics.fill({ color });
27458
+ }
27459
+ function createCardTag() {
27460
+ const root = new Container({ label: "card-tag" });
27461
+ const icon = new Graphics();
27462
+ root.addChild(icon);
27463
+ root.visible = false;
27464
+ return {
27465
+ root,
27466
+ icon,
27467
+ text: null,
27468
+ sig: "",
27469
+ resolution: 0,
27470
+ width: 0
27471
+ };
27472
+ }
27473
+ function zoomCardTag(tag, zoom) {
27474
+ if (!tag.text || !tag.sig) return;
27475
+ const scale = Math.min(MAX_GROWTH, Math.max(1, 1 / Math.max(zoom, .01)));
27476
+ const span = 17 + tag.text.width;
27477
+ tag.root.scale.set(scale);
27478
+ tag.root.position.set(0, -18 * scale);
27479
+ tag.root.visible = 11 * scale * zoom >= MIN_READABLE && span * scale <= tag.width + .5;
27480
+ }
27481
+ function syncCardTag(tag, input, context) {
27482
+ tag.width = input.width;
27483
+ if (!input.text || input.width <= 24) {
27484
+ tag.sig = "";
27485
+ tag.root.visible = false;
27486
+ return;
27487
+ }
27488
+ if (!tag.text) {
27489
+ tag.resolution = textResolutionForZoom(context.zoom);
27490
+ tag.text = new Text({
27491
+ text: "",
27492
+ style: {
27493
+ fontFamily: context.style.font,
27494
+ fontSize: 11,
27495
+ fontWeight: "500"
27496
+ },
27497
+ resolution: tag.resolution,
27498
+ roundPixels: true
27499
+ });
27500
+ tag.root.addChild(tag.text);
27501
+ }
27502
+ syncTextResolution(tag.text, tag, context.zoom);
27503
+ const sig = [
27504
+ input.glyph,
27505
+ input.text,
27506
+ input.width,
27507
+ context.themeKey
27508
+ ].join("|");
27509
+ if (sig === tag.sig) {
27510
+ zoomCardTag(tag, context.zoom);
27511
+ return;
27512
+ }
27513
+ tag.sig = sig;
27514
+ const color = context.palette.muted;
27515
+ const pixel = context.style.icons === "pixel";
27516
+ drawCardGlyph(tag.icon.clear(), input.glyph, color, pixel);
27517
+ tag.text.style.fill = color;
27518
+ tag.text.style.fontFamily = context.style.font;
27519
+ fitTextToWidth(tag.text, input.text, Math.max(1, input.width - ICON - GAP$2));
27520
+ tag.text.position.set(17, (ICON - tag.text.height) / 2);
27521
+ zoomCardTag(tag, context.zoom);
27522
+ }
27523
+ //#endregion
27161
27524
  //#region ../sdk/src/board/render/clip.ts
27162
27525
  function isClippingFrame(item) {
27163
27526
  return item.type === "frame" && item.props.clip !== false;
@@ -27221,7 +27584,7 @@ function audioWaveformBars(seed, count) {
27221
27584
  }
27222
27585
  return bars;
27223
27586
  }
27224
- function drawAudioWaveform(graphics, seed, rect, color, alpha = .72) {
27587
+ function drawAudioWaveform(graphics, seed, rect, color, alpha = .72, square = false) {
27225
27588
  const count = Math.max(8, Math.min(DEFAULT_BARS, Math.floor(rect.width / 4)));
27226
27589
  const step = rect.width / count;
27227
27590
  const barWidth = Math.max(1.5, step - BAR_GAP);
@@ -27230,53 +27593,225 @@ function drawAudioWaveform(graphics, seed, rect, color, alpha = .72) {
27230
27593
  const bars = audioWaveformBars(seed, count);
27231
27594
  for (let index = 0; index < count; index += 1) {
27232
27595
  const height = Math.max(2, (bars[index] ?? .4) * maxHeight);
27233
- graphics.roundRect(rect.x + index * step, centerY - height / 2, barWidth, height, barWidth / 2).fill({
27234
- color,
27235
- alpha
27236
- });
27596
+ const x = rect.x + index * step;
27597
+ if (square) graphics.rect(x, centerY - height / 2, barWidth, height);
27598
+ else graphics.roundRect(x, centerY - height / 2, barWidth, height, barWidth / 2);
27237
27599
  }
27600
+ graphics.fill({
27601
+ color,
27602
+ alpha
27603
+ });
27238
27604
  }
27239
27605
  //#endregion
27240
- //#region ../sdk/src/board/render/text-resolution.ts
27241
- const MAX_BOARD_RESOLUTION = 2;
27242
- function getBoardResolution() {
27243
- return Math.min(globalThis.devicePixelRatio || 1, MAX_BOARD_RESOLUTION);
27244
- }
27245
- /**
27246
- * Zoom buckets for text re-rasterisation. Pixi Text is a bitmap: at high zoom it
27247
- * looks soft unless we raise its resolution. We quantise zoom into a few
27248
- * buckets so small zoom changes don't thrash texture regeneration.
27249
- */
27250
- const TEXT_ZOOM_BUCKETS = [
27251
- .5,
27252
- 1,
27253
- 1.5,
27254
- 2,
27255
- 3,
27256
- 4
27257
- ];
27258
- function textZoomBucket(zoom) {
27259
- const clamped = Math.max(.1, Math.min(4, zoom));
27260
- for (const bucket of TEXT_ZOOM_BUCKETS) if (clamped <= bucket) return bucket;
27261
- return TEXT_ZOOM_BUCKETS[TEXT_ZOOM_BUCKETS.length - 1] ?? 4;
27606
+ //#region ../sdk/src/board/render/stroke.ts
27607
+ const HEAD_SPREAD = Math.PI / 6;
27608
+ function dashPattern(dash, width) {
27609
+ if (dash === "dashed") return [Math.max(6, width * 4), Math.max(4, width * 3)];
27610
+ if (dash === "dotted") return [Math.max(.5, width * .01), Math.max(3, width * 2)];
27611
+ return null;
27262
27612
  }
27263
- /** Effective text resolution for a given camera zoom. */
27264
- function textResolutionForZoom(zoom) {
27265
- const bucket = textZoomBucket(zoom);
27266
- return Math.min(getBoardResolution() * Math.max(1, bucket), 6);
27613
+ function trimPolyline(path, trim) {
27614
+ if (trim === void 0 || trim >= 1 || path.length < 2) return path;
27615
+ if (trim <= 0) return [];
27616
+ let total = 0;
27617
+ for (let index = 1; index < path.length; index += 1) {
27618
+ const a = path[index - 1];
27619
+ const b = path[index];
27620
+ total += Math.hypot(b.x - a.x, b.y - a.y);
27621
+ }
27622
+ let remaining = total * trim;
27623
+ const out = [path[0]];
27624
+ for (let index = 1; index < path.length; index += 1) {
27625
+ const a = path[index - 1];
27626
+ const b = path[index];
27627
+ const length = Math.hypot(b.x - a.x, b.y - a.y);
27628
+ if (length >= remaining) {
27629
+ const t = length > 0 ? remaining / length : 0;
27630
+ out.push(worldPoint(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t));
27631
+ return out;
27632
+ }
27633
+ out.push(b);
27634
+ remaining -= length;
27635
+ }
27636
+ return out;
27267
27637
  }
27268
- /** Update a Pixi text texture only when zoom crosses a resolution bucket. */
27269
- function syncTextResolution(text, state, zoom) {
27270
- const resolution = textResolutionForZoom(zoom);
27271
- if (resolution === state.resolution) return;
27272
- text.resolution = resolution;
27273
- state.resolution = resolution;
27638
+ function tracePolyline(graphics, path, pattern, closed = false, offset = 0) {
27639
+ const points = closed && path.length > 2 ? [...path, path[0]] : path;
27640
+ if (points.length < 2) return;
27641
+ if (!pattern) {
27642
+ const first = points[0];
27643
+ graphics.moveTo(first.x, first.y);
27644
+ for (let index = 1; index < points.length; index += 1) {
27645
+ const point = points[index];
27646
+ graphics.lineTo(point.x, point.y);
27647
+ }
27648
+ if (closed) graphics.closePath();
27649
+ return;
27650
+ }
27651
+ const [dash, gap] = pattern;
27652
+ const phase = Math.max(0, offset) % (dash + gap);
27653
+ let penDown = phase < dash;
27654
+ let fresh = penDown;
27655
+ let remaining = penDown ? dash - phase : dash + gap - phase;
27656
+ for (let index = 1; index < points.length; index += 1) {
27657
+ const a = points[index - 1];
27658
+ const b = points[index];
27659
+ const dx = b.x - a.x;
27660
+ const dy = b.y - a.y;
27661
+ const length = Math.hypot(dx, dy);
27662
+ let travelled = 0;
27663
+ while (length - travelled > 1e-4) {
27664
+ const step = Math.min(length - travelled, remaining);
27665
+ if (penDown) {
27666
+ const t0 = travelled / length;
27667
+ const t1 = (travelled + step) / length;
27668
+ if (fresh) graphics.moveTo(a.x + dx * t0, a.y + dy * t0);
27669
+ fresh = false;
27670
+ graphics.lineTo(a.x + dx * t1, a.y + dy * t1);
27671
+ }
27672
+ travelled += step;
27673
+ remaining -= step;
27674
+ if (remaining <= 1e-4) {
27675
+ penDown = !penDown;
27676
+ fresh = penDown;
27677
+ remaining = penDown ? dash : gap;
27678
+ }
27679
+ }
27680
+ }
27274
27681
  }
27275
- /** Defer expensive Pixi word-wrap rasterisation during a live resize. */
27276
- function syncTextWrapWidth(text, state, width, defer) {
27277
- if (defer || width === state.wrapWidth) return;
27278
- text.style.wordWrapWidth = width;
27279
- state.wrapWidth = width;
27682
+ function traceArrowhead(graphics, tip, angle, size) {
27683
+ graphics.moveTo(tip.x - size * Math.cos(angle - HEAD_SPREAD), tip.y - size * Math.sin(angle - HEAD_SPREAD)).lineTo(tip.x, tip.y).lineTo(tip.x - size * Math.cos(angle + HEAD_SPREAD), tip.y - size * Math.sin(angle + HEAD_SPREAD));
27684
+ }
27685
+ function endAngle(path, atStart) {
27686
+ if (path.length < 2) return null;
27687
+ const tip = atStart ? path[0] : path[path.length - 1];
27688
+ for (let offset = 1; offset < path.length; offset += 1) {
27689
+ const other = atStart ? path[offset] : path[path.length - 1 - offset];
27690
+ if (Math.hypot(tip.x - other.x, tip.y - other.y) > .001) return Math.atan2(tip.y - other.y, tip.x - other.x);
27691
+ }
27692
+ return null;
27693
+ }
27694
+ function roundRectPoints(width, height, radius) {
27695
+ const r = Math.min(radius, width / 2, height / 2);
27696
+ if (r <= 0) return [
27697
+ worldPoint(0, 0),
27698
+ worldPoint(width, 0),
27699
+ worldPoint(width, height),
27700
+ worldPoint(0, height),
27701
+ worldPoint(0, 0)
27702
+ ];
27703
+ const corner = (cx, cy, from) => Array.from({ length: 9 }, (_, index) => {
27704
+ const angle = from + index / 8 * (Math.PI / 2);
27705
+ return worldPoint(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
27706
+ });
27707
+ return [
27708
+ ...corner(width - r, r, -Math.PI / 2),
27709
+ ...corner(width - r, height - r, 0),
27710
+ ...corner(r, height - r, Math.PI / 2),
27711
+ ...corner(r, r, Math.PI),
27712
+ worldPoint(width - r, 0)
27713
+ ];
27714
+ }
27715
+ const SHADOW_LAYERS = 5;
27716
+ function traceCardOutline(graphics, rect, style, grow = 0) {
27717
+ const x = rect.x - grow;
27718
+ const y = rect.y - grow;
27719
+ const width = Math.max(0, rect.width + grow * 2);
27720
+ const height = Math.max(0, rect.height + grow * 2);
27721
+ if (style.corner === "pixel") return tracePixelRect(graphics, x, y, width, height, 2);
27722
+ const radius = Math.min(Math.max(0, style.radius + grow), width / 2, height / 2);
27723
+ return radius > 0 ? graphics.roundRect(x, y, width, height, radius) : graphics.rect(x, y, width, height);
27724
+ }
27725
+ function tracePixelRect(graphics, x, y, width, height, step) {
27726
+ const s = Math.min(step, width / 4, height / 4);
27727
+ if (s <= 0) return graphics.rect(x, y, width, height);
27728
+ return graphics.poly(pixelRectPoints(x, y, width, height, s).flatMap((point) => [point.x, point.y]));
27729
+ }
27730
+ function pixelRectPoints(x, y, width, height, s) {
27731
+ const r = x + width;
27732
+ const b = y + height;
27733
+ const p = worldPoint;
27734
+ return [
27735
+ p(x, y + s * 2),
27736
+ p(x + s, y + s * 2),
27737
+ p(x + s, y + s),
27738
+ p(x + s * 2, y + s),
27739
+ p(x + s * 2, y),
27740
+ p(r - s * 2, y),
27741
+ p(r - s * 2, y + s),
27742
+ p(r - s, y + s),
27743
+ p(r - s, y + s * 2),
27744
+ p(r, y + s * 2),
27745
+ p(r, b - s * 2),
27746
+ p(r - s, b - s * 2),
27747
+ p(r - s, b - s),
27748
+ p(r - s * 2, b - s),
27749
+ p(r - s * 2, b),
27750
+ p(x + s * 2, b),
27751
+ p(x + s * 2, b - s),
27752
+ p(x + s, b - s),
27753
+ p(x + s, b - s * 2),
27754
+ p(x, b - s * 2)
27755
+ ];
27756
+ }
27757
+ function drawCardShadow(graphics, rect, style, shadow) {
27758
+ if (!shadow || shadow.alpha <= 0 || shadow.blur <= 0) return;
27759
+ const offset = {
27760
+ ...rect,
27761
+ y: rect.y + shadow.y
27762
+ };
27763
+ const reach = (index) => (1 - index / SHADOW_LAYERS) ** 2;
27764
+ for (let index = SHADOW_LAYERS; index >= 1; index -= 1) {
27765
+ const alpha = shadow.alpha * (reach(index - 1) - reach(index));
27766
+ traceCardOutline(graphics, offset, style, shadow.blur * index / SHADOW_LAYERS).fill({
27767
+ color: shadow.color,
27768
+ alpha
27769
+ });
27770
+ traceCardOutline(graphics, rect, style).cut();
27771
+ }
27772
+ }
27773
+ function cardShadow(style, elevation) {
27774
+ const shadow = style.shadow;
27775
+ if (!shadow || elevation === 0) return null;
27776
+ if (elevation === 1) return shadow;
27777
+ return {
27778
+ ...shadow,
27779
+ alpha: Math.min(1, shadow.alpha * 1.6),
27780
+ blur: shadow.blur * 1.6,
27781
+ y: shadow.y * 2 + 4
27782
+ };
27783
+ }
27784
+ function drawCardSurface(graphics, rect, style, surface) {
27785
+ drawCardShadow(graphics, rect, style, cardShadow(style, surface.elevation ?? 0));
27786
+ traceCardOutline(graphics, rect, style).fill({
27787
+ color: surface.fill,
27788
+ alpha: surface.fillAlpha ?? 1
27789
+ });
27790
+ if (surface.border != null) traceCardOutline(graphics, rect, style).stroke({
27791
+ color: surface.border,
27792
+ width: 1,
27793
+ alpha: surface.borderAlpha ?? 1,
27794
+ alignment: 1
27795
+ });
27796
+ return graphics;
27797
+ }
27798
+ function containRect(width, height, imageWidth, imageHeight) {
27799
+ if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return {
27800
+ x: 0,
27801
+ y: 0,
27802
+ width: 0,
27803
+ height: 0
27804
+ };
27805
+ const scale = Math.min(width / imageWidth, height / imageHeight);
27806
+ return {
27807
+ x: (width - imageWidth * scale) / 2,
27808
+ y: (height - imageHeight * scale) / 2,
27809
+ width: imageWidth * scale,
27810
+ height: imageHeight * scale
27811
+ };
27812
+ }
27813
+ function cardElevation(context, id) {
27814
+ return context.liftedIds.has(id) ? 2 : 1;
27280
27815
  }
27281
27816
  //#endregion
27282
27817
  //#region ../sdk/src/board/render/renderers/base-card-renderer.ts
@@ -27288,7 +27823,10 @@ function createLabel(text, style) {
27288
27823
  return new Text({
27289
27824
  ...TEXT_OPTIONS,
27290
27825
  text,
27291
- style
27826
+ style: {
27827
+ fontFamily: BOARD_FONT_STACK,
27828
+ ...style
27829
+ }
27292
27830
  });
27293
27831
  }
27294
27832
  function positionShell(root, item) {
@@ -27370,796 +27908,162 @@ function drawFarStroke(graphics, points, style) {
27370
27908
  });
27371
27909
  }
27372
27910
  //#endregion
27373
- //#region ../sdk/src/board/render/renderers/file-card-renderer.ts
27374
- const RADIUS$5 = 4;
27375
- const PADDING$1 = 10;
27376
- const COVER_RATIO = .56;
27377
- const MIN_BODY_FOR_COVER = 38;
27378
- const TITLE_SIZE = 13;
27379
- const TITLE_LINE = TITLE_SIZE * 1.35;
27380
- const TITLE_MAX_LINES = 2;
27381
- const EXCERPT_SIZE = 11;
27382
- const EXCERPT_LINE = EXCERPT_SIZE * 1.45;
27383
- const EXCERPT_MAX_LINES = 3;
27384
- const TYPE_MARK_SIZE = 28;
27385
- const GAP = 4;
27386
- const STRIPE = 2;
27387
- const LOD_TITLE_ZOOM = .35;
27388
- const LOD_BODY_ZOOM = .6;
27389
- const MAX_DASHES = 40;
27390
- const partsByContainer$12 = /* @__PURE__ */ new WeakMap();
27391
- function detailFor(zoom) {
27392
- if (zoom < LOD_TITLE_ZOOM) return "plate";
27393
- if (zoom < LOD_BODY_ZOOM) return "title";
27394
- return "full";
27395
- }
27396
- function coverHeight(item, height) {
27397
- if (filePreviewKind(item.props.snapshot) !== "cover") return 0;
27398
- const ideal = Math.round(height * COVER_RATIO);
27399
- return Math.max(0, Math.min(ideal, height - MIN_BODY_FOR_COVER));
27400
- }
27401
- function fitLineWithEllipsis(line, wrapWidth, measureWidth) {
27402
- const trimmed = line.replace(/\s+$/u, "");
27403
- if (!trimmed) return "…";
27404
- const full = `${trimmed}…`;
27405
- if (measureWidth(full) <= wrapWidth) return full;
27406
- let lo = 0;
27407
- let hi = trimmed.length;
27408
- let best = "…";
27409
- while (lo <= hi) {
27410
- const mid = lo + hi >> 1;
27411
- const candidate = mid > 0 ? `${trimmed.slice(0, mid)}…` : "…";
27412
- if (measureWidth(candidate) <= wrapWidth) {
27413
- best = candidate;
27414
- lo = mid + 1;
27415
- } else hi = mid - 1;
27416
- }
27417
- return best;
27418
- }
27419
- function fitTextToLines(text, value, maxLines, wrapWidth) {
27420
- const width = Math.max(1, wrapWidth);
27421
- text.style.wordWrapWidth = width;
27422
- if (!value || maxLines <= 0) {
27423
- if (text.text !== "") text.text = "";
27424
- return;
27425
- }
27426
- const charCap = Math.max(maxLines * Math.ceil(width), maxLines * 4);
27427
- const sample = value.length > charCap ? value.slice(0, charCap) : value;
27428
- const metrics = CanvasTextMetrics.measureText(sample, text.style);
27429
- if (!(metrics.lines.length > maxLines || sample !== value)) {
27430
- if (text.text !== value) text.text = value;
27911
+ //#region ../sdk/src/board/render/renderers/media-key.ts
27912
+ const MEDIA_SCRIM = 0;
27913
+ const MEDIA_INK = 16777215;
27914
+ function drawMediaPlayKey(graphics, cx, cy, radius, context, solid = false) {
27915
+ const face = solid ? context.palette.text : MEDIA_SCRIM;
27916
+ const ink = solid ? context.palette.surface : MEDIA_INK;
27917
+ if (context.style.corner === "pixel") {
27918
+ const size = Math.round(radius * 1.6);
27919
+ tracePixelRect(graphics, Math.round(cx - size / 2), Math.round(cy - size / 2), size, size, 2).fill({
27920
+ color: face,
27921
+ alpha: solid ? 1 : .62
27922
+ });
27923
+ const cell = Math.max(1, Math.round(size / 14));
27924
+ const x = Math.round(cx - cell * 1.5);
27925
+ const y = Math.round(cy - cell * 3.5);
27926
+ for (let row = 0; row < 7; row += 1) graphics.rect(x, y + row * cell, (4 - Math.abs(row - 3)) * cell, cell);
27927
+ graphics.fill({ color: ink });
27431
27928
  return;
27432
27929
  }
27433
- const keptCount = Math.min(maxLines, metrics.lines.length);
27434
- const kept = metrics.lines.slice(0, keptCount);
27435
- const lastIndex = kept.length - 1;
27436
- const probe = text.style.clone();
27437
- probe.wordWrap = false;
27438
- kept[lastIndex] = fitLineWithEllipsis(kept[lastIndex] ?? "", width, (candidate) => CanvasTextMetrics.measureText(candidate, probe).width);
27439
- const fitted = kept.join("\n");
27440
- if (text.text !== fitted) text.text = fitted;
27441
- }
27442
- function linesInRoom(room, lineHeight, max) {
27443
- if (room < lineHeight * .85) return 0;
27444
- return Math.max(0, Math.min(max, Math.floor((room + .5) / lineHeight)));
27930
+ graphics.circle(cx, cy, radius).fill({
27931
+ color: face,
27932
+ alpha: solid ? 1 : .45
27933
+ });
27934
+ const size = radius * .5;
27935
+ graphics.poly([
27936
+ cx - size * .45,
27937
+ cy - size * .75,
27938
+ cx + size * .8,
27939
+ cy,
27940
+ cx - size * .45,
27941
+ cy + size * .75
27942
+ ]).fill({
27943
+ color: ink,
27944
+ alpha: .96
27945
+ });
27445
27946
  }
27446
- function containCoverRect(width, height, imageWidth, imageHeight) {
27447
- if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return {
27448
- x: 0,
27449
- y: 0,
27450
- width: 0,
27451
- height: 0
27452
- };
27453
- const scale = Math.min(width / imageWidth, height / imageHeight);
27454
- const renderedWidth = imageWidth * scale;
27455
- const renderedHeight = imageHeight * scale;
27947
+ const GAP$1 = 12;
27948
+ const WAVE_HEIGHT = 16;
27949
+ const partsByContainer$12 = /* @__PURE__ */ new WeakMap();
27950
+ function audioKeyCenter(width, height) {
27951
+ const radius = Math.min(16, Math.max(8, height / 2 - 8), width / 4);
27456
27952
  return {
27457
- x: (width - renderedWidth) / 2,
27458
- y: (height - renderedHeight) / 2,
27459
- width: renderedWidth,
27460
- height: renderedHeight
27953
+ x: 14 + radius,
27954
+ y: height / 2,
27955
+ radius
27461
27956
  };
27462
27957
  }
27463
- function layoutCover(sprite, width, height, texture) {
27464
- const rect = containCoverRect(width, height, texture.width, texture.height);
27465
- sprite.position.set(rect.x, rect.y);
27466
- sprite.width = rect.width;
27467
- sprite.height = rect.height;
27468
- }
27469
- function syncClip(parts, width, height) {
27470
- parts.clip.clear().roundRect(0, 0, Math.max(1, width), Math.max(1, height), RADIUS$5).fill({ color: 16777215 });
27471
- }
27472
27958
  function sync$12(container, item, context) {
27473
27959
  const parts = partsByContainer$12.get(container);
27474
27960
  if (!parts) return;
27475
27961
  positionShell(parts.root, item);
27476
27962
  const { width, height } = item.frame;
27477
- const selected = context.selectedIds.has(item.id);
27478
- const hovered = context.hoveredId === item.id;
27479
- const detail = detailFor(context.zoom);
27480
- const key = context.assetKey(item);
27481
- const texture = key ? context.getTexture(key) : null;
27482
- const coverFailed = Boolean(key && !texture && context.hasError(key));
27483
- const fileState = context.fileState(item.props.src);
27484
- const band = coverHeight(item, height);
27485
- const kind = filePreviewKind(item.props.snapshot);
27486
- const category = fileCategory(item.props.src, item.props.snapshot?.mimeType);
27487
- const accent = fileCategoryAccent(category, context.palette);
27488
- syncTextResolution(parts.title, parts.titleRes, context.zoom);
27489
- syncTextResolution(parts.excerpt, parts.excerptRes, context.zoom);
27490
- syncTextResolution(parts.typeMark, parts.typeMarkRes, context.zoom);
27491
- const visualSig = [
27492
- key ?? "",
27963
+ const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
27964
+ const durationMs = item.props.snapshot?.durationMs;
27965
+ const detailed = boardCardDetailed(context.zoom);
27966
+ syncTextResolution(parts.title, parts, context.zoom);
27967
+ const elevation = cardElevation(context, item.id);
27968
+ const sig = [
27493
27969
  width,
27494
27970
  height,
27495
- selected,
27496
- hovered,
27497
- detail,
27498
- texture ? `${texture.width}x${texture.height}` : "none",
27499
- coverFailed,
27500
- fileState,
27501
- kind,
27502
- category,
27503
- context.colorScheme,
27504
- context.palette.surface,
27505
- context.palette.hover,
27506
- accent
27971
+ title,
27972
+ durationMs ?? "",
27973
+ detailed,
27974
+ elevation,
27975
+ item.props.src,
27976
+ item.props.snapshot?.mtimeMs ?? "",
27977
+ context.themeKey
27507
27978
  ].join("|");
27508
- if (visualSig !== parts.visualSig) {
27509
- parts.visualSig = visualSig;
27510
- syncClip(parts, width, height);
27511
- parts.plate.clear();
27512
- parts.plate.roundRect(0, 0, width, height, RADIUS$5).fill({
27513
- color: context.palette.surface,
27514
- alpha: .98
27515
- }).roundRect(0, 0, width, height, RADIUS$5).stroke({
27516
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
27517
- width: selected ? 2 : 1,
27518
- alpha: selected ? .95 : .85
27519
- });
27520
- if (fileState !== "ok") {
27521
- const dash = Math.max(6, width / MAX_DASHES / 2);
27522
- for (let x = 0; x < width; x += dash * 2) parts.plate.moveTo(x, .5).lineTo(Math.min(x + dash, width), .5);
27523
- parts.plate.stroke({
27524
- color: context.palette.muted,
27525
- width: 2,
27526
- alpha: fileState === "missing" ? .9 : .5
27527
- });
27528
- }
27529
- const showCover = band > 0 && Boolean(texture);
27530
- if (band > 0) parts.plate.rect(1, 1, width - 2, band - 1).fill({
27531
- color: context.palette.hover,
27532
- alpha: coverFailed ? .35 : .6
27533
- });
27534
- if (showCover && texture) {
27535
- if (parts.cover.texture !== texture) parts.cover.texture = texture;
27536
- layoutCover(parts.cover, width, band, texture);
27537
- parts.coverMask.clear().roundRect(0, 0, width, band, RADIUS$5).fill({ color: 16777215 });
27538
- parts.coverMask.rect(0, band - RADIUS$5, width, RADIUS$5).fill({ color: 16777215 });
27539
- }
27540
- parts.cover.visible = showCover;
27541
- parts.coverMask.visible = showCover;
27542
- if (!band) parts.plate.rect(1, 1, STRIPE, height - 2).fill({
27543
- color: accent,
27544
- alpha: .55
27545
- });
27546
- parts.title.visible = detail !== "plate";
27547
- parts.excerpt.visible = detail === "full";
27548
- parts.typeMark.visible = kind === "blank" && detail !== "plate";
27549
- }
27550
- if (detail === "plate") {
27551
- parts.typeMark.visible = false;
27552
- return;
27553
- }
27554
- const title = item.props.snapshot?.title || fileStem(item.props.src);
27555
- const excerpt = item.props.snapshot?.excerpt ?? "";
27556
- const mark = fileTypeLabel(item.props.src);
27557
- const innerWidth = Math.max(1, width - 20);
27558
- const showTypeMark = kind === "blank";
27559
- const textSig = [
27560
- title,
27561
- excerpt,
27562
- mark,
27563
- detail,
27564
- innerWidth,
27565
- band,
27566
- height,
27567
- kind,
27568
- context.palette.text,
27569
- context.palette.muted,
27570
- accent
27571
- ].join("|");
27572
- if (textSig === parts.textSig) return;
27573
- parts.textSig = textSig;
27574
- const top = band > 0 ? band + PADDING$1 * .8 : PADDING$1;
27575
- const contentBottom = height - PADDING$1;
27576
- let cursor = top;
27577
- if (showTypeMark) {
27578
- const markSize = Math.max(18, Math.min(TYPE_MARK_SIZE, Math.round(height * .22)));
27579
- parts.typeMark.style.fill = accent;
27580
- parts.typeMark.style.fontSize = markSize;
27581
- parts.typeMark.style.lineHeight = markSize * 1.1;
27582
- if (parts.typeMark.text !== mark) parts.typeMark.text = mark;
27583
- parts.typeMark.position.set(PADDING$1, cursor);
27584
- parts.typeMark.visible = true;
27585
- cursor += parts.typeMark.height + GAP;
27586
- } else parts.typeMark.visible = false;
27587
- const titleLines = linesInRoom(Math.max(0, contentBottom - cursor), TITLE_LINE, TITLE_MAX_LINES);
27588
- parts.title.style.fill = context.palette.text;
27589
- fitTextToLines(parts.title, title, titleLines, innerWidth);
27590
- parts.title.position.set(PADDING$1, cursor);
27591
- parts.title.visible = titleLines > 0;
27592
- if (detail !== "full") {
27593
- parts.excerpt.visible = false;
27594
- return;
27595
- }
27596
- const excerptTop = cursor + (titleLines > 0 ? parts.title.height + GAP : 0);
27597
- const excerptLines = linesInRoom(contentBottom - excerptTop, EXCERPT_LINE, EXCERPT_MAX_LINES);
27598
- const showExcerpt = Boolean(excerpt) && excerptLines > 0;
27599
- if (showExcerpt) {
27600
- parts.excerpt.style.fill = context.palette.muted;
27601
- fitTextToLines(parts.excerpt, excerpt, excerptLines, innerWidth);
27602
- parts.excerpt.position.set(PADDING$1, excerptTop);
27603
- }
27604
- parts.excerpt.visible = showExcerpt;
27605
- }
27606
- const fileCardRenderer = {
27607
- id: "file-card",
27608
- canRender: (item) => item.type === "file",
27609
- create: (item, context) => {
27610
- const root = new Container();
27611
- const plate = new Graphics();
27612
- const body = new Container();
27613
- const clip = new Graphics();
27614
- const cover = new Sprite(Texture.EMPTY);
27615
- const coverMask = new Graphics();
27616
- cover.mask = coverMask;
27617
- const resolution = 1;
27618
- const title = new Text({
27619
- text: "",
27620
- style: {
27621
- fill: context.palette.text,
27622
- fontFamily: BOARD_FONT_STACK,
27623
- fontSize: TITLE_SIZE,
27624
- fontWeight: "600",
27625
- wordWrap: true,
27626
- breakWords: true,
27627
- lineHeight: TITLE_LINE
27628
- },
27629
- resolution,
27630
- roundPixels: true
27631
- });
27632
- const excerpt = new Text({
27633
- text: "",
27634
- style: {
27635
- fill: context.palette.muted,
27636
- fontFamily: BOARD_FONT_STACK,
27637
- fontSize: EXCERPT_SIZE,
27638
- fontWeight: "400",
27639
- wordWrap: true,
27640
- breakWords: true,
27641
- lineHeight: EXCERPT_LINE
27642
- },
27643
- resolution,
27644
- roundPixels: true
27645
- });
27646
- const typeMark = new Text({
27647
- text: "",
27648
- style: {
27649
- fill: context.palette.muted,
27650
- fontFamily: BOARD_MONO_FONT_STACK,
27651
- fontSize: TYPE_MARK_SIZE,
27652
- fontWeight: "700",
27653
- lineHeight: TYPE_MARK_SIZE * 1.1
27654
- },
27655
- resolution,
27656
- roundPixels: true
27657
- });
27658
- body.mask = clip;
27659
- body.addChild(cover, coverMask, typeMark, title, excerpt);
27660
- root.addChild(plate, body, clip);
27661
- partsByContainer$12.set(root, {
27662
- root,
27663
- plate,
27664
- body,
27665
- clip,
27666
- cover,
27667
- coverMask,
27668
- typeMark,
27669
- title,
27670
- excerpt,
27671
- visualSig: "",
27672
- textSig: "",
27673
- titleRes: { resolution },
27674
- excerptRes: { resolution },
27675
- typeMarkRes: { resolution }
27676
- });
27677
- if (item.type === "file") sync$12(root, item, context);
27678
- return root;
27679
- },
27680
- update: (container, item, context) => {
27681
- if (item.type === "file") sync$12(container, item, context);
27682
- },
27683
- renderFar: (graphics, item, context) => {
27684
- const category = fileCategory(item.type === "file" ? item.props.src : "", item.type === "file" ? item.props.snapshot?.mimeType : void 0);
27685
- drawFarPlate(graphics, item.frame, {
27686
- fill: context.palette.surface,
27687
- fillAlpha: .96,
27688
- accent: fileCategoryAccent(category, context.palette),
27689
- accentAlpha: .45
27690
- });
27691
- },
27692
- destroy: (container) => {
27693
- partsByContainer$12.get(container)?.root.destroy({ children: true });
27694
- partsByContainer$12.delete(container);
27695
- }
27696
- };
27697
- //#endregion
27698
- //#region ../sdk/src/board/render/renderers/task-card-renderer.ts
27699
- const RADIUS$4 = 4;
27700
- const PADDING = 12;
27701
- const META_HEIGHT = 28;
27702
- const TASK_CARD_FULL_DETAIL_ZOOM = .55;
27703
- const PLAY_BADGE_RADIUS = 15;
27704
- const partsByContainer$11 = /* @__PURE__ */ new WeakMap();
27705
- function previewKindFor(artifact) {
27706
- if (!artifact) return "empty";
27707
- if (artifact.type === "image" || artifact.type === "video") return "texture";
27708
- if (artifact.type === "audio") return artifact.previewUrl ? "texture" : "waveform";
27709
- return artifact.textExcerpt ? "text" : "empty";
27710
- }
27711
- function stateSurfaceFor(item, kind, hasTexture, previewFailed) {
27712
- if (kind === "texture" && !hasTexture) {
27713
- if (item.props.snapshot.status === "failed") return "failed";
27714
- return previewFailed ? "unavailable" : "media-loading";
27715
- }
27716
- if (kind !== "empty") return null;
27717
- if (item.props.snapshot.status === "failed") return "failed";
27718
- if (item.props.snapshot.status === "running") return "generating";
27719
- if (item.props.snapshot.status === "pending") return "queued";
27720
- return "empty";
27721
- }
27722
- function stateLabel(surface) {
27723
- switch (surface) {
27724
- case "generating": return "Generating";
27725
- case "queued": return "Queued";
27726
- case "failed": return "Failed";
27727
- case "unavailable": return "Preview unavailable";
27728
- case "empty": return "No preview";
27729
- default: return "";
27730
- }
27731
- }
27732
- function statusColor(item, context) {
27733
- if (item.props.snapshot.status === "failed") return context.colors.rose.stroke;
27734
- if (item.props.snapshot.status === "running") return context.colors.brand.stroke;
27735
- return context.colors.neutral.stroke;
27736
- }
27737
- function metadata(item) {
27738
- const { artifactCount, artifacts } = item.props.snapshot;
27739
- const extra = artifactCount > artifacts.length ? `${artifacts.length}/${artifactCount}` : artifactCount > 1 ? `+${artifactCount - 1}` : null;
27740
- return [item.props.snapshot.model, extra].filter(Boolean).join(" · ");
27741
- }
27742
- function containTaskPreviewRect(width, height, imageWidth, imageHeight) {
27743
- if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return {
27979
+ if (sig === parts.sig) return;
27980
+ parts.sig = sig;
27981
+ drawCardSurface(parts.plate.clear(), {
27744
27982
  x: 0,
27745
27983
  y: 0,
27746
- width: 0,
27747
- height: 0
27748
- };
27749
- const scale = Math.min(width / imageWidth, height / imageHeight);
27750
- const renderedWidth = imageWidth * scale;
27751
- const renderedHeight = imageHeight * scale;
27752
- return {
27753
- x: (width - renderedWidth) / 2,
27754
- y: (height - renderedHeight) / 2,
27755
- width: renderedWidth,
27756
- height: renderedHeight
27757
- };
27758
- }
27759
- function drawPlayBadge(graphics, rect, context) {
27760
- const radius = Math.min(PLAY_BADGE_RADIUS, Math.max(8, Math.min(rect.width, rect.height) * .16));
27761
- const cx = rect.x + rect.width / 2;
27762
- const cy = rect.y + rect.height / 2;
27763
- graphics.circle(cx, cy, radius).fill({
27764
- color: context.palette.bg,
27765
- alpha: .64
27766
- });
27767
- const size = radius * .72;
27768
- graphics.moveTo(cx - size * .34, cy - size * .56).lineTo(cx + size * .62, cy).lineTo(cx - size * .34, cy + size * .56).closePath().fill({
27769
- color: context.palette.text,
27770
- alpha: .94
27771
- });
27772
- }
27773
- function drawStateMark(graphics, surface, cx, cy, color) {
27774
- if (surface === "generating" || surface === "media-loading") {
27775
- graphics.circle(cx, cy, 4).fill({
27776
- color,
27777
- alpha: .9
27778
- });
27779
- graphics.circle(cx - 12, cy + 7, 2.5).fill({
27780
- color,
27781
- alpha: .48
27782
- });
27783
- graphics.circle(cx + 11, cy + 6, 3).fill({
27784
- color,
27785
- alpha: .68
27786
- });
27787
- graphics.circle(cx + 3, cy - 12, 2).fill({
27788
- color,
27789
- alpha: .36
27790
- });
27791
- return;
27792
- }
27793
- if (surface === "queued") {
27794
- graphics.circle(cx, cy, 10).stroke({
27795
- color,
27796
- width: 1.5,
27797
- alpha: .62
27798
- }).circle(cx, cy, 3).fill({
27799
- color,
27800
- alpha: .78
27801
- });
27802
- return;
27803
- }
27804
- if (surface === "failed") {
27805
- graphics.circle(cx, cy, 11).stroke({
27806
- color,
27807
- width: 1.5,
27808
- alpha: .86
27809
- });
27810
- graphics.moveTo(cx - 4, cy - 4).lineTo(cx + 4, cy + 4).moveTo(cx + 4, cy - 4).lineTo(cx - 4, cy + 4).stroke({
27811
- color,
27812
- width: 1.5,
27813
- alpha: .94,
27814
- cap: "round"
27815
- });
27816
- return;
27817
- }
27818
- if (surface === "unavailable") graphics.roundRect(cx - 12, cy - 9, 24, 18, 3).stroke({
27819
- color,
27820
- width: 1.25,
27821
- alpha: .62
27822
- }).moveTo(cx - 8, cy + 5).lineTo(cx + 8, cy - 5).stroke({
27823
- color,
27824
- width: 1.25,
27825
- alpha: .72,
27826
- cap: "round"
27827
- });
27828
- }
27829
- function drawFailedBadge(graphics, width, color, context) {
27830
- const cx = Math.max(12, width - 12);
27831
- graphics.circle(cx, 12, 8).fill({
27832
- color: context.palette.bg,
27833
- alpha: .72
27834
- });
27835
- graphics.moveTo(cx - 2.5, 9.5).lineTo(cx + 2.5, 14.5).moveTo(cx + 2.5, 9.5).lineTo(cx - 2.5, 14.5).stroke({
27836
- color,
27837
- width: 1.5,
27838
- alpha: .96,
27839
- cap: "round"
27840
- });
27841
- }
27842
- function sync$11(container, item, context) {
27843
- const parts = partsByContainer$11.get(container);
27844
- if (!parts) return;
27845
- positionShell(parts.root, item);
27846
- const { width, height } = item.frame;
27847
- const selected = context.selectedIds.has(item.id);
27848
- const hovered = context.hoveredId === item.id;
27849
- const full = context.zoom >= TASK_CARD_FULL_DETAIL_ZOOM;
27850
- const color = statusColor(item, context);
27851
- const key = context.assetKey(item);
27852
- if (key !== parts.assetKey) {
27853
- if (parts.assetKey) context.releaseTexture(parts.assetKey);
27854
- parts.assetKey = key;
27855
- if (key) context.acquireTexture(key);
27856
- }
27857
- const texture = key ? context.getTexture(key) : null;
27858
- const previewFailed = Boolean(key && !texture && context.hasError(key));
27859
- const artifact = featuredTaskArtifact(item.props.snapshot.artifacts);
27860
- const kind = previewKindFor(artifact);
27861
- const surface = stateSurfaceFor(item, kind, Boolean(texture), previewFailed);
27862
- const frame = {
27863
- x: 1,
27864
- y: 1,
27865
- width: Math.max(1, width - 2),
27866
- height: Math.max(1, height - 2)
27867
- };
27868
- const metaText = artifact ? metadata(item) : "";
27869
- const showMeta = full && Boolean(metaText);
27870
- const failedWithOutput = item.props.snapshot.status === "failed" && kind !== "empty";
27871
- syncTextResolution(parts.body, parts, context.zoom);
27872
- syncTextResolution(parts.meta, parts, context.zoom);
27873
- const visualSig = [
27874
27984
  width,
27875
- height,
27876
- selected,
27877
- hovered,
27878
- full,
27879
- color,
27880
- kind,
27881
- surface,
27882
- showMeta,
27883
- failedWithOutput,
27884
- artifact?.type ?? "none",
27885
- artifact?.id ?? "none",
27886
- item.props.taskRunId,
27887
- texture ? `${texture.width}x${texture.height}` : "none",
27888
- context.palette.surface,
27889
- context.palette.hover,
27890
- context.palette.border,
27891
- context.palette.bg
27892
- ].join("|");
27893
- if (visualSig !== parts.visualSig) {
27894
- parts.visualSig = visualSig;
27895
- parts.plate.clear().roundRect(0, 0, width, height, RADIUS$4).fill({
27896
- color: context.palette.surface,
27897
- alpha: .98
27898
- }).roundRect(0, 0, width, height, RADIUS$4).stroke({
27899
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
27900
- width: selected ? 2 : 1,
27901
- alpha: selected ? .96 : .82
27902
- });
27903
- parts.clip.clear().roundRect(1, 1, frame.width, frame.height, 3).fill({ color: 16777215 });
27904
- parts.previewBg.clear().rect(frame.x, frame.y, frame.width, frame.height).fill({
27905
- color: context.palette.hover,
27906
- alpha: .5
27907
- });
27908
- parts.previewMask.clear().rect(frame.x, frame.y, frame.width, frame.height).fill({ color: 16777215 });
27909
- parts.previewArt.clear();
27910
- if (kind === "waveform") {
27911
- const bottomInset = showMeta ? META_HEIGHT : 0;
27912
- drawAudioWaveform(parts.previewArt, item.props.taskRunId, {
27913
- x: frame.x + PADDING,
27914
- y: frame.y + PADDING,
27915
- width: Math.max(1, frame.width - 24),
27916
- height: Math.max(1, frame.height - 24 - bottomInset)
27917
- }, context.colors.brand.stroke);
27918
- }
27919
- if (full && (artifact?.type === "audio" || artifact?.type === "video" && texture)) drawPlayBadge(parts.previewArt, frame, context);
27920
- if (surface) {
27921
- const markY = frame.y + frame.height / 2 - (full && stateLabel(surface) ? 10 : 0);
27922
- drawStateMark(parts.previewArt, surface, frame.x + frame.width / 2, markY, surface === "failed" ? color : context.colors.neutral.stroke);
27923
- }
27924
- if (showMeta) parts.previewArt.rect(frame.x, frame.y + frame.height - META_HEIGHT, frame.width, META_HEIGHT).fill({
27925
- color: context.palette.bg,
27926
- alpha: .68
27927
- });
27928
- if (failedWithOutput) drawFailedBadge(parts.previewArt, width, color, context);
27929
- }
27930
- parts.preview.visible = kind === "texture" && Boolean(texture);
27931
- if (parts.preview.visible && texture) {
27932
- if (parts.preview.texture !== texture) parts.preview.texture = texture;
27933
- const fitted = containTaskPreviewRect(frame.width, frame.height, texture.width, texture.height);
27934
- parts.preview.position.set(frame.x + fitted.x, frame.y + fitted.y);
27935
- parts.preview.width = fitted.width;
27936
- parts.preview.height = fitted.height;
27937
- }
27938
- const bodyText = kind === "text" && artifact?.type === "text" ? artifact.textExcerpt : stateLabel(surface);
27939
- const textSig = [
27940
- bodyText,
27941
- metaText,
27942
- kind,
27943
- surface,
27944
- full,
27945
- width,
27946
- height,
27947
- context.palette.text,
27948
- context.palette.muted
27949
- ].join("|");
27950
- if (textSig !== parts.textSig) {
27951
- parts.textSig = textSig;
27952
- parts.body.style.fill = surface === "failed" ? color : context.palette.text;
27953
- fitTextToLines(parts.body, bodyText, kind === "text" ? Math.max(1, Math.floor((height - 24 - (showMeta ? META_HEIGHT : 0)) / 16)) : 1, Math.max(24, width - 24));
27954
- parts.meta.style.fill = context.palette.text;
27955
- fitTextToLines(parts.meta, metaText, 1, Math.max(24, width - 24));
27956
- }
27957
- parts.body.visible = full && Boolean(bodyText);
27958
- parts.meta.visible = showMeta;
27959
- if (kind === "text") parts.body.position.set(PADDING, PADDING);
27960
- else parts.body.position.set(Math.max(PADDING, (width - parts.body.width) / 2), height / 2 + 10);
27961
- parts.meta.position.set(PADDING, height - META_HEIGHT + 7);
27985
+ height
27986
+ }, context.style, {
27987
+ fill: context.palette.surface,
27988
+ border: context.palette.border,
27989
+ borderAlpha: .85,
27990
+ elevation
27991
+ });
27992
+ const key = audioKeyCenter(width, height);
27993
+ drawMediaPlayKey(parts.plate, key.x, key.y, key.radius, context, true);
27994
+ const left = key.x + key.radius + GAP$1;
27995
+ const room = width - 14 - left;
27996
+ parts.title.visible = detailed && room > 24;
27997
+ parts.wave.clear();
27998
+ if (!parts.title.visible) return;
27999
+ parts.title.style.fontFamily = context.style.font;
28000
+ parts.title.style.fill = context.palette.text;
28001
+ fitTextToWidth(parts.title, durationMs ? `${title} · ${formatBoardDuration(durationMs)}` : title, room);
28002
+ const withWave = height >= 56;
28003
+ const top = (height - (parts.title.height + (withWave ? 22 : 0))) / 2;
28004
+ parts.title.position.set(left, top);
28005
+ if (withWave) drawAudioWaveform(parts.wave, `${item.props.src}:${item.props.snapshot?.mtimeMs ?? "unknown"}`, {
28006
+ x: left,
28007
+ y: top + parts.title.height + 6,
28008
+ width: room,
28009
+ height: WAVE_HEIGHT
28010
+ }, context.palette.muted, .45, context.style.corner === "pixel");
27962
28011
  }
27963
- const taskCardRenderer = {
27964
- id: "task-card",
27965
- canRender: (item) => item.type === "task",
28012
+ const audioCardRenderer = {
28013
+ id: "audio-card",
28014
+ canRender: (item) => item.type === "audio",
27966
28015
  create: (item, context) => {
27967
28016
  const root = new Container();
27968
28017
  const plate = new Graphics();
27969
- const clip = new Graphics();
27970
- const previewBg = new Graphics();
27971
- const preview = new Sprite();
27972
- const previewMask = new Graphics();
27973
- const previewArt = new Graphics();
28018
+ const wave = new Graphics();
27974
28019
  const resolution = textResolutionForZoom(context.zoom);
27975
- const body = new Text({
27976
- text: "",
27977
- style: {
27978
- fontFamily: BOARD_FONT_STACK,
27979
- fontSize: 11.5,
27980
- fontWeight: "500",
27981
- wordWrap: true,
27982
- breakWords: true
27983
- },
27984
- resolution,
27985
- roundPixels: true
27986
- });
27987
- const meta = new Text({
28020
+ const title = new Text({
27988
28021
  text: "",
27989
28022
  style: {
27990
- fontFamily: BOARD_MONO_FONT_STACK,
27991
- fontSize: 10,
27992
- fontWeight: "500",
27993
- wordWrap: true,
27994
- breakWords: true
28023
+ fontSize: 13,
28024
+ fontWeight: "600"
27995
28025
  },
27996
28026
  resolution,
27997
28027
  roundPixels: true
27998
28028
  });
27999
- preview.mask = previewMask;
28000
- root.mask = clip;
28001
- root.addChild(plate, clip, previewBg, preview, previewMask, previewArt, body, meta);
28002
- partsByContainer$11.set(root, {
28029
+ root.addChild(plate, wave, title);
28030
+ partsByContainer$12.set(root, {
28003
28031
  root,
28004
28032
  plate,
28005
- clip,
28006
- previewBg,
28007
- preview,
28008
- previewMask,
28009
- previewArt,
28010
- body,
28011
- meta,
28012
- assetKey: null,
28013
- visualSig: "",
28014
- textSig: "",
28033
+ wave,
28034
+ title,
28035
+ sig: "",
28015
28036
  resolution
28016
28037
  });
28017
- if (item.type === "task") sync$11(root, item, context);
28038
+ if (item.type === "audio") sync$12(root, item, context);
28018
28039
  return root;
28019
28040
  },
28020
28041
  update: (container, item, context) => {
28021
- if (item.type === "task") sync$11(container, item, context);
28042
+ if (item.type === "audio") sync$12(container, item, context);
28022
28043
  },
28023
28044
  renderFar: (graphics, item, context) => {
28024
- if (item.type !== "task") return;
28025
- const active = item.props.snapshot.status === "failed" || item.props.snapshot.status === "running" || item.props.snapshot.status === "pending";
28026
28045
  drawFarPlate(graphics, item.frame, {
28027
28046
  fill: context.palette.surface,
28028
- fillAlpha: .82,
28029
- ...active ? {
28030
- accent: statusColor(item, context),
28031
- accentAlpha: .9
28032
- } : {}
28047
+ fillAlpha: .96,
28048
+ accent: context.palette.brand,
28049
+ accentAlpha: .72
28033
28050
  });
28034
28051
  },
28035
- destroy: (container, context) => {
28036
- const parts = partsByContainer$11.get(container);
28037
- if (parts?.assetKey) context.releaseTexture(parts.assetKey);
28038
- parts?.root.destroy({ children: true });
28039
- partsByContainer$11.delete(container);
28052
+ destroy: (container) => {
28053
+ partsByContainer$12.get(container)?.root.destroy({ children: true });
28054
+ partsByContainer$12.delete(container);
28040
28055
  }
28041
28056
  };
28042
28057
  //#endregion
28043
- //#region ../sdk/src/board/render/stroke.ts
28044
- const HEAD_SPREAD = Math.PI / 6;
28045
- function dashPattern(dash, width) {
28046
- if (dash === "dashed") return [Math.max(6, width * 4), Math.max(4, width * 3)];
28047
- if (dash === "dotted") return [Math.max(.5, width * .01), Math.max(3, width * 2)];
28048
- return null;
28049
- }
28050
- function trimPolyline(path, trim) {
28051
- if (trim === void 0 || trim >= 1 || path.length < 2) return path;
28052
- if (trim <= 0) return [];
28053
- let total = 0;
28054
- for (let index = 1; index < path.length; index += 1) {
28055
- const a = path[index - 1];
28056
- const b = path[index];
28057
- total += Math.hypot(b.x - a.x, b.y - a.y);
28058
- }
28059
- let remaining = total * trim;
28060
- const out = [path[0]];
28061
- for (let index = 1; index < path.length; index += 1) {
28062
- const a = path[index - 1];
28063
- const b = path[index];
28064
- const length = Math.hypot(b.x - a.x, b.y - a.y);
28065
- if (length >= remaining) {
28066
- const t = length > 0 ? remaining / length : 0;
28067
- out.push(worldPoint(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t));
28068
- return out;
28069
- }
28070
- out.push(b);
28071
- remaining -= length;
28072
- }
28073
- return out;
28074
- }
28075
- function tracePolyline(graphics, path, pattern, closed = false) {
28076
- const points = closed && path.length > 2 ? [...path, path[0]] : path;
28077
- if (points.length < 2) return;
28078
- if (!pattern) {
28079
- const first = points[0];
28080
- graphics.moveTo(first.x, first.y);
28081
- for (let index = 1; index < points.length; index += 1) {
28082
- const point = points[index];
28083
- graphics.lineTo(point.x, point.y);
28084
- }
28085
- if (closed) graphics.closePath();
28086
- return;
28087
- }
28088
- const [dash, gap] = pattern;
28089
- let penDown = true;
28090
- let fresh = true;
28091
- let remaining = dash;
28092
- for (let index = 1; index < points.length; index += 1) {
28093
- const a = points[index - 1];
28094
- const b = points[index];
28095
- const dx = b.x - a.x;
28096
- const dy = b.y - a.y;
28097
- const length = Math.hypot(dx, dy);
28098
- let travelled = 0;
28099
- while (length - travelled > 1e-4) {
28100
- const step = Math.min(length - travelled, remaining);
28101
- if (penDown) {
28102
- const t0 = travelled / length;
28103
- const t1 = (travelled + step) / length;
28104
- if (fresh) graphics.moveTo(a.x + dx * t0, a.y + dy * t0);
28105
- fresh = false;
28106
- graphics.lineTo(a.x + dx * t1, a.y + dy * t1);
28107
- }
28108
- travelled += step;
28109
- remaining -= step;
28110
- if (remaining <= 1e-4) {
28111
- penDown = !penDown;
28112
- fresh = penDown;
28113
- remaining = penDown ? dash : gap;
28114
- }
28115
- }
28116
- }
28117
- }
28118
- function traceArrowhead(graphics, tip, angle, size) {
28119
- graphics.moveTo(tip.x - size * Math.cos(angle - HEAD_SPREAD), tip.y - size * Math.sin(angle - HEAD_SPREAD)).lineTo(tip.x, tip.y).lineTo(tip.x - size * Math.cos(angle + HEAD_SPREAD), tip.y - size * Math.sin(angle + HEAD_SPREAD));
28120
- }
28121
- function endAngle(path, atStart) {
28122
- if (path.length < 2) return null;
28123
- const tip = atStart ? path[0] : path[path.length - 1];
28124
- for (let offset = 1; offset < path.length; offset += 1) {
28125
- const other = atStart ? path[offset] : path[path.length - 1 - offset];
28126
- if (Math.hypot(tip.x - other.x, tip.y - other.y) > .001) return Math.atan2(tip.y - other.y, tip.x - other.x);
28127
- }
28128
- return null;
28129
- }
28130
- function roundRectPoints(width, height, radius) {
28131
- const r = Math.min(radius, width / 2, height / 2);
28132
- if (r <= 0) return [
28133
- worldPoint(0, 0),
28134
- worldPoint(width, 0),
28135
- worldPoint(width, height),
28136
- worldPoint(0, height),
28137
- worldPoint(0, 0)
28138
- ];
28139
- const corner = (cx, cy, from) => Array.from({ length: 9 }, (_, index) => {
28140
- const angle = from + index / 8 * (Math.PI / 2);
28141
- return worldPoint(cx + Math.cos(angle) * r, cy + Math.sin(angle) * r);
28142
- });
28143
- return [
28144
- ...corner(width - r, r, -Math.PI / 2),
28145
- ...corner(width - r, height - r, 0),
28146
- ...corner(r, height - r, Math.PI / 2),
28147
- ...corner(r, r, Math.PI),
28148
- worldPoint(width - r, 0)
28149
- ];
28150
- }
28151
- //#endregion
28152
28058
  //#region ../sdk/src/board/render/renderers/arrow-card-renderer.ts
28153
- const partsByContainer$10 = /* @__PURE__ */ new WeakMap();
28154
- function sync$10(container, item, context) {
28155
- const parts = partsByContainer$10.get(container);
28059
+ const partsByContainer$11 = /* @__PURE__ */ new WeakMap();
28060
+ function sync$11(container, item, context) {
28061
+ const parts = partsByContainer$11.get(container);
28156
28062
  if (!parts) return;
28157
28063
  parts.root.position.set(0, 0);
28158
28064
  parts.root.rotation = 0;
28159
28065
  parts.root.scale.set(1);
28160
28066
  const { props, style } = item;
28161
- const selected = context.selectedIds.has(item.id);
28162
- const hovered = context.hoveredId === item.id;
28163
28067
  parts.line.alpha = itemAlpha(context, style.stroke);
28164
28068
  const color = itemColor(context, style.stroke, "brand");
28165
28069
  const size = style.strokeWidth ?? 2.5;
@@ -28168,8 +28072,6 @@ function sync$10(container, item, context) {
28168
28072
  const path = trimPolyline(resolved.path, style.trim);
28169
28073
  const lineSig = [
28170
28074
  path.map((point) => `${point.x},${point.y}`).join(";"),
28171
- selected,
28172
- hovered,
28173
28075
  color,
28174
28076
  size,
28175
28077
  style.dash,
@@ -28179,11 +28081,11 @@ function sync$10(container, item, context) {
28179
28081
  if (lineSig !== parts.lineSig) {
28180
28082
  parts.lineSig = lineSig;
28181
28083
  parts.line.clear();
28182
- const width = selected ? size + 1 : size;
28084
+ const width = size;
28183
28085
  const stroke = {
28184
28086
  color,
28185
28087
  width,
28186
- alpha: selected || hovered ? 1 : .92,
28088
+ alpha: .92,
28187
28089
  cap: "round",
28188
28090
  join: "round"
28189
28091
  };
@@ -28207,22 +28109,29 @@ function sync$10(container, item, context) {
28207
28109
  }
28208
28110
  const labelSig = [
28209
28111
  props.label,
28210
- context.palette.text,
28211
- context.palette.bg,
28212
- props.fontSize
28112
+ props.fontSize,
28113
+ context.themeKey
28213
28114
  ].join("|");
28214
28115
  if (labelSig !== parts.labelSig) {
28215
28116
  parts.labelSig = labelSig;
28216
28117
  parts.label.text = props.label;
28217
28118
  parts.label.style.fill = context.palette.text;
28218
28119
  parts.label.style.fontSize = props.fontSize;
28219
- const padX = props.fontSize * .4;
28220
- const padY = props.fontSize * .15;
28120
+ const padX = props.fontSize * .5;
28121
+ const padY = props.fontSize * .25;
28221
28122
  const { width, height } = parts.label;
28222
- parts.backdrop.clear();
28223
- parts.backdrop.roundRect(-width / 2 - padX, -height / 2 - padY, width + padX * 2, height + padY * 2, props.fontSize * .3).fill({
28224
- color: context.palette.bg,
28225
- alpha: .92
28123
+ const plate = {
28124
+ x: -width / 2 - padX,
28125
+ y: -height / 2 - padY,
28126
+ width: width + padX * 2,
28127
+ height: height + padY * 2
28128
+ };
28129
+ drawCardSurface(parts.backdrop.clear(), plate, {
28130
+ ...context.style,
28131
+ radius: Math.min(context.style.radius, plate.height / 2)
28132
+ }, {
28133
+ fill: context.palette.surface,
28134
+ border: context.palette.border
28226
28135
  });
28227
28136
  }
28228
28137
  const visible = props.label.length > 0 && (style.trim === void 0 || style.trim >= .5);
@@ -28253,7 +28162,7 @@ const arrowCardRenderer = {
28253
28162
  label.anchor.set(.5);
28254
28163
  const backdrop = new Graphics();
28255
28164
  root.addChild(line, backdrop, label);
28256
- partsByContainer$10.set(root, {
28165
+ partsByContainer$11.set(root, {
28257
28166
  root,
28258
28167
  line,
28259
28168
  backdrop,
@@ -28262,11 +28171,11 @@ const arrowCardRenderer = {
28262
28171
  labelSig: "",
28263
28172
  resolution
28264
28173
  });
28265
- if (item.type === "arrow") sync$10(root, item, context);
28174
+ if (item.type === "arrow") sync$11(root, item, context);
28266
28175
  return root;
28267
28176
  },
28268
28177
  update: (container, item, context) => {
28269
- if (item.type === "arrow") sync$10(container, item, context);
28178
+ if (item.type === "arrow") sync$11(container, item, context);
28270
28179
  },
28271
28180
  renderFar: (graphics, item, context) => {
28272
28181
  if (item.type !== "arrow") return;
@@ -28278,126 +28187,13 @@ const arrowCardRenderer = {
28278
28187
  });
28279
28188
  },
28280
28189
  destroy: (container) => {
28281
- partsByContainer$10.get(container)?.root.destroy({ children: true });
28282
- partsByContainer$10.delete(container);
28283
- }
28284
- };
28285
- //#endregion
28286
- //#region ../sdk/src/board/render/renderers/audio-card-renderer.ts
28287
- const RADIUS$3 = 4;
28288
- const PLAY_RADIUS = 18;
28289
- const partsByContainer$9 = /* @__PURE__ */ new WeakMap();
28290
- function sync$9(container, item, context) {
28291
- const parts = partsByContainer$9.get(container);
28292
- if (!parts) return;
28293
- positionShell(parts.root, item);
28294
- const { width, height } = item.frame;
28295
- const selected = context.selectedIds.has(item.id);
28296
- const hovered = context.hoveredId === item.id;
28297
- const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
28298
- syncTextResolution(parts.label, parts, context.zoom);
28299
- if (parts.label.text !== title) parts.label.text = title;
28300
- syncTextWrapWidth(parts.label, parts, Math.max(1, width - 24), false);
28301
- parts.label.position.set(12, Math.max(8, height - parts.label.height - 8));
28302
- const sig = [
28303
- width,
28304
- height,
28305
- selected,
28306
- hovered,
28307
- title,
28308
- parts.label.height,
28309
- context.palette.surface,
28310
- context.palette.brand,
28311
- context.palette.border,
28312
- context.palette.muted,
28313
- context.palette.text
28314
- ].join("|");
28315
- if (sig === parts.sig) return;
28316
- parts.sig = sig;
28317
- parts.plate.clear().roundRect(0, 0, width, height, RADIUS$3).fill({
28318
- color: context.palette.surface,
28319
- alpha: .98
28320
- }).roundRect(0, 0, width, height, RADIUS$3).stroke({
28321
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
28322
- width: selected ? 2 : 1,
28323
- alpha: selected ? .96 : .84
28324
- });
28325
- const labelBand = Math.min(height * .38, parts.label.height + 16);
28326
- const waveformInset = Math.min(24, width * .08);
28327
- parts.waveform.clear();
28328
- drawAudioWaveform(parts.waveform, `${item.props.src}:${item.props.snapshot?.mtimeMs ?? "unknown"}`, {
28329
- x: waveformInset,
28330
- y: 10,
28331
- width: Math.max(1, width - waveformInset * 2),
28332
- height: Math.max(8, height - labelBand - 16)
28333
- }, context.palette.brand);
28334
- const cx = width / 2;
28335
- const cy = Math.max(24, (height - labelBand) / 2);
28336
- parts.chrome.clear().circle(cx, cy, PLAY_RADIUS).fill({
28337
- color: selected ? context.palette.brand : context.palette.surface,
28338
- alpha: .92
28339
- });
28340
- const triangle = PLAY_RADIUS * .58;
28341
- parts.chrome.moveTo(cx - triangle * .35, cy - triangle * .62).lineTo(cx - triangle * .35, cy + triangle * .62).lineTo(cx + triangle * .72, cy).closePath().fill({
28342
- color: context.palette.text,
28343
- alpha: .96
28344
- });
28345
- parts.label.style.fill = context.palette.text;
28346
- }
28347
- const audioCardRenderer = {
28348
- id: "audio-card",
28349
- canRender: (item) => item.type === "audio",
28350
- create: (item, context) => {
28351
- const root = new Container();
28352
- const plate = new Graphics();
28353
- const waveform = new Graphics();
28354
- const chrome = new Graphics();
28355
- const resolution = textResolutionForZoom(context.zoom);
28356
- const label = new Text({
28357
- text: "",
28358
- style: {
28359
- fill: context.palette.text,
28360
- fontFamily: BOARD_FONT_STACK,
28361
- fontSize: 12,
28362
- fontWeight: "500",
28363
- wordWrap: true
28364
- },
28365
- resolution,
28366
- roundPixels: true
28367
- });
28368
- root.addChild(plate, waveform, chrome, label);
28369
- partsByContainer$9.set(root, {
28370
- root,
28371
- plate,
28372
- waveform,
28373
- chrome,
28374
- label,
28375
- sig: "",
28376
- wrapWidth: 0,
28377
- resolution
28378
- });
28379
- if (item.type === "audio") sync$9(root, item, context);
28380
- return root;
28381
- },
28382
- update: (container, item, context) => {
28383
- if (item.type === "audio") sync$9(container, item, context);
28384
- },
28385
- renderFar: (graphics, item, context) => {
28386
- drawFarPlate(graphics, item.frame, {
28387
- fill: context.palette.surface,
28388
- fillAlpha: .96,
28389
- accent: context.palette.brand,
28390
- accentAlpha: .72
28391
- });
28392
- },
28393
- destroy: (container) => {
28394
- partsByContainer$9.get(container)?.root.destroy({ children: true });
28395
- partsByContainer$9.delete(container);
28190
+ partsByContainer$11.get(container)?.root.destroy({ children: true });
28191
+ partsByContainer$11.delete(container);
28396
28192
  }
28397
28193
  };
28398
28194
  //#endregion
28399
28195
  //#region ../sdk/src/board/render/renderers/draw-card-renderer.ts
28400
- const partsByContainer$8 = /* @__PURE__ */ new WeakMap();
28196
+ const partsByContainer$10 = /* @__PURE__ */ new WeakMap();
28401
28197
  function fillRibbon(graphics, ribbon, color) {
28402
28198
  const { positions, indices } = ribbon;
28403
28199
  for (let offset = 0; offset < indices.length; offset += 3) {
@@ -28448,8 +28244,8 @@ function tessellate(parts, points, size, paint) {
28448
28244
  texture: Texture.WHITE
28449
28245
  }));
28450
28246
  }
28451
- function sync$8(container, item, context) {
28452
- const parts = partsByContainer$8.get(container);
28247
+ function sync$10(container, item, context) {
28248
+ const parts = partsByContainer$10.get(container);
28453
28249
  if (!parts) return;
28454
28250
  positionShell(parts.root, item);
28455
28251
  const { points } = item.props;
@@ -28482,7 +28278,7 @@ const drawCardRenderer = {
28482
28278
  canRender: (item) => item.type === "draw",
28483
28279
  create: (item, context) => {
28484
28280
  const root = new Container();
28485
- partsByContainer$8.set(root, {
28281
+ partsByContainer$10.set(root, {
28486
28282
  root,
28487
28283
  stroke: null,
28488
28284
  ribbon: null,
@@ -28499,11 +28295,11 @@ const drawCardRenderer = {
28499
28295
  height: 1
28500
28296
  }
28501
28297
  });
28502
- if (item.type === "draw") sync$8(root, item, context);
28298
+ if (item.type === "draw") sync$10(root, item, context);
28503
28299
  return root;
28504
28300
  },
28505
28301
  update: (container, item, context) => {
28506
- if (item.type === "draw") sync$8(container, item, context);
28302
+ if (item.type === "draw") sync$10(container, item, context);
28507
28303
  },
28508
28304
  renderFar: (graphics, item, context) => {
28509
28305
  if (item.type !== "draw") return;
@@ -28516,15 +28312,15 @@ const drawCardRenderer = {
28516
28312
  });
28517
28313
  },
28518
28314
  destroy: (container) => {
28519
- partsByContainer$8.get(container)?.root.destroy({ children: true });
28520
- partsByContainer$8.delete(container);
28315
+ partsByContainer$10.get(container)?.root.destroy({ children: true });
28316
+ partsByContainer$10.delete(container);
28521
28317
  }
28522
28318
  };
28523
28319
  //#endregion
28524
28320
  //#region ../sdk/src/board/render/renderers/effect-card-renderer.ts
28525
28321
  const BOARD_EFFECT_MAX_PARTICLES = 4e3;
28526
28322
  const TRAIL_SAMPLES = 64;
28527
- const partsByContainer$7 = /* @__PURE__ */ new WeakMap();
28323
+ const partsByContainer$9 = /* @__PURE__ */ new WeakMap();
28528
28324
  function hashString(value) {
28529
28325
  let hash = 2166136261;
28530
28326
  for (let index = 0; index < value.length; index += 1) {
@@ -28665,8 +28461,8 @@ function drawTrail(graphics, parts, item, context, color) {
28665
28461
  });
28666
28462
  }
28667
28463
  }
28668
- function sync$7(container, item, context) {
28669
- const parts = partsByContainer$7.get(container);
28464
+ function sync$9(container, item, context) {
28465
+ const parts = partsByContainer$9.get(container);
28670
28466
  if (!parts) return;
28671
28467
  positionShell(parts.root, item);
28672
28468
  const { width, height } = item.frame;
@@ -28692,11 +28488,6 @@ function sync$7(container, item, context) {
28692
28488
  break;
28693
28489
  case "trail": drawTrail(graphics, parts, item, context, color);
28694
28490
  }
28695
- if (context.selectedIds.has(item.id) || context.hoveredId === item.id) graphics.rect(0, 0, width, height).stroke({
28696
- color: context.palette.muted,
28697
- width: 1,
28698
- alpha: .6
28699
- });
28700
28491
  }
28701
28492
  const effectCardRenderer = {
28702
28493
  id: "effect-card",
@@ -28706,16 +28497,16 @@ const effectCardRenderer = {
28706
28497
  const root = new Container();
28707
28498
  const graphics = new Graphics();
28708
28499
  root.addChild(graphics);
28709
- partsByContainer$7.set(root, {
28500
+ partsByContainer$9.set(root, {
28710
28501
  root,
28711
28502
  graphics,
28712
28503
  trail: []
28713
28504
  });
28714
- if (item.type === "effect") sync$7(root, item, context);
28505
+ if (item.type === "effect") sync$9(root, item, context);
28715
28506
  return root;
28716
28507
  },
28717
28508
  update: (container, item, context) => {
28718
- if (item.type === "effect") sync$7(container, item, context);
28509
+ if (item.type === "effect") sync$9(container, item, context);
28719
28510
  },
28720
28511
  renderFar: (graphics, item, context) => {
28721
28512
  if (item.type !== "effect") return;
@@ -28725,27 +28516,293 @@ const effectCardRenderer = {
28725
28516
  });
28726
28517
  },
28727
28518
  destroy: (container) => {
28728
- partsByContainer$7.get(container)?.root.destroy({ children: true });
28729
- partsByContainer$7.delete(container);
28519
+ partsByContainer$9.get(container)?.root.destroy({ children: true });
28520
+ partsByContainer$9.delete(container);
28521
+ }
28522
+ };
28523
+ //#endregion
28524
+ //#region ../sdk/src/board/render/renderers/file-card-renderer.ts
28525
+ const PADDING$1 = 10;
28526
+ const COVER_RATIO = .56;
28527
+ const MIN_BODY_FOR_COVER = 38;
28528
+ const TITLE_SIZE = 13;
28529
+ const TITLE_LINE = TITLE_SIZE * 1.35;
28530
+ const TITLE_MAX_LINES = 2;
28531
+ const EXCERPT_SIZE = 11;
28532
+ const EXCERPT_LINE = EXCERPT_SIZE * 1.45;
28533
+ const EXCERPT_MAX_LINES = 3;
28534
+ const TYPE_MARK_SIZE = 28;
28535
+ const GAP = 4;
28536
+ const STRIPE = 2;
28537
+ const MAX_DASHES = 40;
28538
+ const partsByContainer$8 = /* @__PURE__ */ new WeakMap();
28539
+ function detailFor(zoom) {
28540
+ return boardCardDetailed(zoom) ? "full" : "title";
28541
+ }
28542
+ function coverHeight(item, height) {
28543
+ if (filePreviewKind(item.props.snapshot) !== "cover") return 0;
28544
+ const ideal = Math.round(height * COVER_RATIO);
28545
+ return Math.max(0, Math.min(ideal, height - MIN_BODY_FOR_COVER));
28546
+ }
28547
+ function linesInRoom(room, lineHeight, max) {
28548
+ if (room < lineHeight * .85) return 0;
28549
+ return Math.max(0, Math.min(max, Math.floor((room + .5) / lineHeight)));
28550
+ }
28551
+ function layoutCover(sprite, width, height, texture) {
28552
+ const rect = containRect(width, height, texture.width, texture.height);
28553
+ sprite.position.set(rect.x, rect.y);
28554
+ sprite.width = rect.width;
28555
+ sprite.height = rect.height;
28556
+ }
28557
+ function syncClip(parts, rect, style) {
28558
+ traceCardOutline(parts.clip.clear(), rect, style).fill({ color: 16777215 });
28559
+ }
28560
+ function sync$8(container, item, context) {
28561
+ const parts = partsByContainer$8.get(container);
28562
+ if (!parts) return;
28563
+ positionShell(parts.root, item);
28564
+ const { width, height } = item.frame;
28565
+ const detail = detailFor(context.zoom);
28566
+ const elevation = cardElevation(context, item.id);
28567
+ syncCardTag(parts.tag, {
28568
+ glyph: "file",
28569
+ text: fileMetaLine(item.props.src, item.props.snapshot?.size),
28570
+ width
28571
+ }, context);
28572
+ const key = context.assetKey(item);
28573
+ const texture = key ? context.getTexture(key) : null;
28574
+ const coverFailed = Boolean(key && !texture && context.hasError(key));
28575
+ const fileState = context.fileState(item.props.src);
28576
+ const band = coverHeight(item, height);
28577
+ const kind = filePreviewKind(item.props.snapshot);
28578
+ const category = fileCategory(item.props.src, item.props.snapshot?.mimeType);
28579
+ const accent = fileCategoryAccent(category, context.palette);
28580
+ syncTextResolution(parts.title, parts.titleRes, context.zoom);
28581
+ syncTextResolution(parts.excerpt, parts.excerptRes, context.zoom);
28582
+ syncTextResolution(parts.typeMark, parts.typeMarkRes, context.zoom);
28583
+ const visualSig = [
28584
+ key ?? "",
28585
+ width,
28586
+ height,
28587
+ detail,
28588
+ elevation,
28589
+ texture ? `${texture.width}x${texture.height}` : "none",
28590
+ coverFailed,
28591
+ fileState,
28592
+ kind,
28593
+ category,
28594
+ context.themeKey,
28595
+ accent
28596
+ ].join("|");
28597
+ if (visualSig !== parts.visualSig) {
28598
+ parts.visualSig = visualSig;
28599
+ const rect = {
28600
+ x: 0,
28601
+ y: 0,
28602
+ width,
28603
+ height
28604
+ };
28605
+ syncClip(parts, rect, context.style);
28606
+ drawCardSurface(parts.plate.clear(), rect, context.style, {
28607
+ fill: context.palette.surface,
28608
+ border: context.palette.border,
28609
+ borderAlpha: .85,
28610
+ elevation
28611
+ });
28612
+ if (fileState !== "ok") {
28613
+ const dash = Math.max(6, width / MAX_DASHES / 2);
28614
+ for (let x = 0; x < width; x += dash * 2) parts.plate.moveTo(x, .5).lineTo(Math.min(x + dash, width), .5);
28615
+ parts.plate.stroke({
28616
+ color: context.palette.muted,
28617
+ width: 2,
28618
+ alpha: fileState === "missing" ? .9 : .5
28619
+ });
28620
+ }
28621
+ const showCover = band > 0 && Boolean(texture);
28622
+ if (band > 0) parts.plate.rect(1, 1, width - 2, band - 1).fill({
28623
+ color: context.palette.hover,
28624
+ alpha: coverFailed ? .35 : .6
28625
+ });
28626
+ if (showCover && texture) {
28627
+ if (parts.cover.texture !== texture) parts.cover.texture = texture;
28628
+ layoutCover(parts.cover, width, band, texture);
28629
+ parts.coverMask.clear().rect(0, 0, width, band).fill({ color: 16777215 });
28630
+ }
28631
+ parts.cover.visible = showCover;
28632
+ parts.coverMask.visible = showCover;
28633
+ if (!band) parts.plate.rect(1, 1, STRIPE, height - 2).fill({
28634
+ color: accent,
28635
+ alpha: .55
28636
+ });
28637
+ parts.excerpt.visible = detail === "full";
28638
+ parts.typeMark.visible = kind === "blank";
28639
+ }
28640
+ const title = item.props.snapshot?.title || fileStem(item.props.src);
28641
+ const excerpt = item.props.snapshot?.excerpt ?? "";
28642
+ const mark = fileTypeLabel(item.props.src);
28643
+ const innerWidth = Math.max(1, width - 20);
28644
+ const showTypeMark = kind === "blank";
28645
+ const textSig = [
28646
+ title,
28647
+ excerpt,
28648
+ mark,
28649
+ detail,
28650
+ innerWidth,
28651
+ band,
28652
+ height,
28653
+ kind,
28654
+ context.palette.text,
28655
+ context.palette.muted,
28656
+ context.style.font,
28657
+ accent
28658
+ ].join("|");
28659
+ if (textSig === parts.textSig) return;
28660
+ parts.textSig = textSig;
28661
+ parts.title.style.fontFamily = context.style.font;
28662
+ parts.excerpt.style.fontFamily = context.style.font;
28663
+ const top = band > 0 ? band + PADDING$1 * .8 : PADDING$1;
28664
+ const contentBottom = height - PADDING$1;
28665
+ let cursor = top;
28666
+ if (showTypeMark) {
28667
+ const markSize = Math.max(18, Math.min(TYPE_MARK_SIZE, Math.round(height * .22)));
28668
+ parts.typeMark.style.fill = accent;
28669
+ parts.typeMark.style.fontSize = markSize;
28670
+ parts.typeMark.style.lineHeight = markSize * 1.1;
28671
+ if (parts.typeMark.text !== mark) parts.typeMark.text = mark;
28672
+ parts.typeMark.position.set(PADDING$1, cursor);
28673
+ parts.typeMark.visible = true;
28674
+ cursor += parts.typeMark.height + GAP;
28675
+ } else parts.typeMark.visible = false;
28676
+ const titleLines = linesInRoom(Math.max(0, contentBottom - cursor), TITLE_LINE, TITLE_MAX_LINES);
28677
+ parts.title.style.fill = context.palette.text;
28678
+ fitTextToLines(parts.title, title, titleLines, innerWidth);
28679
+ parts.title.position.set(PADDING$1, cursor);
28680
+ parts.title.visible = titleLines > 0;
28681
+ if (detail !== "full") {
28682
+ parts.excerpt.visible = false;
28683
+ return;
28684
+ }
28685
+ const excerptTop = cursor + (titleLines > 0 ? parts.title.height + GAP : 0);
28686
+ const excerptLines = linesInRoom(contentBottom - excerptTop, EXCERPT_LINE, EXCERPT_MAX_LINES);
28687
+ const showExcerpt = Boolean(excerpt) && excerptLines > 0;
28688
+ if (showExcerpt) {
28689
+ parts.excerpt.style.fill = context.palette.muted;
28690
+ fitTextToLines(parts.excerpt, excerpt, excerptLines, innerWidth);
28691
+ parts.excerpt.position.set(PADDING$1, excerptTop);
28692
+ }
28693
+ parts.excerpt.visible = showExcerpt;
28694
+ }
28695
+ const fileCardRenderer = {
28696
+ id: "file-card",
28697
+ canRender: (item) => item.type === "file",
28698
+ create: (item, context) => {
28699
+ const root = new Container();
28700
+ const plate = new Graphics();
28701
+ const body = new Container();
28702
+ const clip = new Graphics();
28703
+ const cover = new Sprite(Texture.EMPTY);
28704
+ const coverMask = new Graphics();
28705
+ cover.mask = coverMask;
28706
+ const resolution = 1;
28707
+ const title = new Text({
28708
+ text: "",
28709
+ style: {
28710
+ fill: context.palette.text,
28711
+ fontFamily: BOARD_FONT_STACK,
28712
+ fontSize: TITLE_SIZE,
28713
+ fontWeight: "600",
28714
+ wordWrap: true,
28715
+ breakWords: true,
28716
+ lineHeight: TITLE_LINE
28717
+ },
28718
+ resolution,
28719
+ roundPixels: true
28720
+ });
28721
+ const excerpt = new Text({
28722
+ text: "",
28723
+ style: {
28724
+ fill: context.palette.muted,
28725
+ fontFamily: BOARD_FONT_STACK,
28726
+ fontSize: EXCERPT_SIZE,
28727
+ fontWeight: "400",
28728
+ wordWrap: true,
28729
+ breakWords: true,
28730
+ lineHeight: EXCERPT_LINE
28731
+ },
28732
+ resolution,
28733
+ roundPixels: true
28734
+ });
28735
+ const typeMark = new Text({
28736
+ text: "",
28737
+ style: {
28738
+ fill: context.palette.muted,
28739
+ fontFamily: BOARD_MONO_FONT_STACK,
28740
+ fontSize: TYPE_MARK_SIZE,
28741
+ fontWeight: "700",
28742
+ lineHeight: TYPE_MARK_SIZE * 1.1
28743
+ },
28744
+ resolution,
28745
+ roundPixels: true
28746
+ });
28747
+ body.mask = clip;
28748
+ body.addChild(cover, coverMask, typeMark, title, excerpt);
28749
+ const tag = createCardTag();
28750
+ root.addChild(plate, body, clip, tag.root);
28751
+ partsByContainer$8.set(root, {
28752
+ tag,
28753
+ root,
28754
+ plate,
28755
+ body,
28756
+ clip,
28757
+ cover,
28758
+ coverMask,
28759
+ typeMark,
28760
+ title,
28761
+ excerpt,
28762
+ visualSig: "",
28763
+ textSig: "",
28764
+ titleRes: { resolution },
28765
+ excerptRes: { resolution },
28766
+ typeMarkRes: { resolution }
28767
+ });
28768
+ if (item.type === "file") sync$8(root, item, context);
28769
+ return root;
28770
+ },
28771
+ update: (container, item, context) => {
28772
+ if (item.type === "file") sync$8(container, item, context);
28773
+ },
28774
+ zoom: (container, zoom) => {
28775
+ const tag = partsByContainer$8.get(container)?.tag;
28776
+ if (tag) zoomCardTag(tag, zoom);
28777
+ },
28778
+ renderFar: (graphics, item, context) => {
28779
+ const category = fileCategory(item.type === "file" ? item.props.src : "", item.type === "file" ? item.props.snapshot?.mimeType : void 0);
28780
+ drawFarPlate(graphics, item.frame, {
28781
+ fill: context.palette.surface,
28782
+ fillAlpha: .96,
28783
+ accent: fileCategoryAccent(category, context.palette),
28784
+ accentAlpha: .45
28785
+ });
28786
+ },
28787
+ destroy: (container) => {
28788
+ partsByContainer$8.get(container)?.root.destroy({ children: true });
28789
+ partsByContainer$8.delete(container);
28730
28790
  }
28731
28791
  };
28732
28792
  //#endregion
28733
28793
  //#region ../sdk/src/board/render/renderers/frame-card-renderer.ts
28734
- const partsByContainer$6 = /* @__PURE__ */ new WeakMap();
28794
+ const partsByContainer$7 = /* @__PURE__ */ new WeakMap();
28735
28795
  const DEFAULT_WASH = .04;
28736
- function sync$6(container, item, context) {
28737
- const parts = partsByContainer$6.get(container);
28796
+ function sync$7(container, item, context) {
28797
+ const parts = partsByContainer$7.get(container);
28738
28798
  if (!parts) return;
28739
28799
  const { frame, style, props, locked } = item;
28740
28800
  positionShell(parts.root, item);
28741
28801
  syncTextResolution(parts.label, parts, context.zoom);
28742
- const selected = context.selectedIds.has(item.id);
28743
- const hovered = context.hoveredId === item.id;
28744
28802
  const fill = itemColor(context, style.fill ?? style.stroke, "neutral", "fill");
28745
28803
  const fillAlpha = (style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH)) * itemAlpha(context, style.fill ?? style.stroke);
28746
28804
  const baseStroke = itemColor(context, style.stroke, "neutral");
28747
- const stroke = selected ? context.palette.brand : hovered && style.stroke ? context.palette.muted : baseStroke;
28748
- const alpha = (selected || style.stroke ? 1 : hovered ? .85 : .55) * itemAlpha(context, style.stroke);
28805
+ const alpha = (style.stroke ? 1 : .55) * itemAlpha(context, style.stroke);
28749
28806
  const radius = style.radius ?? 4;
28750
28807
  const strokeWidth = style.strokeWidth ?? 1.5;
28751
28808
  const decorated = props.decoration !== "none";
@@ -28753,11 +28810,9 @@ function sync$6(container, item, context) {
28753
28810
  props.decoration,
28754
28811
  frame.width,
28755
28812
  frame.height,
28756
- selected,
28757
- hovered,
28758
28813
  fill,
28759
28814
  fillAlpha,
28760
- stroke,
28815
+ baseStroke,
28761
28816
  alpha,
28762
28817
  radius,
28763
28818
  strokeWidth,
@@ -28771,15 +28826,14 @@ function sync$6(container, item, context) {
28771
28826
  color: fill,
28772
28827
  alpha: fillAlpha
28773
28828
  });
28774
- const width = selected ? Math.max(2, strokeWidth) : strokeWidth;
28775
- const pattern = decorated ? dashPattern(style.dash, width) : null;
28776
- const trim = decorated ? style.trim ?? 1 : 1;
28777
- if ((decorated && strokeWidth > 0 || selected || hovered) && trim > 0) {
28829
+ const pattern = dashPattern(style.dash, strokeWidth);
28830
+ const trim = style.trim ?? 1;
28831
+ if (decorated && strokeWidth > 0 && trim > 0) {
28778
28832
  if (pattern || trim < 1) tracePolyline(parts.box, trimPolyline(roundRectPoints(frame.width, frame.height, radius), trim), pattern);
28779
28833
  else parts.box.roundRect(0, 0, frame.width, frame.height, radius);
28780
28834
  parts.box.stroke({
28781
- color: stroke,
28782
- width,
28835
+ color: baseStroke,
28836
+ width: strokeWidth,
28783
28837
  alpha,
28784
28838
  cap: pattern || trim < 1 ? "round" : "butt",
28785
28839
  join: "round"
@@ -28789,11 +28843,16 @@ function sync$6(container, item, context) {
28789
28843
  parts.label.visible = decorated;
28790
28844
  const title = locked ? `🔒 ${props.label}`.trim() : props.label;
28791
28845
  const labelColor = style.stroke ? baseStroke : context.palette.muted;
28792
- const textSig = [title, labelColor].join("|");
28846
+ const textSig = [
28847
+ title,
28848
+ labelColor,
28849
+ context.style.font
28850
+ ].join("|");
28793
28851
  if (textSig !== parts.textSig) {
28794
28852
  parts.textSig = textSig;
28795
28853
  parts.label.text = title;
28796
28854
  parts.label.style.fill = labelColor;
28855
+ parts.label.style.fontFamily = context.style.font;
28797
28856
  }
28798
28857
  parts.label.position.set(4, -18);
28799
28858
  }
@@ -28810,7 +28869,7 @@ const frameCardRenderer = {
28810
28869
  fontWeight: "600"
28811
28870
  });
28812
28871
  root.addChild(box, label);
28813
- partsByContainer$6.set(root, {
28872
+ partsByContainer$7.set(root, {
28814
28873
  root,
28815
28874
  box,
28816
28875
  label,
@@ -28818,11 +28877,11 @@ const frameCardRenderer = {
28818
28877
  textSig: "",
28819
28878
  resolution: label.resolution
28820
28879
  });
28821
- if (item.type === "frame") sync$6(root, item, context);
28880
+ if (item.type === "frame") sync$7(root, item, context);
28822
28881
  return root;
28823
28882
  },
28824
28883
  update: (container, item, context) => {
28825
- if (item.type === "frame") sync$6(container, item, context);
28884
+ if (item.type === "frame") sync$7(container, item, context);
28826
28885
  },
28827
28886
  renderFar: (graphics, item, context) => {
28828
28887
  if (item.type !== "frame" || item.props.decoration === "none") return;
@@ -28832,14 +28891,14 @@ const frameCardRenderer = {
28832
28891
  });
28833
28892
  },
28834
28893
  destroy: (container) => {
28835
- partsByContainer$6.delete(container);
28894
+ partsByContainer$7.delete(container);
28836
28895
  container.destroy({ children: true });
28837
28896
  }
28838
28897
  };
28839
28898
  //#endregion
28840
28899
  //#region ../sdk/src/board/render/renderers/shape-card-renderer.ts
28841
28900
  const DEFAULT_ROUNDED_RADIUS = 8;
28842
- const partsByContainer$5 = /* @__PURE__ */ new WeakMap();
28901
+ const partsByContainer$6 = /* @__PURE__ */ new WeakMap();
28843
28902
  const pathCache = /* @__PURE__ */ new Map();
28844
28903
  function parsedPath(d) {
28845
28904
  if (pathCache.has(d)) return pathCache.get(d) ?? null;
@@ -28920,14 +28979,12 @@ function outlinePoints(geometry, width, height, radius) {
28920
28979
  default: return roundRectPoints(width, height, radius);
28921
28980
  }
28922
28981
  }
28923
- function sync$5(container, item, context) {
28924
- const parts = partsByContainer$5.get(container);
28982
+ function sync$6(container, item, context) {
28983
+ const parts = partsByContainer$6.get(container);
28925
28984
  if (!parts) return;
28926
28985
  positionShell(parts.root, item);
28927
28986
  const { width, height } = item.frame;
28928
28987
  const { props, style } = item;
28929
- const selected = context.selectedIds.has(item.id);
28930
- const hovered = context.hoveredId === item.id;
28931
28988
  const stroke = itemColor(context, style.stroke ?? style.fill, "brand");
28932
28989
  const fill = itemColor(context, style.fill ?? style.stroke, "brand", "fill");
28933
28990
  const fillOpacity = (style.fillOpacity ?? (style.fill ? 1 : 0)) * itemAlpha(context, style.fill ?? style.stroke);
@@ -28937,8 +28994,6 @@ function sync$5(container, item, context) {
28937
28994
  const visualSig = [
28938
28995
  width,
28939
28996
  height,
28940
- selected,
28941
- hovered,
28942
28997
  props.geometry,
28943
28998
  props.path,
28944
28999
  fill,
@@ -28948,8 +29003,7 @@ function sync$5(container, item, context) {
28948
29003
  strokeWidth,
28949
29004
  style.dash,
28950
29005
  style.radius,
28951
- style.trim,
28952
- context.palette.muted
29006
+ style.trim
28953
29007
  ].join("|");
28954
29008
  if (visualSig !== parts.visualSig) {
28955
29009
  parts.visualSig = visualSig;
@@ -28961,8 +29015,7 @@ function sync$5(container, item, context) {
28961
29015
  alpha: fillOpacity
28962
29016
  });
28963
29017
  }
28964
- const lineWidth = selected ? Math.max(2.5, strokeWidth) : strokeWidth;
28965
- const pattern = dashPattern(style.dash, lineWidth);
29018
+ const pattern = dashPattern(style.dash, strokeWidth);
28966
29019
  const partial = style.trim !== void 0 && style.trim < 1;
28967
29020
  if (strokeWidth > 0 && (!partial || style.trim)) {
28968
29021
  if ((pattern || partial) && props.geometry !== "path") {
@@ -28970,9 +29023,9 @@ function sync$5(container, item, context) {
28970
29023
  tracePolyline(parts.shape, trimPolyline(outlinePoints(props.geometry, width, height, radius), style.trim), pattern);
28971
29024
  } else traceShapeOutline(parts.shape, props, width, height, style.radius);
28972
29025
  parts.shape.stroke({
28973
- color: hovered && !selected ? context.palette.muted : stroke,
28974
- width: lineWidth,
28975
- alpha: (selected ? 1 : hovered ? .95 : .85) * strokeAlpha,
29026
+ color: stroke,
29027
+ width: strokeWidth,
29028
+ alpha: .85 * strokeAlpha,
28976
29029
  cap: partial || pattern ? "round" : "butt",
28977
29030
  join: "round"
28978
29031
  });
@@ -29021,7 +29074,7 @@ const shapeCardRenderer = {
29021
29074
  });
29022
29075
  label.anchor.set(.5);
29023
29076
  root.addChild(shape, label);
29024
- partsByContainer$5.set(root, {
29077
+ partsByContainer$6.set(root, {
29025
29078
  root,
29026
29079
  shape,
29027
29080
  label,
@@ -29030,11 +29083,11 @@ const shapeCardRenderer = {
29030
29083
  wrapWidth: 0,
29031
29084
  resolution
29032
29085
  });
29033
- if (item.type === "shape") sync$5(root, item, context);
29086
+ if (item.type === "shape") sync$6(root, item, context);
29034
29087
  return root;
29035
29088
  },
29036
29089
  update: (container, item, context) => {
29037
- if (item.type === "shape") sync$5(container, item, context);
29090
+ if (item.type === "shape") sync$6(container, item, context);
29038
29091
  },
29039
29092
  renderFar: (graphics, item, context) => {
29040
29093
  if (item.type !== "shape") return;
@@ -29046,15 +29099,15 @@ const shapeCardRenderer = {
29046
29099
  });
29047
29100
  },
29048
29101
  destroy: (container) => {
29049
- partsByContainer$5.get(container)?.root.destroy({ children: true });
29050
- partsByContainer$5.delete(container);
29102
+ partsByContainer$6.get(container)?.root.destroy({ children: true });
29103
+ partsByContainer$6.delete(container);
29051
29104
  }
29052
29105
  };
29053
29106
  //#endregion
29054
29107
  //#region ../sdk/src/board/render/renderers/custom-card-renderer.ts
29055
- const partsByContainer$4 = /* @__PURE__ */ new WeakMap();
29056
- function sync$4(container, item, context) {
29057
- const parts = partsByContainer$4.get(container);
29108
+ const partsByContainer$5 = /* @__PURE__ */ new WeakMap();
29109
+ function sync$5(container, item, context) {
29110
+ const parts = partsByContainer$5.get(container);
29058
29111
  if (!parts) return;
29059
29112
  if (parts.id !== item.id) {
29060
29113
  context.scripts?.release(parts.id);
@@ -29066,27 +29119,25 @@ function sync$4(container, item, context) {
29066
29119
  const texture = context.scripts?.frame(item, context.time, resolution) ?? null;
29067
29120
  const error = context.scripts?.error(item.id) ?? null;
29068
29121
  parts.sprite.visible = Boolean(texture);
29069
- if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
29122
+ const display = texture ?? Texture.EMPTY;
29123
+ if (parts.sprite.texture !== display) parts.sprite.texture = display;
29070
29124
  parts.sprite.width = width;
29071
29125
  parts.sprite.height = height;
29072
- const selected = context.selectedIds.has(item.id);
29073
29126
  const ready = Boolean(texture) || context.scripts?.ready?.(item.id) === true;
29074
29127
  const message = error ? `${item.script.src}\n${error}` : ready ? "" : item.script.src;
29075
29128
  const sig = [
29076
29129
  width,
29077
29130
  height,
29078
- selected,
29079
29131
  Boolean(texture),
29080
29132
  message,
29081
- context.palette.border,
29082
- context.palette.muted
29133
+ context.themeKey
29083
29134
  ].join("|");
29084
29135
  if (sig === parts.sig) return;
29085
29136
  parts.sig = sig;
29086
29137
  parts.placeholder.clear();
29087
- if (!ready || selected || error) parts.placeholder.rect(0, 0, width, height).stroke({
29088
- color: selected ? context.palette.brand : context.palette.border,
29089
- width: selected ? 2 : 1,
29138
+ if (!ready || error) parts.placeholder.rect(0, 0, width, height).stroke({
29139
+ color: context.palette.border,
29140
+ width: 1,
29090
29141
  alpha: .8
29091
29142
  });
29092
29143
  parts.label.visible = !ready || Boolean(error);
@@ -29116,7 +29167,7 @@ const customCardRenderer = {
29116
29167
  });
29117
29168
  label.anchor.set(.5);
29118
29169
  root.addChild(sprite, placeholder, label);
29119
- partsByContainer$4.set(root, {
29170
+ partsByContainer$5.set(root, {
29120
29171
  id: item.id,
29121
29172
  root,
29122
29173
  sprite,
@@ -29124,11 +29175,11 @@ const customCardRenderer = {
29124
29175
  label,
29125
29176
  sig: ""
29126
29177
  });
29127
- if (item.type === "custom") sync$4(root, item, context);
29178
+ if (item.type === "custom") sync$5(root, item, context);
29128
29179
  return root;
29129
29180
  },
29130
29181
  update: (container, item, context) => {
29131
- if (item.type === "custom") sync$4(container, item, context);
29182
+ if (item.type === "custom") sync$5(container, item, context);
29132
29183
  },
29133
29184
  renderFar: (graphics, item, context) => {
29134
29185
  drawFarPlate(graphics, item.frame, {
@@ -29137,27 +29188,15 @@ const customCardRenderer = {
29137
29188
  });
29138
29189
  },
29139
29190
  destroy: (container, context) => {
29140
- const parts = partsByContainer$4.get(container);
29191
+ const parts = partsByContainer$5.get(container);
29141
29192
  if (parts) context.scripts?.release(parts.id);
29142
29193
  parts?.root.destroy({ children: true });
29143
- partsByContainer$4.delete(container);
29194
+ partsByContainer$5.delete(container);
29144
29195
  }
29145
29196
  };
29146
29197
  //#endregion
29147
29198
  //#region ../sdk/src/board/render/renderers/image-card-renderer.ts
29148
- const partsByContainer$3 = /* @__PURE__ */ new WeakMap();
29149
- const RADIUS$2 = 2;
29150
- function layoutContain$1(sprite, width, height) {
29151
- const texture = sprite.texture;
29152
- const tw = texture.width;
29153
- const th = texture.height;
29154
- if (!tw || !th) return;
29155
- const scale = Math.min(width / tw, height / th);
29156
- sprite.width = tw * scale;
29157
- sprite.height = th * scale;
29158
- sprite.x = (width - sprite.width) / 2;
29159
- sprite.y = (height - sprite.height) / 2;
29160
- }
29199
+ const partsByContainer$4 = /* @__PURE__ */ new WeakMap();
29161
29200
  function cropRect(crop) {
29162
29201
  if (!crop) return null;
29163
29202
  const w = Math.min(crop.w, 1 - crop.x);
@@ -29191,116 +29230,640 @@ function showTexture(parts, texture, crop) {
29191
29230
  if (parts.sprite.texture !== display) parts.sprite.texture = display;
29192
29231
  if (previous && previous !== parts.cropped) previous.destroy(false);
29193
29232
  }
29233
+ const PRINT_MARGIN = 6;
29234
+ function sync$4(container, item, context) {
29235
+ const parts = partsByContainer$4.get(container);
29236
+ if (!parts) return;
29237
+ positionShell(parts.root, item);
29238
+ const { width, height } = item.frame;
29239
+ const key = context.assetKey(item);
29240
+ const texture = key ? context.getTexture(key) : null;
29241
+ const failed = Boolean(key && !texture && context.hasError(key));
29242
+ const elevation = cardElevation(context, item.id);
29243
+ const sig = [
29244
+ key ?? "",
29245
+ width,
29246
+ height,
29247
+ texture ? `${texture.width}x${texture.height}` : "none",
29248
+ JSON.stringify(item.props.crop ?? null),
29249
+ failed,
29250
+ elevation,
29251
+ context.themeKey
29252
+ ].join("|");
29253
+ if (sig === parts.sig) return;
29254
+ parts.sig = sig;
29255
+ showTexture(parts, texture, item.props.crop);
29256
+ const display = parts.sprite.texture;
29257
+ const framed = context.style.media === "framed";
29258
+ const margin = framed ? Math.min(PRINT_MARGIN, width / 8, height / 8) : 0;
29259
+ const area = {
29260
+ x: margin,
29261
+ y: margin,
29262
+ width: width - margin * 2,
29263
+ height: height - margin * 2
29264
+ };
29265
+ const fitted = texture ? containRect(area.width, area.height, display.width, display.height) : {
29266
+ ...area,
29267
+ x: 0,
29268
+ y: 0
29269
+ };
29270
+ const picture = {
29271
+ x: area.x + fitted.x,
29272
+ y: area.y + fitted.y,
29273
+ width: fitted.width,
29274
+ height: fitted.height
29275
+ };
29276
+ const card = framed ? {
29277
+ x: picture.x - margin,
29278
+ y: picture.y - margin,
29279
+ width: picture.width + margin * 2,
29280
+ height: picture.height + margin * 2
29281
+ } : picture;
29282
+ parts.sprite.visible = Boolean(texture);
29283
+ if (texture) {
29284
+ parts.sprite.position.set(picture.x, picture.y);
29285
+ parts.sprite.width = picture.width;
29286
+ parts.sprite.height = picture.height;
29287
+ }
29288
+ traceCardOutline(parts.mask.clear(), picture, framed ? {
29289
+ corner: "round",
29290
+ radius: 0
29291
+ } : context.style).fill({ color: 16777215 });
29292
+ parts.placeholder.clear();
29293
+ if (texture && !framed) return;
29294
+ drawCardSurface(parts.placeholder, card, context.style, {
29295
+ fill: context.palette.surface,
29296
+ border: texture ? null : failed ? context.palette.border : context.palette.hover,
29297
+ elevation
29298
+ });
29299
+ if (texture) return;
29300
+ const cx = width / 2;
29301
+ const cy = height / 2;
29302
+ if (failed) parts.placeholder.moveTo(cx - 10, cy - 10).lineTo(cx + 10, cy + 10).moveTo(cx + 10, cy - 10).lineTo(cx - 10, cy + 10).stroke({
29303
+ color: context.palette.muted,
29304
+ width: 1.5,
29305
+ alpha: .8
29306
+ });
29307
+ else parts.placeholder.circle(cx, cy, 6).stroke({
29308
+ color: context.palette.muted,
29309
+ width: 1.25,
29310
+ alpha: .55
29311
+ });
29312
+ }
29313
+ const imageCardRenderer = {
29314
+ id: "image-card",
29315
+ canRender: (item) => item.type === "image",
29316
+ create: (item, context) => {
29317
+ const root = new Container();
29318
+ const sprite = new Sprite(Texture.EMPTY);
29319
+ const placeholder = new Graphics();
29320
+ const mask = new Graphics();
29321
+ root.addChild(placeholder, sprite, mask);
29322
+ sprite.mask = mask;
29323
+ partsByContainer$4.set(root, {
29324
+ root,
29325
+ sprite,
29326
+ placeholder,
29327
+ mask,
29328
+ sig: "",
29329
+ cropped: null,
29330
+ cropSource: null,
29331
+ cropKey: ""
29332
+ });
29333
+ if (item.type === "image") sync$4(root, item, context);
29334
+ return root;
29335
+ },
29336
+ update: (container, item, context) => {
29337
+ if (item.type === "image") sync$4(container, item, context);
29338
+ },
29339
+ renderFar: (graphics, item, context) => {
29340
+ drawFarPlate(graphics, item.frame, {
29341
+ fill: context.palette.hover,
29342
+ fillAlpha: .9
29343
+ });
29344
+ },
29345
+ destroy: (container) => {
29346
+ const parts = partsByContainer$4.get(container);
29347
+ parts?.root.destroy({ children: true });
29348
+ parts?.cropped?.destroy(false);
29349
+ partsByContainer$4.delete(container);
29350
+ }
29351
+ };
29352
+ //#endregion
29353
+ //#region ../sdk/src/board/render/renderers/task-card-renderer.ts
29354
+ const PADDING = 12;
29355
+ const META_HEIGHT = 28;
29356
+ const PLAY_BADGE_RADIUS = 15;
29357
+ const SHIMMER_PERIOD_MS = 1600;
29358
+ const SHIMMER_FRAME_MS = 33;
29359
+ const REVEAL_MS = 180;
29360
+ const SECOND = 1e3;
29361
+ const partsByContainer$3 = /* @__PURE__ */ new WeakMap();
29362
+ function previewKindFor(artifact) {
29363
+ if (!artifact) return "empty";
29364
+ if (artifact.type === "image" || artifact.type === "video") return "texture";
29365
+ if (artifact.type === "audio") return artifact.previewUrl ? "texture" : "waveform";
29366
+ return artifact.textExcerpt ? "text" : "empty";
29367
+ }
29368
+ function liveStatus(item, context) {
29369
+ const { snapshot } = item.props;
29370
+ const live = snapshot.status === "completed" ? null : context.taskRun(item.props.taskRunId);
29371
+ const liveStart = Date.parse(live?.startedAt ?? "");
29372
+ const working = live?.status === "pending" || live?.status === "running";
29373
+ const adopt = snapshot.status === "failed" ? working && liveStart > Date.parse(snapshot.updatedAt ?? "") : live?.status === "running" || live?.status === "pending" && snapshot.status === "pending";
29374
+ const status = adopt && live ? live.status : snapshot.status;
29375
+ const started = adopt ? liveStart : Date.parse(snapshot.startedAt ?? "");
29376
+ return {
29377
+ status,
29378
+ startedAt: Number.isFinite(started) ? started : null
29379
+ };
29380
+ }
29381
+ function stateSurfaceFor(status, kind, hasTexture, previewFailed) {
29382
+ if (kind === "texture" && !hasTexture) {
29383
+ if (status === "failed") return "failed";
29384
+ return previewFailed ? "unavailable" : "media-loading";
29385
+ }
29386
+ if (kind !== "empty") return null;
29387
+ if (status === "failed") return "failed";
29388
+ if (status === "running") return "generating";
29389
+ if (status === "pending") return "queued";
29390
+ return "empty";
29391
+ }
29392
+ function stateLabel(surface, context) {
29393
+ switch (surface) {
29394
+ case "generating": return context.labels.generating;
29395
+ case "queued": return context.labels.queued;
29396
+ case "failed": return context.labels.failed;
29397
+ case "unavailable": return context.labels.previewUnavailable;
29398
+ case "empty": return context.labels.noPreview;
29399
+ default: return "";
29400
+ }
29401
+ }
29402
+ function statusColor(status, context) {
29403
+ if (status === "failed") return context.colors.rose.stroke;
29404
+ if (status === "running") return context.colors.brand.stroke;
29405
+ return context.colors.neutral.stroke;
29406
+ }
29407
+ function drawPlayBadge(graphics, rect, context) {
29408
+ const radius = Math.min(PLAY_BADGE_RADIUS, Math.max(8, Math.min(rect.width, rect.height) * .16));
29409
+ const cx = rect.x + rect.width / 2;
29410
+ const cy = rect.y + rect.height / 2;
29411
+ graphics.circle(cx, cy, radius).fill({
29412
+ color: context.palette.bg,
29413
+ alpha: .64
29414
+ });
29415
+ const size = radius * .72;
29416
+ graphics.moveTo(cx - size * .34, cy - size * .56).lineTo(cx + size * .62, cy).lineTo(cx - size * .34, cy + size * .56).closePath().fill({
29417
+ color: context.palette.text,
29418
+ alpha: .94
29419
+ });
29420
+ }
29421
+ function drawStateMark(graphics, surface, cx, cy, color) {
29422
+ if (surface === "generating" || surface === "media-loading") {
29423
+ graphics.circle(cx, cy, 4).fill({
29424
+ color,
29425
+ alpha: .9
29426
+ });
29427
+ graphics.circle(cx - 12, cy + 7, 2.5).fill({
29428
+ color,
29429
+ alpha: .48
29430
+ });
29431
+ graphics.circle(cx + 11, cy + 6, 3).fill({
29432
+ color,
29433
+ alpha: .68
29434
+ });
29435
+ graphics.circle(cx + 3, cy - 12, 2).fill({
29436
+ color,
29437
+ alpha: .36
29438
+ });
29439
+ return;
29440
+ }
29441
+ if (surface === "queued") {
29442
+ graphics.circle(cx, cy, 10).stroke({
29443
+ color,
29444
+ width: 1.5,
29445
+ alpha: .62
29446
+ }).circle(cx, cy, 3).fill({
29447
+ color,
29448
+ alpha: .78
29449
+ });
29450
+ return;
29451
+ }
29452
+ if (surface === "failed") {
29453
+ graphics.circle(cx, cy, 11).stroke({
29454
+ color,
29455
+ width: 1.5,
29456
+ alpha: .86
29457
+ });
29458
+ graphics.moveTo(cx - 4, cy - 4).lineTo(cx + 4, cy + 4).moveTo(cx + 4, cy - 4).lineTo(cx - 4, cy + 4).stroke({
29459
+ color,
29460
+ width: 1.5,
29461
+ alpha: .94,
29462
+ cap: "round"
29463
+ });
29464
+ return;
29465
+ }
29466
+ if (surface === "unavailable") graphics.roundRect(cx - 12, cy - 9, 24, 18, 3).stroke({
29467
+ color,
29468
+ width: 1.25,
29469
+ alpha: .62
29470
+ }).moveTo(cx - 8, cy + 5).lineTo(cx + 8, cy - 5).stroke({
29471
+ color,
29472
+ width: 1.25,
29473
+ alpha: .72,
29474
+ cap: "round"
29475
+ });
29476
+ }
29477
+ function drawFailedBadge(graphics, width, color, context) {
29478
+ const cx = Math.max(12, width - 12);
29479
+ graphics.circle(cx, 12, 8).fill({
29480
+ color: context.palette.bg,
29481
+ alpha: .72
29482
+ });
29483
+ graphics.moveTo(cx - 2.5, 9.5).lineTo(cx + 2.5, 14.5).moveTo(cx + 2.5, 9.5).lineTo(cx - 2.5, 14.5).stroke({
29484
+ color,
29485
+ width: 1.5,
29486
+ alpha: .96,
29487
+ cap: "round"
29488
+ });
29489
+ }
29490
+ const shimmerGradients = /* @__PURE__ */ new Map();
29491
+ function shimmerGradient(color) {
29492
+ let gradient = shimmerGradients.get(color);
29493
+ if (!gradient) {
29494
+ const rgb = `${color >> 16 & 255}, ${color >> 8 & 255}, ${color & 255}`;
29495
+ gradient = new FillGradient({
29496
+ type: "linear",
29497
+ start: {
29498
+ x: 0,
29499
+ y: 0
29500
+ },
29501
+ end: {
29502
+ x: 1,
29503
+ y: 0
29504
+ },
29505
+ textureSpace: "local",
29506
+ colorStops: [
29507
+ {
29508
+ offset: 0,
29509
+ color: `rgba(${rgb}, 0)`
29510
+ },
29511
+ {
29512
+ offset: .5,
29513
+ color: `rgba(${rgb}, 0.07)`
29514
+ },
29515
+ {
29516
+ offset: 1,
29517
+ color: `rgba(${rgb}, 0)`
29518
+ }
29519
+ ]
29520
+ });
29521
+ shimmerGradients.set(color, gradient);
29522
+ }
29523
+ return gradient;
29524
+ }
29525
+ function drawShimmerBand(graphics, width, height, color) {
29526
+ const band = Math.max(48, width * .45);
29527
+ graphics.clear().rect(-band, 0, band, height).fill(shimmerGradient(color));
29528
+ }
29529
+ const MOSAIC_FRAME_MS = 160;
29530
+ const MOSAIC_CELL = 8;
29531
+ function drawMosaic(graphics, width, height, color, frame) {
29532
+ const columns = Math.max(1, Math.floor(width / MOSAIC_CELL));
29533
+ const rows = Math.max(1, Math.floor(height / MOSAIC_CELL));
29534
+ const left = (width - columns * MOSAIC_CELL) / 2;
29535
+ const top = (height - rows * MOSAIC_CELL) / 2;
29536
+ graphics.clear();
29537
+ for (const [threshold, alpha] of [[.08, .16], [.02, .3]]) {
29538
+ for (let row = 0; row < rows; row += 1) for (let column = 0; column < columns; column += 1) {
29539
+ let hash = Math.imul(column + 1, 2654435761) ^ Math.imul(row + 1, 2246822507) ^ Math.imul(frame + 1, 3266489909);
29540
+ hash ^= hash >>> 15;
29541
+ hash = Math.imul(hash, 739982445);
29542
+ hash ^= hash >>> 12;
29543
+ if ((hash >>> 0) / 4294967295 < threshold) graphics.rect(left + column * MOSAIC_CELL + 1, top + row * MOSAIC_CELL + 1, 6, 6);
29544
+ }
29545
+ graphics.fill({
29546
+ color,
29547
+ alpha
29548
+ });
29549
+ }
29550
+ }
29551
+ function advance(parts, clock) {
29552
+ const { motion } = parts;
29553
+ let next = null;
29554
+ if (motion.mosaic) {
29555
+ const frame = Math.floor(clock / MOSAIC_FRAME_MS);
29556
+ if (frame !== motion.mosaic.step) {
29557
+ motion.mosaic.step = frame;
29558
+ drawMosaic(parts.shimmer, motion.width, motion.mosaic.height, motion.mosaic.color, frame);
29559
+ }
29560
+ next = MOSAIC_FRAME_MS - clock % MOSAIC_FRAME_MS;
29561
+ } else if (motion.shimmer) {
29562
+ const travel = motion.width + parts.shimmer.width;
29563
+ parts.shimmer.x = clock % SHIMMER_PERIOD_MS / SHIMMER_PERIOD_MS * travel;
29564
+ next = SHIMMER_FRAME_MS;
29565
+ }
29566
+ if (motion.startedAt !== null) {
29567
+ const elapsedMs = Date.now() - motion.startedAt;
29568
+ const elapsed = formatBoardDuration(elapsedMs);
29569
+ if (elapsed !== motion.elapsed) {
29570
+ motion.elapsed = elapsed;
29571
+ parts.body.text = `${motion.label} · ${elapsed}`;
29572
+ }
29573
+ const untilNextSecond = SECOND - Math.max(0, elapsedMs) % SECOND;
29574
+ next = next === null ? untilNextSecond : Math.min(next, untilNextSecond);
29575
+ }
29576
+ if (motion.revealFrom !== null) {
29577
+ const progress = Math.min(1, (clock - motion.revealFrom) / REVEAL_MS);
29578
+ parts.preview.alpha = progress * (2 - progress);
29579
+ if (progress >= 1) motion.revealFrom = null;
29580
+ else next = 0;
29581
+ }
29582
+ return next;
29583
+ }
29194
29584
  function sync$3(container, item, context) {
29195
29585
  const parts = partsByContainer$3.get(container);
29196
29586
  if (!parts) return;
29197
29587
  positionShell(parts.root, item);
29198
29588
  const { width, height } = item.frame;
29199
- const selected = context.selectedIds.has(item.id);
29200
- const hovered = context.hoveredId === item.id;
29589
+ const { snapshot } = item.props;
29590
+ const { status, startedAt } = liveStatus(item, context);
29591
+ const full = boardCardDetailed(context.zoom);
29592
+ const elevation = cardElevation(context, item.id);
29593
+ syncCardTag(parts.tag, {
29594
+ glyph: "task",
29595
+ text: snapshot.title,
29596
+ width
29597
+ }, context);
29598
+ const working = status === "pending" || status === "running";
29599
+ const color = statusColor(status, context);
29201
29600
  const key = context.assetKey(item);
29601
+ if (key !== parts.assetKey) {
29602
+ if (parts.assetKey) context.releaseTexture(parts.assetKey);
29603
+ parts.assetKey = key;
29604
+ if (key) context.acquireTexture(key);
29605
+ }
29202
29606
  const texture = key ? context.getTexture(key) : null;
29203
- const failed = Boolean(key && !texture && context.hasError(key));
29204
- const texId = texture ? `${texture.width}x${texture.height}` : "none";
29205
- const sig = [
29206
- key ?? "",
29607
+ const previewFailed = Boolean(key && !texture && context.hasError(key));
29608
+ const artifact = featuredTaskArtifact(snapshot.artifacts);
29609
+ const kind = previewKindFor(artifact);
29610
+ const surface = stateSurfaceFor(status, kind, Boolean(texture), previewFailed);
29611
+ const frame = {
29612
+ x: 1,
29613
+ y: 1,
29614
+ width: Math.max(1, width - 2),
29615
+ height: Math.max(1, height - 2)
29616
+ };
29617
+ const metaText = artifact || working ? snapshot.model ?? "" : "";
29618
+ const showMeta = full && Boolean(metaText);
29619
+ const failedWithOutput = status === "failed" && kind !== "empty";
29620
+ const count = artifact && snapshot.artifactCount > 1 ? String(snapshot.artifactCount) : "";
29621
+ const showBadge = full && Boolean(count);
29622
+ const label = stateLabel(surface, context);
29623
+ const counting = full && working && startedAt !== null && surface !== null;
29624
+ const elapsed = counting ? formatBoardDuration(Date.now() - startedAt) : "";
29625
+ const detailText = !full || !surface ? "" : status === "failed" ? snapshot.errorExcerpt ?? "" : working ? snapshot.promptExcerpt ?? "" : "";
29626
+ const textArtifact = kind === "text" && artifact?.type === "text";
29627
+ const bodyText = textArtifact ? artifact.textExcerpt : counting ? `${label} · ${elapsed}` : label;
29628
+ syncTextResolution(parts.body, parts, context.zoom);
29629
+ syncTextResolution(parts.detail, parts, context.zoom);
29630
+ syncTextResolution(parts.meta, parts, context.zoom);
29631
+ syncTextResolution(parts.badge, parts, context.zoom);
29632
+ const textSig = [
29633
+ counting ? label : bodyText,
29634
+ detailText,
29635
+ metaText,
29636
+ count,
29637
+ kind,
29638
+ surface,
29639
+ full,
29207
29640
  width,
29208
29641
  height,
29209
- selected,
29210
- hovered,
29211
- texId,
29212
- JSON.stringify(item.props.crop ?? null),
29213
- failed,
29214
- context.colorScheme
29642
+ color,
29643
+ context.palette.text,
29644
+ context.palette.muted,
29645
+ context.style.font
29215
29646
  ].join("|");
29216
- if (sig === parts.sig) return;
29217
- parts.sig = sig;
29218
- showTexture(parts, texture, item.props.crop);
29219
- parts.sprite.visible = Boolean(texture);
29220
- if (texture) layoutContain$1(parts.sprite, width, height);
29221
- parts.placeholder.clear();
29222
- if (!texture) {
29223
- parts.placeholder.roundRect(0, 0, width, height, RADIUS$2).fill({
29224
- color: context.palette.surface,
29225
- alpha: .85
29226
- }).roundRect(0, 0, width, height, RADIUS$2).stroke({
29227
- color: failed ? context.palette.border : context.palette.hover,
29228
- width: 1,
29229
- alpha: .9
29647
+ if (textSig !== parts.textSig) {
29648
+ parts.textSig = textSig;
29649
+ parts.body.style.fontFamily = context.style.font;
29650
+ parts.detail.style.fontFamily = context.style.font;
29651
+ const wrap = Math.max(24, width - 24);
29652
+ parts.body.style.fill = surface === "failed" ? color : context.palette.text;
29653
+ parts.body.anchor.set(textArtifact ? 0 : .5, 0);
29654
+ fitTextToLines(parts.body, bodyText, textArtifact ? Math.max(1, Math.floor((height - 24 - (showMeta ? META_HEIGHT : 0)) / 16)) : 1, wrap);
29655
+ parts.detail.style.fill = context.palette.muted;
29656
+ fitTextToLines(parts.detail, detailText, height - 24 > 120 ? 2 : 1, Math.min(wrap, 360));
29657
+ parts.meta.style.fill = context.palette.text;
29658
+ fitTextToLines(parts.meta, metaText, 1, wrap);
29659
+ parts.badge.style.fill = context.palette.text;
29660
+ if (parts.badge.text !== count) parts.badge.text = count;
29661
+ }
29662
+ const badgeWidth = Math.max(22, parts.badge.width + 12);
29663
+ const visualSig = [
29664
+ width,
29665
+ height,
29666
+ full,
29667
+ elevation,
29668
+ color,
29669
+ kind,
29670
+ surface,
29671
+ showMeta,
29672
+ showBadge && badgeWidth,
29673
+ failedWithOutput,
29674
+ artifact?.type ?? "none",
29675
+ artifact?.id ?? "none",
29676
+ item.props.taskRunId,
29677
+ texture ? `${texture.width}x${texture.height}` : "none",
29678
+ context.themeKey
29679
+ ].join("|");
29680
+ if (visualSig !== parts.visualSig) {
29681
+ parts.visualSig = visualSig;
29682
+ const rect = {
29683
+ x: 0,
29684
+ y: 0,
29685
+ width,
29686
+ height
29687
+ };
29688
+ drawCardSurface(parts.plate.clear(), rect, context.style, {
29689
+ fill: context.palette.surface,
29690
+ border: context.palette.border,
29691
+ borderAlpha: .82,
29692
+ elevation
29230
29693
  });
29231
- const cx = width / 2;
29232
- const cy = height / 2;
29233
- if (failed) parts.placeholder.moveTo(cx - 10, cy - 10).lineTo(cx + 10, cy + 10).moveTo(cx + 10, cy - 10).lineTo(cx - 10, cy + 10).stroke({
29234
- color: context.palette.muted,
29235
- width: 1.5,
29236
- alpha: .8
29694
+ traceCardOutline(parts.clip.clear(), rect, context.style, -1).fill({ color: 16777215 });
29695
+ parts.previewBg.clear().rect(frame.x, frame.y, frame.width, frame.height).fill({
29696
+ color: context.palette.hover,
29697
+ alpha: .5
29237
29698
  });
29238
- else parts.placeholder.circle(cx, cy, 6).stroke({
29239
- color: context.palette.muted,
29240
- width: 1.25,
29241
- alpha: .55
29699
+ parts.previewMask.clear().rect(frame.x, frame.y, frame.width, frame.height).fill({ color: 16777215 });
29700
+ if (context.style.progress === "shimmer") drawShimmerBand(parts.shimmer, frame.width, frame.height, context.palette.text);
29701
+ parts.shimmer.position.set(context.style.progress === "shimmer" ? 0 : frame.x, frame.y);
29702
+ parts.previewArt.clear();
29703
+ if (kind === "waveform") {
29704
+ const bottomInset = showMeta ? META_HEIGHT : 0;
29705
+ drawAudioWaveform(parts.previewArt, item.props.taskRunId, {
29706
+ x: frame.x + PADDING,
29707
+ y: frame.y + PADDING,
29708
+ width: Math.max(1, frame.width - 24),
29709
+ height: Math.max(1, frame.height - 24 - bottomInset)
29710
+ }, context.colors.brand.stroke);
29711
+ }
29712
+ if (full && (artifact?.type === "audio" || artifact?.type === "video" && texture)) drawPlayBadge(parts.previewArt, frame, context);
29713
+ if (surface) {
29714
+ const markY = frame.y + frame.height / 2 - (full && label ? 18 : 0);
29715
+ drawStateMark(parts.previewArt, surface, frame.x + frame.width / 2, markY, surface === "failed" ? color : context.colors.neutral.stroke);
29716
+ }
29717
+ if (showMeta) parts.previewArt.rect(frame.x, frame.y + frame.height - META_HEIGHT, frame.width, META_HEIGHT).fill({
29718
+ color: context.palette.bg,
29719
+ alpha: .68
29242
29720
  });
29243
- }
29244
- parts.placeholder.visible = !texture;
29245
- if (selected || hovered) {
29246
- const hasPixels = Boolean(texture) && parts.sprite.width > 0;
29247
- const ox = hasPixels ? parts.sprite.x : 0;
29248
- const oy = hasPixels ? parts.sprite.y : 0;
29249
- const ow = hasPixels ? parts.sprite.width : width;
29250
- const oh = hasPixels ? parts.sprite.height : height;
29251
- parts.placeholder.visible = true;
29252
- parts.placeholder.roundRect(ox, oy, ow, oh, RADIUS$2).stroke({
29253
- color: selected ? context.palette.brand : context.palette.muted,
29254
- width: selected ? 2 : 1,
29255
- alpha: selected ? .95 : .7
29721
+ if (showBadge) parts.previewArt.roundRect(width - badgeWidth - 8, 8, badgeWidth, 20, 10).fill({
29722
+ color: context.palette.bg,
29723
+ alpha: .72
29256
29724
  });
29725
+ if (failedWithOutput) drawFailedBadge(parts.previewArt, width, color, context);
29726
+ }
29727
+ const showPreview = kind === "texture" && Boolean(texture);
29728
+ const revealKey = showPreview ? `${artifact?.id}:${texture?.uid}` : null;
29729
+ const sameCard = parts.motion.itemId === item.id;
29730
+ parts.motion.itemId = item.id;
29731
+ if (revealKey !== parts.motion.revealKey || !sameCard) {
29732
+ const appearing = sameCard && parts.motion.revealKey === null && revealKey !== null;
29733
+ parts.motion.revealKey = revealKey;
29734
+ parts.motion.revealFrom = appearing && !context.reducedMotion ? performance.now() : null;
29735
+ parts.preview.alpha = parts.motion.revealFrom === null ? 1 : 0;
29736
+ }
29737
+ parts.preview.visible = showPreview;
29738
+ if (showPreview && texture) {
29739
+ if (parts.preview.texture !== texture) parts.preview.texture = texture;
29740
+ const fitted = containRect(frame.width, frame.height, texture.width, texture.height);
29741
+ parts.preview.position.set(frame.x + fitted.x, frame.y + fitted.y);
29742
+ parts.preview.width = fitted.width;
29743
+ parts.preview.height = fitted.height;
29257
29744
  }
29745
+ parts.body.visible = full && Boolean(bodyText);
29746
+ parts.detail.visible = full && Boolean(detailText);
29747
+ parts.meta.visible = showMeta;
29748
+ parts.badge.visible = showBadge;
29749
+ if (textArtifact) parts.body.position.set(PADDING, PADDING);
29750
+ else parts.body.position.set(width / 2, height / 2 + 2);
29751
+ parts.detail.position.set(width / 2, height / 2 + 2 + parts.body.height + 4);
29752
+ parts.meta.position.set(PADDING, height - META_HEIGHT + 7);
29753
+ parts.badge.position.set(width - 8 - badgeWidth / 2, 18);
29754
+ parts.motion.width = frame.width;
29755
+ parts.motion.shimmer = full && working && kind !== "texture" && !context.reducedMotion;
29756
+ const mosaic = parts.motion.shimmer && context.style.progress === "mosaic";
29757
+ parts.motion.mosaic = mosaic ? {
29758
+ color: context.palette.brand,
29759
+ height: frame.height,
29760
+ step: -1
29761
+ } : null;
29762
+ parts.shimmer.visible = parts.motion.shimmer;
29763
+ parts.motion.startedAt = counting ? startedAt : null;
29764
+ parts.motion.label = label;
29765
+ parts.motion.elapsed = elapsed;
29258
29766
  }
29259
- const imageCardRenderer = {
29260
- id: "image-card",
29261
- canRender: (item) => item.type === "image",
29767
+ const taskCardRenderer = {
29768
+ id: "task-card",
29769
+ canRender: (item) => item.type === "task",
29262
29770
  create: (item, context) => {
29263
29771
  const root = new Container();
29264
- const sprite = new Sprite(Texture.EMPTY);
29265
- const placeholder = new Graphics();
29266
- const mask = new Graphics();
29267
- const maskBox = () => {
29268
- mask.clear();
29269
- if (item.type === "image") mask.roundRect(0, 0, item.frame.width, item.frame.height, RADIUS$2).fill({ color: 16777215 });
29270
- };
29271
- maskBox();
29272
- root.addChild(placeholder, sprite, mask);
29273
- sprite.mask = mask;
29772
+ const plate = new Graphics();
29773
+ const clip = new Graphics();
29774
+ const previewBg = new Graphics();
29775
+ const preview = new Sprite();
29776
+ const previewMask = new Graphics();
29777
+ const shimmer = new Graphics();
29778
+ const previewArt = new Graphics();
29779
+ const resolution = textResolutionForZoom(context.zoom);
29780
+ const text = (fontFamily, fontSize, fontWeight) => new Text({
29781
+ text: "",
29782
+ style: {
29783
+ fontFamily,
29784
+ fontSize,
29785
+ fontWeight,
29786
+ wordWrap: true,
29787
+ breakWords: true
29788
+ },
29789
+ resolution,
29790
+ roundPixels: true
29791
+ });
29792
+ const body = text(BOARD_FONT_STACK, 11.5, "500");
29793
+ const detail = text(BOARD_FONT_STACK, 10.5, "400");
29794
+ const meta = text(BOARD_MONO_FONT_STACK, 10, "500");
29795
+ const badge = text(BOARD_MONO_FONT_STACK, 10, "500");
29796
+ badge.style.wordWrap = false;
29797
+ badge.anchor.set(.5);
29798
+ detail.anchor.set(.5, 0);
29799
+ detail.style.align = "center";
29800
+ preview.mask = previewMask;
29801
+ const content = new Container();
29802
+ content.mask = clip;
29803
+ content.addChild(previewBg, shimmer, preview, previewMask, previewArt, body, detail, meta, badge);
29804
+ const tag = createCardTag();
29805
+ root.addChild(plate, content, clip, tag.root);
29274
29806
  partsByContainer$3.set(root, {
29275
29807
  root,
29276
- sprite,
29277
- placeholder,
29278
- sig: "",
29279
- cropped: null,
29280
- cropSource: null,
29281
- cropKey: ""
29808
+ tag,
29809
+ plate,
29810
+ clip,
29811
+ previewBg,
29812
+ preview,
29813
+ previewMask,
29814
+ shimmer,
29815
+ previewArt,
29816
+ badge,
29817
+ body,
29818
+ detail,
29819
+ meta,
29820
+ assetKey: null,
29821
+ visualSig: "",
29822
+ textSig: "",
29823
+ resolution,
29824
+ motion: {
29825
+ itemId: "",
29826
+ width: 0,
29827
+ shimmer: false,
29828
+ mosaic: null,
29829
+ startedAt: null,
29830
+ label: "",
29831
+ elapsed: "",
29832
+ revealFrom: null,
29833
+ revealKey: null
29834
+ }
29282
29835
  });
29283
- if (item.type === "image") sync$3(root, item, context);
29836
+ if (item.type === "task") sync$3(root, item, context);
29284
29837
  return root;
29285
29838
  },
29286
29839
  update: (container, item, context) => {
29287
- if (item.type !== "image") return;
29840
+ if (item.type === "task") sync$3(container, item, context);
29841
+ },
29842
+ tick: (container, clock) => {
29288
29843
  const parts = partsByContainer$3.get(container);
29289
- if (!parts) return;
29290
- const mask = parts.root.children.find((child) => child !== parts.sprite && child !== parts.placeholder);
29291
- if (mask) mask.clear().roundRect(0, 0, item.frame.width, item.frame.height, RADIUS$2).fill({ color: 16777215 });
29292
- sync$3(container, item, context);
29844
+ return parts ? advance(parts, clock) : null;
29845
+ },
29846
+ zoom: (container, zoom) => {
29847
+ const tag = partsByContainer$3.get(container)?.tag;
29848
+ if (tag) zoomCardTag(tag, zoom);
29293
29849
  },
29294
29850
  renderFar: (graphics, item, context) => {
29851
+ if (item.type !== "task") return;
29852
+ const { status } = liveStatus(item, context);
29853
+ const active = status === "failed" || status === "running" || status === "pending";
29295
29854
  drawFarPlate(graphics, item.frame, {
29296
- fill: context.palette.hover,
29297
- fillAlpha: .9
29855
+ fill: context.palette.surface,
29856
+ fillAlpha: .82,
29857
+ ...active ? {
29858
+ accent: statusColor(status, context),
29859
+ accentAlpha: .9
29860
+ } : {}
29298
29861
  });
29299
29862
  },
29300
- destroy: (container) => {
29863
+ destroy: (container, context) => {
29301
29864
  const parts = partsByContainer$3.get(container);
29865
+ if (parts?.assetKey) context.releaseTexture(parts.assetKey);
29302
29866
  parts?.root.destroy({ children: true });
29303
- parts?.cropped?.destroy(false);
29304
29867
  partsByContainer$3.delete(container);
29305
29868
  }
29306
29869
  };
@@ -29404,37 +29967,37 @@ const textCardRenderer = {
29404
29967
  };
29405
29968
  //#endregion
29406
29969
  //#region ../sdk/src/board/render/renderers/unknown-card-renderer.ts
29407
- const RADIUS$1 = 10;
29408
29970
  const partsByContainer$1 = /* @__PURE__ */ new WeakMap();
29409
29971
  function sync$1(container, item, context) {
29410
29972
  const parts = partsByContainer$1.get(container);
29411
29973
  if (!parts) return;
29412
29974
  positionShell(parts.root, item);
29413
29975
  const { width, height } = item.frame;
29414
- const selected = context.selectedIds.has(item.id);
29415
29976
  const realType = item.type;
29416
29977
  syncTextResolution(parts.label, parts, context.zoom);
29978
+ const elevation = cardElevation(context, item.id);
29417
29979
  const visualSig = [
29418
29980
  width,
29419
29981
  height,
29420
- selected,
29421
- context.palette.surface,
29422
- context.palette.brand,
29423
- context.palette.border,
29424
- context.palette.muted
29982
+ elevation,
29983
+ context.themeKey
29425
29984
  ].join("|");
29426
29985
  if (visualSig !== parts.visualSig) {
29427
29986
  parts.visualSig = visualSig;
29428
- parts.box.clear();
29429
- parts.box.roundRect(0, 0, width, height, RADIUS$1).fill({
29430
- color: context.palette.surface,
29431
- alpha: .7
29432
- }).roundRect(0, 0, width, height, RADIUS$1).stroke({
29433
- color: selected ? context.palette.brand : context.palette.border,
29434
- width: selected ? 2 : 1,
29435
- alpha: .8
29987
+ const rect = {
29988
+ x: 0,
29989
+ y: 0,
29990
+ width,
29991
+ height
29992
+ };
29993
+ drawCardSurface(parts.box.clear(), rect, context.style, {
29994
+ fill: context.palette.surface,
29995
+ fillAlpha: .7,
29996
+ border: context.palette.border,
29997
+ borderAlpha: .8,
29998
+ elevation
29436
29999
  });
29437
- parts.box.roundRect(6, 6, width - 12, height - 12, 6).stroke({
30000
+ traceCardOutline(parts.box, rect, context.style, -6).stroke({
29438
30001
  color: context.palette.muted,
29439
30002
  width: 1,
29440
30003
  alpha: .4
@@ -29496,146 +30059,145 @@ const unknownCardRenderer = {
29496
30059
  //#endregion
29497
30060
  //#region ../sdk/src/board/render/renderers/video-card-renderer.ts
29498
30061
  const partsByContainer = /* @__PURE__ */ new WeakMap();
29499
- const RADIUS = 4;
29500
- function layoutContain(sprite, width, height) {
29501
- const texture = sprite.texture;
29502
- if (!texture.width || !texture.height) return;
29503
- const scale = Math.min(width / texture.width, height / texture.height);
29504
- sprite.width = texture.width * scale;
29505
- sprite.height = texture.height * scale;
29506
- sprite.x = (width - sprite.width) / 2;
29507
- sprite.y = (height - sprite.height) / 2;
30062
+ const FILM_RAIL = 10;
30063
+ const FILM_HOLE = 4;
30064
+ function videoTitle(item) {
30065
+ return item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
29508
30066
  }
29509
30067
  function sync(container, item, context) {
29510
30068
  const parts = partsByContainer.get(container);
29511
30069
  if (!parts) return;
29512
30070
  positionShell(parts.root, item);
29513
30071
  const { width, height } = item.frame;
29514
- const selected = context.selectedIds.has(item.id);
29515
- const hovered = context.hoveredId === item.id;
29516
- const resizing = context.resizingIds.has(item.id);
29517
- const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
30072
+ const title = videoTitle(item);
30073
+ const durationMs = item.props.snapshot?.durationMs;
30074
+ const duration = durationMs ? formatBoardDuration(durationMs) : "";
30075
+ syncCardTag(parts.tag, {
30076
+ glyph: "video",
30077
+ text: duration ? `${title} · ${duration}` : title,
30078
+ width
30079
+ }, context);
29518
30080
  const key = context.assetKey(item);
29519
30081
  const texture = key ? context.getTexture(key) : null;
29520
- const failed = Boolean(key && !texture && context.hasError(key));
29521
- syncTextResolution(parts.label, parts, context.zoom);
29522
- if (parts.label.text !== title) parts.label.text = title;
29523
- syncTextWrapWidth(parts.label, parts, Math.max(1, width - 16), resizing);
29524
- parts.label.position.set(8, Math.max(8, height - parts.label.height - 8));
30082
+ const elevation = cardElevation(context, item.id);
29525
30083
  const sig = [
29526
30084
  key ?? "",
29527
30085
  texture ? `${texture.width}x${texture.height}` : "none",
29528
30086
  width,
29529
30087
  height,
29530
- selected,
29531
- hovered,
29532
- failed,
29533
- title,
29534
- item.props.time ?? 0,
29535
- parts.label.height,
29536
- context.palette.surface,
29537
- context.palette.brand,
29538
- context.palette.muted,
29539
- context.palette.border,
29540
- context.palette.hover,
29541
- context.palette.text
30088
+ elevation,
30089
+ boardCardDetailed(context.zoom),
30090
+ context.themeKey
29542
30091
  ].join("|");
29543
30092
  if (sig === parts.sig) return;
29544
30093
  parts.sig = sig;
30094
+ const framed = context.style.media === "framed";
30095
+ const rect = {
30096
+ x: 0,
30097
+ y: 0,
30098
+ width,
30099
+ height
30100
+ };
30101
+ const rail = framed ? Math.min(FILM_RAIL, height / 6) : 0;
30102
+ const screen = {
30103
+ x: framed ? rail * .6 : 0,
30104
+ y: rail,
30105
+ width: width - (framed ? rail * 1.2 : 0),
30106
+ height: height - rail * 2
30107
+ };
30108
+ drawCardSurface(parts.plate.clear(), rect, context.style, {
30109
+ fill: framed ? context.palette.text : context.palette.surface,
30110
+ border: framed ? null : context.palette.border,
30111
+ borderAlpha: .85,
30112
+ elevation
30113
+ });
30114
+ if (framed) drawFilmRails(parts.plate, width, height, rail, context.palette.bg);
30115
+ traceCardOutline(parts.mask.clear(), framed ? screen : rect, framed ? {
30116
+ corner: "round",
30117
+ radius: 1
30118
+ } : context.style).fill({ color: 16777215 });
29545
30119
  if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
29546
30120
  parts.sprite.visible = Boolean(texture);
29547
- if (texture) layoutContain(parts.sprite, width, height);
29548
- parts.mask.clear().roundRect(0, 0, width, height, RADIUS).fill({ color: 16777215 });
29549
- parts.plate.clear().roundRect(0, 0, width, height, RADIUS).fill({
29550
- color: context.palette.surface,
29551
- alpha: .96
29552
- });
29553
- parts.chrome.clear();
29554
30121
  if (texture) {
29555
- const bandHeight = Math.min(height, parts.label.height + 16);
29556
- parts.chrome.rect(0, height - bandHeight, width, bandHeight).fill({
29557
- color: context.palette.surface,
29558
- alpha: .78
29559
- });
30122
+ const fitted = containRect(screen.width, screen.height, texture.width, texture.height);
30123
+ parts.sprite.position.set(screen.x + fitted.x, screen.y + fitted.y);
30124
+ parts.sprite.width = fitted.width;
30125
+ parts.sprite.height = fitted.height;
29560
30126
  }
29561
- const cx = width / 2;
29562
- const cy = height / 2;
29563
- const radius = Math.min(22, Math.min(width, height) * .18);
29564
- parts.chrome.circle(cx, cy, radius).fill({
29565
- color: selected ? context.palette.brand : context.palette.surface,
29566
- alpha: texture ? .88 : .96
29567
- });
29568
- const triangle = radius * .55;
29569
- parts.chrome.moveTo(cx - triangle * .35, cy - triangle * .6).lineTo(cx - triangle * .35, cy + triangle * .6).lineTo(cx + triangle * .7, cy).closePath().fill({
29570
- color: context.palette.text,
29571
- alpha: .94
29572
- });
29573
- parts.chrome.roundRect(0, 0, width, height, RADIUS).stroke({
29574
- color: selected ? context.palette.brand : hovered ? context.palette.muted : context.palette.border,
29575
- width: selected ? 2 : 1,
29576
- alpha: selected ? .95 : .85
30127
+ parts.chrome.clear();
30128
+ if (boardCardDetailed(context.zoom)) drawMediaPlayKey(parts.chrome, screen.x + screen.width / 2, screen.y + screen.height / 2, Math.min(18, Math.min(screen.width, screen.height) * .16), context);
30129
+ }
30130
+ function drawFilmRails(graphics, width, height, rail, hole) {
30131
+ const size = Math.min(FILM_HOLE, rail * .45);
30132
+ const step = size * 2.5;
30133
+ const count = Math.max(1, Math.floor((width - step) / step));
30134
+ const start = (width - (count - 1) * step - size) / 2;
30135
+ for (let index = 0; index < count; index += 1) {
30136
+ const x = start + index * step;
30137
+ graphics.rect(x, (rail - size) / 2, size, size).rect(x, height - rail + (rail - size) / 2, size, size);
30138
+ }
30139
+ graphics.fill({
30140
+ color: hole,
30141
+ alpha: .9
29577
30142
  });
29578
- parts.label.style.fill = texture ? context.palette.text : context.palette.muted;
29579
30143
  }
30144
+ const videoCardRenderer = {
30145
+ id: "video-card",
30146
+ canRender: (item) => item.type === "video",
30147
+ create: (item, context) => {
30148
+ const root = new Container();
30149
+ const plate = new Graphics();
30150
+ const sprite = new Sprite(Texture.EMPTY);
30151
+ const mask = new Graphics();
30152
+ const chrome = new Graphics();
30153
+ const tag = createCardTag();
30154
+ root.addChild(plate, sprite, mask, chrome, tag.root);
30155
+ sprite.mask = mask;
30156
+ partsByContainer.set(root, {
30157
+ root,
30158
+ plate,
30159
+ sprite,
30160
+ mask,
30161
+ chrome,
30162
+ tag,
30163
+ sig: ""
30164
+ });
30165
+ if (item.type === "video") sync(root, item, context);
30166
+ return root;
30167
+ },
30168
+ update: (container, item, context) => {
30169
+ if (item.type === "video") sync(container, item, context);
30170
+ },
30171
+ zoom: (container, zoom) => {
30172
+ const tag = partsByContainer.get(container)?.tag;
30173
+ if (tag) zoomCardTag(tag, zoom);
30174
+ },
30175
+ renderFar: (graphics, item, context) => {
30176
+ drawFarPlate(graphics, item.frame, {
30177
+ fill: context.palette.surface,
30178
+ fillAlpha: .96,
30179
+ accent: context.palette.brand,
30180
+ accentAlpha: .75
30181
+ });
30182
+ },
30183
+ destroy: (container) => {
30184
+ partsByContainer.get(container)?.root.destroy({ children: true });
30185
+ partsByContainer.delete(container);
30186
+ }
30187
+ };
29580
30188
  //#endregion
29581
30189
  //#region ../sdk/src/board/render/renderers/board-renderer-registry.ts
30190
+ const DEFAULT_BOARD_RENDER_LABELS = {
30191
+ queued: "Queued",
30192
+ generating: "Generating",
30193
+ failed: "Failed",
30194
+ previewUnavailable: "Preview unavailable",
30195
+ noPreview: "No preview"
30196
+ };
29582
30197
  const builtinCardRenderers = [
29583
30198
  textCardRenderer,
29584
30199
  imageCardRenderer,
29585
- {
29586
- id: "video-card",
29587
- canRender: (item) => item.type === "video",
29588
- create: (item, context) => {
29589
- const root = new Container();
29590
- const plate = new Graphics();
29591
- const sprite = new Sprite(Texture.EMPTY);
29592
- const mask = new Graphics();
29593
- const chrome = new Graphics();
29594
- const resolution = textResolutionForZoom(context.zoom);
29595
- const label = new Text({
29596
- text: "",
29597
- style: {
29598
- fill: context.palette.muted,
29599
- fontFamily: BOARD_FONT_STACK,
29600
- fontSize: 12,
29601
- fontWeight: "500",
29602
- wordWrap: true
29603
- },
29604
- resolution,
29605
- roundPixels: true
29606
- });
29607
- root.addChild(plate, sprite, mask, chrome, label);
29608
- sprite.mask = mask;
29609
- partsByContainer.set(root, {
29610
- root,
29611
- plate,
29612
- sprite,
29613
- mask,
29614
- chrome,
29615
- label,
29616
- sig: "",
29617
- wrapWidth: 0,
29618
- resolution
29619
- });
29620
- if (item.type === "video") sync(root, item, context);
29621
- return root;
29622
- },
29623
- update: (container, item, context) => {
29624
- if (item.type === "video") sync(container, item, context);
29625
- },
29626
- renderFar: (graphics, item, context) => {
29627
- drawFarPlate(graphics, item.frame, {
29628
- fill: context.palette.surface,
29629
- fillAlpha: .96,
29630
- accent: context.palette.brand,
29631
- accentAlpha: .75
29632
- });
29633
- },
29634
- destroy: (container) => {
29635
- partsByContainer.get(container)?.root.destroy({ children: true });
29636
- partsByContainer.delete(container);
29637
- }
29638
- },
30200
+ videoCardRenderer,
29639
30201
  audioCardRenderer,
29640
30202
  fileCardRenderer,
29641
30203
  taskCardRenderer,
@@ -29960,8 +30522,16 @@ const fileProps = {
29960
30522
  coverUrl: string().optional()
29961
30523
  }).strict().optional()
29962
30524
  };
29963
- const timedFileProps = {
30525
+ const BoardMediaOriginSchema = object({
30526
+ taskRunId: string().min(1).max(160),
30527
+ artifactId: string().min(1).max(240)
30528
+ }).strict();
30529
+ const mediaProps = {
29964
30530
  ...fileProps,
30531
+ origin: BoardMediaOriginSchema.optional()
30532
+ };
30533
+ const timedFileProps = {
30534
+ ...mediaProps,
29965
30535
  time: nonNegative.optional()
29966
30536
  };
29967
30537
  const BoardCropSchema = object({
@@ -29974,7 +30544,7 @@ object({
29974
30544
  ...boxed(640, 360),
29975
30545
  type: literal("image"),
29976
30546
  props: object({
29977
- ...fileProps,
30547
+ ...mediaProps,
29978
30548
  crop: BoardCropSchema.optional()
29979
30549
  }).strict()
29980
30550
  }).strict();
@@ -30039,8 +30609,10 @@ const BoardTaskSnapshotSchema = object({
30039
30609
  title: string().min(1).max(240),
30040
30610
  model: string().max(160).optional(),
30041
30611
  promptExcerpt: string().max(480).optional(),
30612
+ errorExcerpt: string().max(240).optional(),
30042
30613
  artifactCount: number$1().int().nonnegative(),
30043
30614
  artifacts: array(BoardTaskArtifactSchema).max(6).default([]),
30615
+ startedAt: string().max(64).optional(),
30044
30616
  updatedAt: string().optional()
30045
30617
  }).strict();
30046
30618
  object({
@@ -30048,7 +30620,7 @@ object({
30048
30620
  type: literal("task"),
30049
30621
  props: object({
30050
30622
  taskRunId: string().min(1).max(160),
30051
- snapshot: BoardTaskSnapshotSchema
30623
+ snapshot: BoardTaskSnapshotSchema.describe("Maintained by the server for tasks in the Board's Space; writers may omit it.")
30052
30624
  }).strict()
30053
30625
  }).strict();
30054
30626
  const BOARD_EFFECT_KINDS = [
@@ -30337,6 +30909,8 @@ function createScriptRuntime(module, initial, host) {
30337
30909
  let patches = effects?.patches ?? {};
30338
30910
  let items = effects?.items ?? {};
30339
30911
  let space = effects?.space ?? "world";
30912
+ let anchor = effects?.anchor;
30913
+ let view = null;
30340
30914
  let hits = effects?.hits ?? {};
30341
30915
  const invalidate = () => {
30342
30916
  if (disposed) return;
@@ -30373,6 +30947,9 @@ function createScriptRuntime(module, initial, host) {
30373
30947
  get frame() {
30374
30948
  return frame;
30375
30949
  },
30950
+ get view() {
30951
+ return view;
30952
+ },
30376
30953
  random() {
30377
30954
  randomState = Math.imul(randomState, 1664525) + 1013904223 >>> 0;
30378
30955
  return randomState / 4294967296;
@@ -30383,10 +30960,11 @@ function createScriptRuntime(module, initial, host) {
30383
30960
  listeners = /* @__PURE__ */ new Set();
30384
30961
  handlers.set(type, listeners);
30385
30962
  }
30386
- listeners.add(handler);
30963
+ const listener = (event) => handler(event);
30964
+ listeners.add(listener);
30387
30965
  invalidate();
30388
30966
  return () => {
30389
- listeners.delete(handler);
30967
+ listeners.delete(listener);
30390
30968
  invalidate();
30391
30969
  };
30392
30970
  },
@@ -30405,6 +30983,7 @@ function createScriptRuntime(module, initial, host) {
30405
30983
  scene(next, options) {
30406
30984
  items = structuredClone(next);
30407
30985
  space = options?.space ?? "world";
30986
+ anchor = options?.anchor ? structuredClone(options.anchor) : void 0;
30408
30987
  invalidate();
30409
30988
  },
30410
30989
  hitRegions(next) {
@@ -30466,6 +31045,7 @@ function createScriptRuntime(module, initial, host) {
30466
31045
  },
30467
31046
  event(event) {
30468
31047
  if (disposed) return;
31048
+ if (event.type === "viewchange") view = event.data;
30469
31049
  const listeners = handlers.get(event.type);
30470
31050
  if (!listeners?.size) return;
30471
31051
  for (const handler of listeners) handler(event);
@@ -30479,6 +31059,7 @@ function createScriptRuntime(module, initial, host) {
30479
31059
  return {
30480
31060
  parameters: module.parameters,
30481
31061
  space,
31062
+ anchor,
30482
31063
  patches,
30483
31064
  items,
30484
31065
  hits,
@@ -30497,7 +31078,8 @@ function createScriptRuntime(module, initial, host) {
30497
31078
  patches,
30498
31079
  items,
30499
31080
  hits,
30500
- space
31081
+ space,
31082
+ anchor
30501
31083
  } : void 0
30502
31084
  };
30503
31085
  return clone ? structuredClone(snapshot) : snapshot;
@@ -30514,6 +31096,58 @@ function createScriptRuntime(module, initial, host) {
30514
31096
  };
30515
31097
  }
30516
31098
  //#endregion
31099
+ //#region ../sdk/src/board/render/style.ts
31100
+ const BASE = {
31101
+ corner: "round",
31102
+ radius: 6,
31103
+ selection: {
31104
+ width: 1.5,
31105
+ outset: 3,
31106
+ dash: 0,
31107
+ handle: "square",
31108
+ handleSize: 8
31109
+ },
31110
+ media: "plain",
31111
+ progress: "shimmer",
31112
+ icons: "line",
31113
+ motion: "smooth",
31114
+ font: BOARD_FONT_STACK
31115
+ };
31116
+ const SHADOW = {
31117
+ light: {
31118
+ color: 1447704,
31119
+ alpha: .06,
31120
+ blur: 12,
31121
+ y: 2
31122
+ },
31123
+ dark: {
31124
+ color: 0,
31125
+ alpha: .24,
31126
+ blur: 12,
31127
+ y: 2
31128
+ }
31129
+ };
31130
+ function defaultBoardRenderStyle(mode) {
31131
+ return {
31132
+ ...BASE,
31133
+ selection: { ...BASE.selection },
31134
+ shadow: { ...SHADOW[mode] }
31135
+ };
31136
+ }
31137
+ function resolveBoardRenderStyle(mode, style) {
31138
+ const base = defaultBoardRenderStyle(mode);
31139
+ if (!style) return base;
31140
+ return {
31141
+ ...base,
31142
+ ...style,
31143
+ selection: {
31144
+ ...base.selection,
31145
+ ...style.selection
31146
+ },
31147
+ shadow: style.shadow === void 0 ? base.shadow : style.shadow
31148
+ };
31149
+ }
31150
+ //#endregion
30517
31151
  //#region ../sdk/src/board/export/scene.ts
30518
31152
  function buildContext(input) {
30519
31153
  const missing = /* @__PURE__ */ new Set();
@@ -30527,14 +31161,20 @@ function buildContext(input) {
30527
31161
  selectedIds: /* @__PURE__ */ new Set(),
30528
31162
  hoveredId: null,
30529
31163
  resizingIds: /* @__PURE__ */ new Set(),
31164
+ liftedIds: /* @__PURE__ */ new Set(),
30530
31165
  palette: {
30531
31166
  ...defaultBoardPalette(input.colorScheme),
30532
31167
  ...input.palette
30533
31168
  },
30534
31169
  colors: input.colors ?? buildFallbackShapeColors(input.colorScheme),
30535
31170
  colorScheme: input.colorScheme,
31171
+ style: resolveBoardRenderStyle(input.colorScheme, input.style),
31172
+ themeKey: "export",
30536
31173
  rendererType: "canvas",
30537
31174
  zoom: input.scale,
31175
+ reducedMotion: true,
31176
+ labels: DEFAULT_BOARD_RENDER_LABELS,
31177
+ taskRun: () => null,
30538
31178
  scripts: input.scripts,
30539
31179
  assetKey: input.assetKey ?? imageAssetKey,
30540
31180
  getTexture: (key) => textures.get(key) ?? null,
@@ -30689,6 +31329,7 @@ function renderBoardExport(renderer, input, options = {}) {
30689
31329
  colorScheme,
30690
31330
  palette,
30691
31331
  colors: options.colors,
31332
+ style: options.style,
30692
31333
  textures: options.textures,
30693
31334
  scripts: options.scripts,
30694
31335
  assetKey: options.assetKey,