@neta-art/cohub-cli 10.0.0 → 10.1.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
  }
@@ -9449,42 +9522,6 @@ function boardFontFamily(font, stacks) {
9449
9522
  return `"${font.replaceAll("\"", "")}", ${stacks.sans}`;
9450
9523
  }
9451
9524
  //#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
9525
  //#region ../sdk/src/board/model/image-key.ts
9489
9526
  function imageAssetKey(item) {
9490
9527
  if (item.type === "image") {
@@ -27701,6 +27738,10 @@ const PADDING = 12;
27701
27738
  const META_HEIGHT = 28;
27702
27739
  const TASK_CARD_FULL_DETAIL_ZOOM = .55;
27703
27740
  const PLAY_BADGE_RADIUS = 15;
27741
+ const SHIMMER_PERIOD_MS = 1600;
27742
+ const SHIMMER_FRAME_MS = 33;
27743
+ const REVEAL_MS = 180;
27744
+ const SECOND = 1e3;
27704
27745
  const partsByContainer$11 = /* @__PURE__ */ new WeakMap();
27705
27746
  function previewKindFor(artifact) {
27706
27747
  if (!artifact) return "empty";
@@ -27708,36 +27749,51 @@ function previewKindFor(artifact) {
27708
27749
  if (artifact.type === "audio") return artifact.previewUrl ? "texture" : "waveform";
27709
27750
  return artifact.textExcerpt ? "text" : "empty";
27710
27751
  }
27711
- function stateSurfaceFor(item, kind, hasTexture, previewFailed) {
27752
+ function liveStatus(item, context) {
27753
+ const { snapshot } = item.props;
27754
+ const live = snapshot.status === "completed" ? null : context.taskRun(item.props.taskRunId);
27755
+ const liveStart = Date.parse(live?.startedAt ?? "");
27756
+ const working = live?.status === "pending" || live?.status === "running";
27757
+ const adopt = snapshot.status === "failed" ? working && liveStart > Date.parse(snapshot.updatedAt ?? "") : live?.status === "running" || live?.status === "pending" && snapshot.status === "pending";
27758
+ const status = adopt && live ? live.status : snapshot.status;
27759
+ const started = adopt ? liveStart : Date.parse(snapshot.startedAt ?? "");
27760
+ return {
27761
+ status,
27762
+ startedAt: Number.isFinite(started) ? started : null
27763
+ };
27764
+ }
27765
+ function stateSurfaceFor(status, kind, hasTexture, previewFailed) {
27712
27766
  if (kind === "texture" && !hasTexture) {
27713
- if (item.props.snapshot.status === "failed") return "failed";
27767
+ if (status === "failed") return "failed";
27714
27768
  return previewFailed ? "unavailable" : "media-loading";
27715
27769
  }
27716
27770
  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";
27771
+ if (status === "failed") return "failed";
27772
+ if (status === "running") return "generating";
27773
+ if (status === "pending") return "queued";
27720
27774
  return "empty";
27721
27775
  }
27722
- function stateLabel(surface) {
27776
+ function stateLabel(surface, context) {
27723
27777
  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";
27778
+ case "generating": return context.labels.generating;
27779
+ case "queued": return context.labels.queued;
27780
+ case "failed": return context.labels.failed;
27781
+ case "unavailable": return context.labels.previewUnavailable;
27782
+ case "empty": return context.labels.noPreview;
27729
27783
  default: return "";
27730
27784
  }
27731
27785
  }
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;
27786
+ function statusColor(status, context) {
27787
+ if (status === "failed") return context.colors.rose.stroke;
27788
+ if (status === "running") return context.colors.brand.stroke;
27735
27789
  return context.colors.neutral.stroke;
27736
27790
  }
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(" · ");
27791
+ function formatTaskElapsed(ms) {
27792
+ const total = Math.max(0, Math.floor(ms / SECOND));
27793
+ const hours = Math.floor(total / 3600);
27794
+ const minutes = Math.floor(total % 3600 / 60);
27795
+ const seconds = String(total % 60).padStart(2, "0");
27796
+ return hours > 0 ? `${hours}:${String(minutes).padStart(2, "0")}:${seconds}` : `${minutes}:${seconds}`;
27741
27797
  }
27742
27798
  function containTaskPreviewRect(width, height, imageWidth, imageHeight) {
27743
27799
  if (width <= 0 || height <= 0 || imageWidth <= 0 || imageHeight <= 0) return {
@@ -27839,15 +27895,83 @@ function drawFailedBadge(graphics, width, color, context) {
27839
27895
  cap: "round"
27840
27896
  });
27841
27897
  }
27898
+ const shimmerGradients = /* @__PURE__ */ new Map();
27899
+ function shimmerGradient(color) {
27900
+ let gradient = shimmerGradients.get(color);
27901
+ if (!gradient) {
27902
+ const rgb = `${color >> 16 & 255}, ${color >> 8 & 255}, ${color & 255}`;
27903
+ gradient = new FillGradient({
27904
+ type: "linear",
27905
+ start: {
27906
+ x: 0,
27907
+ y: 0
27908
+ },
27909
+ end: {
27910
+ x: 1,
27911
+ y: 0
27912
+ },
27913
+ textureSpace: "local",
27914
+ colorStops: [
27915
+ {
27916
+ offset: 0,
27917
+ color: `rgba(${rgb}, 0)`
27918
+ },
27919
+ {
27920
+ offset: .5,
27921
+ color: `rgba(${rgb}, 0.07)`
27922
+ },
27923
+ {
27924
+ offset: 1,
27925
+ color: `rgba(${rgb}, 0)`
27926
+ }
27927
+ ]
27928
+ });
27929
+ shimmerGradients.set(color, gradient);
27930
+ }
27931
+ return gradient;
27932
+ }
27933
+ function drawShimmerBand(graphics, width, height, color) {
27934
+ const band = Math.max(48, width * .45);
27935
+ graphics.clear().rect(-band, 0, band, height).fill(shimmerGradient(color));
27936
+ }
27937
+ function advance(parts, clock) {
27938
+ const { motion } = parts;
27939
+ let next = null;
27940
+ if (motion.shimmer) {
27941
+ const travel = motion.width + parts.shimmer.width;
27942
+ parts.shimmer.x = clock % SHIMMER_PERIOD_MS / SHIMMER_PERIOD_MS * travel;
27943
+ next = SHIMMER_FRAME_MS;
27944
+ }
27945
+ if (motion.startedAt !== null) {
27946
+ const elapsedMs = Date.now() - motion.startedAt;
27947
+ const elapsed = formatTaskElapsed(elapsedMs);
27948
+ if (elapsed !== motion.elapsed) {
27949
+ motion.elapsed = elapsed;
27950
+ parts.body.text = `${motion.label} · ${elapsed}`;
27951
+ }
27952
+ const untilNextSecond = SECOND - Math.max(0, elapsedMs) % SECOND;
27953
+ next = next === null ? untilNextSecond : Math.min(next, untilNextSecond);
27954
+ }
27955
+ if (motion.revealFrom !== null) {
27956
+ const progress = Math.min(1, (clock - motion.revealFrom) / REVEAL_MS);
27957
+ parts.preview.alpha = progress * (2 - progress);
27958
+ if (progress >= 1) motion.revealFrom = null;
27959
+ else next = 0;
27960
+ }
27961
+ return next;
27962
+ }
27842
27963
  function sync$11(container, item, context) {
27843
27964
  const parts = partsByContainer$11.get(container);
27844
27965
  if (!parts) return;
27845
27966
  positionShell(parts.root, item);
27846
27967
  const { width, height } = item.frame;
27968
+ const { snapshot } = item.props;
27969
+ const { status, startedAt } = liveStatus(item, context);
27847
27970
  const selected = context.selectedIds.has(item.id);
27848
27971
  const hovered = context.hoveredId === item.id;
27849
27972
  const full = context.zoom >= TASK_CARD_FULL_DETAIL_ZOOM;
27850
- const color = statusColor(item, context);
27973
+ const working = status === "pending" || status === "running";
27974
+ const color = statusColor(status, context);
27851
27975
  const key = context.assetKey(item);
27852
27976
  if (key !== parts.assetKey) {
27853
27977
  if (parts.assetKey) context.releaseTexture(parts.assetKey);
@@ -27856,20 +27980,58 @@ function sync$11(container, item, context) {
27856
27980
  }
27857
27981
  const texture = key ? context.getTexture(key) : null;
27858
27982
  const previewFailed = Boolean(key && !texture && context.hasError(key));
27859
- const artifact = featuredTaskArtifact(item.props.snapshot.artifacts);
27983
+ const artifact = featuredTaskArtifact(snapshot.artifacts);
27860
27984
  const kind = previewKindFor(artifact);
27861
- const surface = stateSurfaceFor(item, kind, Boolean(texture), previewFailed);
27985
+ const surface = stateSurfaceFor(status, kind, Boolean(texture), previewFailed);
27862
27986
  const frame = {
27863
27987
  x: 1,
27864
27988
  y: 1,
27865
27989
  width: Math.max(1, width - 2),
27866
27990
  height: Math.max(1, height - 2)
27867
27991
  };
27868
- const metaText = artifact ? metadata(item) : "";
27992
+ const metaText = artifact || working ? snapshot.model ?? "" : "";
27869
27993
  const showMeta = full && Boolean(metaText);
27870
- const failedWithOutput = item.props.snapshot.status === "failed" && kind !== "empty";
27994
+ const failedWithOutput = status === "failed" && kind !== "empty";
27995
+ const count = artifact && snapshot.artifactCount > 1 ? String(snapshot.artifactCount) : "";
27996
+ const showBadge = full && Boolean(count);
27997
+ const label = stateLabel(surface, context);
27998
+ const counting = full && working && startedAt !== null && surface !== null;
27999
+ const elapsed = counting ? formatTaskElapsed(Date.now() - startedAt) : "";
28000
+ const detailText = !full || !surface ? "" : status === "failed" ? snapshot.errorExcerpt ?? "" : working ? snapshot.promptExcerpt ?? "" : "";
28001
+ const textArtifact = kind === "text" && artifact?.type === "text";
28002
+ const bodyText = textArtifact ? artifact.textExcerpt : counting ? `${label} · ${elapsed}` : label;
27871
28003
  syncTextResolution(parts.body, parts, context.zoom);
28004
+ syncTextResolution(parts.detail, parts, context.zoom);
27872
28005
  syncTextResolution(parts.meta, parts, context.zoom);
28006
+ syncTextResolution(parts.badge, parts, context.zoom);
28007
+ const textSig = [
28008
+ counting ? label : bodyText,
28009
+ detailText,
28010
+ metaText,
28011
+ count,
28012
+ kind,
28013
+ surface,
28014
+ full,
28015
+ width,
28016
+ height,
28017
+ color,
28018
+ context.palette.text,
28019
+ context.palette.muted
28020
+ ].join("|");
28021
+ if (textSig !== parts.textSig) {
28022
+ parts.textSig = textSig;
28023
+ const wrap = Math.max(24, width - 24);
28024
+ parts.body.style.fill = surface === "failed" ? color : context.palette.text;
28025
+ parts.body.anchor.set(textArtifact ? 0 : .5, 0);
28026
+ fitTextToLines(parts.body, bodyText, textArtifact ? Math.max(1, Math.floor((height - 24 - (showMeta ? META_HEIGHT : 0)) / 16)) : 1, wrap);
28027
+ parts.detail.style.fill = context.palette.muted;
28028
+ fitTextToLines(parts.detail, detailText, height - 24 > 120 ? 2 : 1, Math.min(wrap, 360));
28029
+ parts.meta.style.fill = context.palette.text;
28030
+ fitTextToLines(parts.meta, metaText, 1, wrap);
28031
+ parts.badge.style.fill = context.palette.text;
28032
+ if (parts.badge.text !== count) parts.badge.text = count;
28033
+ }
28034
+ const badgeWidth = Math.max(22, parts.badge.width + 12);
27873
28035
  const visualSig = [
27874
28036
  width,
27875
28037
  height,
@@ -27880,6 +28042,7 @@ function sync$11(container, item, context) {
27880
28042
  kind,
27881
28043
  surface,
27882
28044
  showMeta,
28045
+ showBadge && badgeWidth,
27883
28046
  failedWithOutput,
27884
28047
  artifact?.type ?? "none",
27885
28048
  artifact?.id ?? "none",
@@ -27888,7 +28051,8 @@ function sync$11(container, item, context) {
27888
28051
  context.palette.surface,
27889
28052
  context.palette.hover,
27890
28053
  context.palette.border,
27891
- context.palette.bg
28054
+ context.palette.bg,
28055
+ context.palette.text
27892
28056
  ].join("|");
27893
28057
  if (visualSig !== parts.visualSig) {
27894
28058
  parts.visualSig = visualSig;
@@ -27906,6 +28070,8 @@ function sync$11(container, item, context) {
27906
28070
  alpha: .5
27907
28071
  });
27908
28072
  parts.previewMask.clear().rect(frame.x, frame.y, frame.width, frame.height).fill({ color: 16777215 });
28073
+ drawShimmerBand(parts.shimmer, frame.width, frame.height, context.palette.text);
28074
+ parts.shimmer.y = frame.y;
27909
28075
  parts.previewArt.clear();
27910
28076
  if (kind === "waveform") {
27911
28077
  const bottomInset = showMeta ? META_HEIGHT : 0;
@@ -27918,47 +28084,52 @@ function sync$11(container, item, context) {
27918
28084
  }
27919
28085
  if (full && (artifact?.type === "audio" || artifact?.type === "video" && texture)) drawPlayBadge(parts.previewArt, frame, context);
27920
28086
  if (surface) {
27921
- const markY = frame.y + frame.height / 2 - (full && stateLabel(surface) ? 10 : 0);
28087
+ const markY = frame.y + frame.height / 2 - (full && label ? 18 : 0);
27922
28088
  drawStateMark(parts.previewArt, surface, frame.x + frame.width / 2, markY, surface === "failed" ? color : context.colors.neutral.stroke);
27923
28089
  }
27924
28090
  if (showMeta) parts.previewArt.rect(frame.x, frame.y + frame.height - META_HEIGHT, frame.width, META_HEIGHT).fill({
27925
28091
  color: context.palette.bg,
27926
28092
  alpha: .68
27927
28093
  });
28094
+ if (showBadge) parts.previewArt.roundRect(width - badgeWidth - 8, 8, badgeWidth, 20, 10).fill({
28095
+ color: context.palette.bg,
28096
+ alpha: .72
28097
+ });
27928
28098
  if (failedWithOutput) drawFailedBadge(parts.previewArt, width, color, context);
27929
28099
  }
27930
- parts.preview.visible = kind === "texture" && Boolean(texture);
27931
- if (parts.preview.visible && texture) {
28100
+ const showPreview = kind === "texture" && Boolean(texture);
28101
+ const revealKey = showPreview ? `${artifact?.id}:${texture?.uid}` : null;
28102
+ const sameCard = parts.motion.itemId === item.id;
28103
+ parts.motion.itemId = item.id;
28104
+ if (revealKey !== parts.motion.revealKey || !sameCard) {
28105
+ const appearing = sameCard && parts.motion.revealKey === null && revealKey !== null;
28106
+ parts.motion.revealKey = revealKey;
28107
+ parts.motion.revealFrom = appearing && !context.reducedMotion ? performance.now() : null;
28108
+ parts.preview.alpha = parts.motion.revealFrom === null ? 1 : 0;
28109
+ }
28110
+ parts.preview.visible = showPreview;
28111
+ if (showPreview && texture) {
27932
28112
  if (parts.preview.texture !== texture) parts.preview.texture = texture;
27933
28113
  const fitted = containTaskPreviewRect(frame.width, frame.height, texture.width, texture.height);
27934
28114
  parts.preview.position.set(frame.x + fitted.x, frame.y + fitted.y);
27935
28115
  parts.preview.width = fitted.width;
27936
28116
  parts.preview.height = fitted.height;
27937
28117
  }
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
28118
  parts.body.visible = full && Boolean(bodyText);
28119
+ parts.detail.visible = full && Boolean(detailText);
27958
28120
  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);
28121
+ parts.badge.visible = showBadge;
28122
+ if (textArtifact) parts.body.position.set(PADDING, PADDING);
28123
+ else parts.body.position.set(width / 2, height / 2 + 2);
28124
+ parts.detail.position.set(width / 2, height / 2 + 2 + parts.body.height + 4);
27961
28125
  parts.meta.position.set(PADDING, height - META_HEIGHT + 7);
28126
+ parts.badge.position.set(width - 8 - badgeWidth / 2, 18);
28127
+ parts.motion.width = frame.width;
28128
+ parts.motion.shimmer = full && working && kind !== "texture" && !context.reducedMotion;
28129
+ parts.shimmer.visible = parts.motion.shimmer;
28130
+ parts.motion.startedAt = counting ? startedAt : null;
28131
+ parts.motion.label = label;
28132
+ parts.motion.elapsed = elapsed;
27962
28133
  }
27963
28134
  const taskCardRenderer = {
27964
28135
  id: "task-card",
@@ -27970,35 +28141,32 @@ const taskCardRenderer = {
27970
28141
  const previewBg = new Graphics();
27971
28142
  const preview = new Sprite();
27972
28143
  const previewMask = new Graphics();
28144
+ const shimmer = new Graphics();
27973
28145
  const previewArt = new Graphics();
27974
28146
  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({
28147
+ const text = (fontFamily, fontSize, fontWeight) => new Text({
27988
28148
  text: "",
27989
28149
  style: {
27990
- fontFamily: BOARD_MONO_FONT_STACK,
27991
- fontSize: 10,
27992
- fontWeight: "500",
28150
+ fontFamily,
28151
+ fontSize,
28152
+ fontWeight,
27993
28153
  wordWrap: true,
27994
28154
  breakWords: true
27995
28155
  },
27996
28156
  resolution,
27997
28157
  roundPixels: true
27998
28158
  });
28159
+ const body = text(BOARD_FONT_STACK, 11.5, "500");
28160
+ const detail = text(BOARD_FONT_STACK, 10.5, "400");
28161
+ const meta = text(BOARD_MONO_FONT_STACK, 10, "500");
28162
+ const badge = text(BOARD_MONO_FONT_STACK, 10, "500");
28163
+ badge.style.wordWrap = false;
28164
+ badge.anchor.set(.5);
28165
+ detail.anchor.set(.5, 0);
28166
+ detail.style.align = "center";
27999
28167
  preview.mask = previewMask;
28000
28168
  root.mask = clip;
28001
- root.addChild(plate, clip, previewBg, preview, previewMask, previewArt, body, meta);
28169
+ root.addChild(plate, clip, previewBg, shimmer, preview, previewMask, previewArt, body, detail, meta, badge);
28002
28170
  partsByContainer$11.set(root, {
28003
28171
  root,
28004
28172
  plate,
@@ -28006,13 +28174,26 @@ const taskCardRenderer = {
28006
28174
  previewBg,
28007
28175
  preview,
28008
28176
  previewMask,
28177
+ shimmer,
28009
28178
  previewArt,
28179
+ badge,
28010
28180
  body,
28181
+ detail,
28011
28182
  meta,
28012
28183
  assetKey: null,
28013
28184
  visualSig: "",
28014
28185
  textSig: "",
28015
- resolution
28186
+ resolution,
28187
+ motion: {
28188
+ itemId: "",
28189
+ width: 0,
28190
+ shimmer: false,
28191
+ startedAt: null,
28192
+ label: "",
28193
+ elapsed: "",
28194
+ revealFrom: null,
28195
+ revealKey: null
28196
+ }
28016
28197
  });
28017
28198
  if (item.type === "task") sync$11(root, item, context);
28018
28199
  return root;
@@ -28020,14 +28201,19 @@ const taskCardRenderer = {
28020
28201
  update: (container, item, context) => {
28021
28202
  if (item.type === "task") sync$11(container, item, context);
28022
28203
  },
28204
+ tick: (container, clock) => {
28205
+ const parts = partsByContainer$11.get(container);
28206
+ return parts ? advance(parts, clock) : null;
28207
+ },
28023
28208
  renderFar: (graphics, item, context) => {
28024
28209
  if (item.type !== "task") return;
28025
- const active = item.props.snapshot.status === "failed" || item.props.snapshot.status === "running" || item.props.snapshot.status === "pending";
28210
+ const { status } = liveStatus(item, context);
28211
+ const active = status === "failed" || status === "running" || status === "pending";
28026
28212
  drawFarPlate(graphics, item.frame, {
28027
28213
  fill: context.palette.surface,
28028
28214
  fillAlpha: .82,
28029
28215
  ...active ? {
28030
- accent: statusColor(item, context),
28216
+ accent: statusColor(status, context),
28031
28217
  accentAlpha: .9
28032
28218
  } : {}
28033
28219
  });
@@ -29066,7 +29252,8 @@ function sync$4(container, item, context) {
29066
29252
  const texture = context.scripts?.frame(item, context.time, resolution) ?? null;
29067
29253
  const error = context.scripts?.error(item.id) ?? null;
29068
29254
  parts.sprite.visible = Boolean(texture);
29069
- if (texture && parts.sprite.texture !== texture) parts.sprite.texture = texture;
29255
+ const display = texture ?? Texture.EMPTY;
29256
+ if (parts.sprite.texture !== display) parts.sprite.texture = display;
29070
29257
  parts.sprite.width = width;
29071
29258
  parts.sprite.height = height;
29072
29259
  const selected = context.selectedIds.has(item.id);
@@ -29577,65 +29764,73 @@ function sync(container, item, context) {
29577
29764
  });
29578
29765
  parts.label.style.fill = texture ? context.palette.text : context.palette.muted;
29579
29766
  }
29767
+ const videoCardRenderer = {
29768
+ id: "video-card",
29769
+ canRender: (item) => item.type === "video",
29770
+ create: (item, context) => {
29771
+ const root = new Container();
29772
+ const plate = new Graphics();
29773
+ const sprite = new Sprite(Texture.EMPTY);
29774
+ const mask = new Graphics();
29775
+ const chrome = new Graphics();
29776
+ const resolution = textResolutionForZoom(context.zoom);
29777
+ const label = new Text({
29778
+ text: "",
29779
+ style: {
29780
+ fill: context.palette.muted,
29781
+ fontFamily: BOARD_FONT_STACK,
29782
+ fontSize: 12,
29783
+ fontWeight: "500",
29784
+ wordWrap: true
29785
+ },
29786
+ resolution,
29787
+ roundPixels: true
29788
+ });
29789
+ root.addChild(plate, sprite, mask, chrome, label);
29790
+ sprite.mask = mask;
29791
+ partsByContainer.set(root, {
29792
+ root,
29793
+ plate,
29794
+ sprite,
29795
+ mask,
29796
+ chrome,
29797
+ label,
29798
+ sig: "",
29799
+ wrapWidth: 0,
29800
+ resolution
29801
+ });
29802
+ if (item.type === "video") sync(root, item, context);
29803
+ return root;
29804
+ },
29805
+ update: (container, item, context) => {
29806
+ if (item.type === "video") sync(container, item, context);
29807
+ },
29808
+ renderFar: (graphics, item, context) => {
29809
+ drawFarPlate(graphics, item.frame, {
29810
+ fill: context.palette.surface,
29811
+ fillAlpha: .96,
29812
+ accent: context.palette.brand,
29813
+ accentAlpha: .75
29814
+ });
29815
+ },
29816
+ destroy: (container) => {
29817
+ partsByContainer.get(container)?.root.destroy({ children: true });
29818
+ partsByContainer.delete(container);
29819
+ }
29820
+ };
29580
29821
  //#endregion
29581
29822
  //#region ../sdk/src/board/render/renderers/board-renderer-registry.ts
29823
+ const DEFAULT_BOARD_RENDER_LABELS = {
29824
+ queued: "Queued",
29825
+ generating: "Generating",
29826
+ failed: "Failed",
29827
+ previewUnavailable: "Preview unavailable",
29828
+ noPreview: "No preview"
29829
+ };
29582
29830
  const builtinCardRenderers = [
29583
29831
  textCardRenderer,
29584
29832
  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
- },
29833
+ videoCardRenderer,
29639
29834
  audioCardRenderer,
29640
29835
  fileCardRenderer,
29641
29836
  taskCardRenderer,
@@ -29960,8 +30155,16 @@ const fileProps = {
29960
30155
  coverUrl: string().optional()
29961
30156
  }).strict().optional()
29962
30157
  };
29963
- const timedFileProps = {
30158
+ const BoardMediaOriginSchema = object({
30159
+ taskRunId: string().min(1).max(160),
30160
+ artifactId: string().min(1).max(240)
30161
+ }).strict();
30162
+ const mediaProps = {
29964
30163
  ...fileProps,
30164
+ origin: BoardMediaOriginSchema.optional()
30165
+ };
30166
+ const timedFileProps = {
30167
+ ...mediaProps,
29965
30168
  time: nonNegative.optional()
29966
30169
  };
29967
30170
  const BoardCropSchema = object({
@@ -29974,7 +30177,7 @@ object({
29974
30177
  ...boxed(640, 360),
29975
30178
  type: literal("image"),
29976
30179
  props: object({
29977
- ...fileProps,
30180
+ ...mediaProps,
29978
30181
  crop: BoardCropSchema.optional()
29979
30182
  }).strict()
29980
30183
  }).strict();
@@ -30039,8 +30242,10 @@ const BoardTaskSnapshotSchema = object({
30039
30242
  title: string().min(1).max(240),
30040
30243
  model: string().max(160).optional(),
30041
30244
  promptExcerpt: string().max(480).optional(),
30245
+ errorExcerpt: string().max(240).optional(),
30042
30246
  artifactCount: number$1().int().nonnegative(),
30043
30247
  artifacts: array(BoardTaskArtifactSchema).max(6).default([]),
30248
+ startedAt: string().max(64).optional(),
30044
30249
  updatedAt: string().optional()
30045
30250
  }).strict();
30046
30251
  object({
@@ -30048,7 +30253,7 @@ object({
30048
30253
  type: literal("task"),
30049
30254
  props: object({
30050
30255
  taskRunId: string().min(1).max(160),
30051
- snapshot: BoardTaskSnapshotSchema
30256
+ snapshot: BoardTaskSnapshotSchema.describe("Maintained by the server for tasks in the Board's Space; writers may omit it.")
30052
30257
  }).strict()
30053
30258
  }).strict();
30054
30259
  const BOARD_EFFECT_KINDS = [
@@ -30337,6 +30542,8 @@ function createScriptRuntime(module, initial, host) {
30337
30542
  let patches = effects?.patches ?? {};
30338
30543
  let items = effects?.items ?? {};
30339
30544
  let space = effects?.space ?? "world";
30545
+ let anchor = effects?.anchor;
30546
+ let view = null;
30340
30547
  let hits = effects?.hits ?? {};
30341
30548
  const invalidate = () => {
30342
30549
  if (disposed) return;
@@ -30373,6 +30580,9 @@ function createScriptRuntime(module, initial, host) {
30373
30580
  get frame() {
30374
30581
  return frame;
30375
30582
  },
30583
+ get view() {
30584
+ return view;
30585
+ },
30376
30586
  random() {
30377
30587
  randomState = Math.imul(randomState, 1664525) + 1013904223 >>> 0;
30378
30588
  return randomState / 4294967296;
@@ -30383,10 +30593,11 @@ function createScriptRuntime(module, initial, host) {
30383
30593
  listeners = /* @__PURE__ */ new Set();
30384
30594
  handlers.set(type, listeners);
30385
30595
  }
30386
- listeners.add(handler);
30596
+ const listener = (event) => handler(event);
30597
+ listeners.add(listener);
30387
30598
  invalidate();
30388
30599
  return () => {
30389
- listeners.delete(handler);
30600
+ listeners.delete(listener);
30390
30601
  invalidate();
30391
30602
  };
30392
30603
  },
@@ -30405,6 +30616,7 @@ function createScriptRuntime(module, initial, host) {
30405
30616
  scene(next, options) {
30406
30617
  items = structuredClone(next);
30407
30618
  space = options?.space ?? "world";
30619
+ anchor = options?.anchor ? structuredClone(options.anchor) : void 0;
30408
30620
  invalidate();
30409
30621
  },
30410
30622
  hitRegions(next) {
@@ -30466,6 +30678,7 @@ function createScriptRuntime(module, initial, host) {
30466
30678
  },
30467
30679
  event(event) {
30468
30680
  if (disposed) return;
30681
+ if (event.type === "viewchange") view = event.data;
30469
30682
  const listeners = handlers.get(event.type);
30470
30683
  if (!listeners?.size) return;
30471
30684
  for (const handler of listeners) handler(event);
@@ -30479,6 +30692,7 @@ function createScriptRuntime(module, initial, host) {
30479
30692
  return {
30480
30693
  parameters: module.parameters,
30481
30694
  space,
30695
+ anchor,
30482
30696
  patches,
30483
30697
  items,
30484
30698
  hits,
@@ -30497,7 +30711,8 @@ function createScriptRuntime(module, initial, host) {
30497
30711
  patches,
30498
30712
  items,
30499
30713
  hits,
30500
- space
30714
+ space,
30715
+ anchor
30501
30716
  } : void 0
30502
30717
  };
30503
30718
  return clone ? structuredClone(snapshot) : snapshot;
@@ -30535,6 +30750,9 @@ function buildContext(input) {
30535
30750
  colorScheme: input.colorScheme,
30536
30751
  rendererType: "canvas",
30537
30752
  zoom: input.scale,
30753
+ reducedMotion: true,
30754
+ labels: DEFAULT_BOARD_RENDER_LABELS,
30755
+ taskRun: () => null,
30538
30756
  scripts: input.scripts,
30539
30757
  assetKey: input.assetKey ?? imageAssetKey,
30540
30758
  getTexture: (key) => textures.get(key) ?? null,