@neta-art/cohub 12.0.0 → 12.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/board/export/index.js +1 -1
  2. package/dist/board/export/node/index.d.ts +1 -0
  3. package/dist/board/export/node/index.js +5 -0
  4. package/dist/board/export/scene.js +2 -1
  5. package/dist/board/render/card-tag.d.ts +0 -1
  6. package/dist/board/render/card-tag.js +15 -17
  7. package/dist/board/render/index.d.ts +2 -2
  8. package/dist/board/render/index.js +6 -6
  9. package/dist/board/render/renderers/arrow-card-renderer.js +5 -7
  10. package/dist/board/render/renderers/audio-card-renderer.js +8 -7
  11. package/dist/board/render/renderers/base-card-renderer.d.ts +1 -1
  12. package/dist/board/render/renderers/base-card-renderer.js +2 -3
  13. package/dist/board/render/renderers/board-renderer-registry.d.ts +3 -1
  14. package/dist/board/render/renderers/custom-card-renderer.js +19 -17
  15. package/dist/board/render/renderers/file-card-renderer.js +10 -8
  16. package/dist/board/render/renderers/frame-card-renderer.js +5 -6
  17. package/dist/board/render/renderers/shape-card-renderer.js +5 -7
  18. package/dist/board/render/renderers/task-card-renderer.js +9 -9
  19. package/dist/board/render/renderers/text-card-renderer.js +5 -7
  20. package/dist/board/render/renderers/unknown-card-renderer.js +5 -7
  21. package/dist/board/render/text-resolution.d.ts +4 -7
  22. package/dist/board/render/text-resolution.js +20 -22
  23. package/dist/board/stage/canvas-size.d.ts +18 -0
  24. package/dist/board/stage/canvas-size.js +31 -0
  25. package/dist/board/stage/scene.d.ts +5 -1
  26. package/dist/board/stage/scene.js +35 -9
  27. package/dist/board/stage/stage.js +34 -13
  28. package/dist/board/stage/text-resolution-tracker.d.ts +8 -0
  29. package/dist/board/stage/text-resolution-tracker.js +33 -0
  30. package/dist/chunks/app-realtime.js +1 -1
  31. package/dist/chunks/{transport.js → http-error.js} +37 -1
  32. package/dist/chunks/http.js +30 -10
  33. package/dist/http.js +1 -1
  34. package/dist/index.js +2 -2
  35. package/dist/websocket.js +951 -1
  36. package/package.json +1 -1
  37. package/dist/chunks/websocket.js +0 -988
@@ -2,9 +2,9 @@ import { buildBoardScene } from "../model/scene.js";
2
2
  import { resolveBoardAnimationCamera } from "../model/sampling.js";
3
3
  import { normalizeBoardDocument, planBoardExport } from "../model/export-plan.js";
4
4
  import { parseBoardCssColor } from "../render/css-color.js";
5
- import { ensureBoardTextMeasurement } from "../render/text-measurement.js";
6
5
  import { defaultBoardPalette } from "../render/palette.js";
7
6
  import { createBoardRenderSampler } from "../render/sampling.js";
7
+ import { ensureBoardTextMeasurement } from "../render/text-measurement.js";
8
8
  import { createBoardExportScene } from "./scene.js";
9
9
  import { Rectangle, RendererType } from "pixi.js";
10
10
  //#region src/board/export/index.ts
@@ -9,6 +9,7 @@ import { Renderer } from "pixi.js";
9
9
  type NodeCanvasModule = {
10
10
  createCanvas: (width: number, height: number) => unknown;
11
11
  Image: new () => unknown;
12
+ DOMMatrix: new (init?: number[]) => unknown;
12
13
  GlobalFonts: BoardFontRegistry;
13
14
  };
14
15
  type BoardNodeRendererOptions = {
@@ -24,6 +24,10 @@ async function loadCanvasModule() {
24
24
  throw new Error("Board image export needs the optional '@napi-rs/canvas' package. Install it with: npm i @napi-rs/canvas", { cause });
25
25
  }
26
26
  }
27
+ function installDOMMatrix(canvasModule) {
28
+ const globals = globalThis;
29
+ globals.DOMMatrix ??= canvasModule.DOMMatrix;
30
+ }
27
31
  function installAnimationFrameShim() {
28
32
  const globals = globalThis;
29
33
  if (globals.requestAnimationFrame) return () => {};
@@ -78,6 +82,7 @@ async function createNodeBoardRenderer(options = {}) {
78
82
  throw new Error("XML parsing is not available in Node board export.");
79
83
  }
80
84
  });
85
+ installDOMMatrix(canvasModule);
81
86
  installBoardTextMeasurement();
82
87
  const restoreAnimationFrame = installAnimationFrameShim();
83
88
  const renderer = new CanvasRenderer();
@@ -1,7 +1,7 @@
1
1
  import { buildFallbackShapeColors } from "../model/palette.js";
2
2
  import { imageAssetKey } from "../model/image-key.js";
3
- import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
4
3
  import { defaultBoardPalette } from "../render/palette.js";
4
+ import { clippingAncestor, isClippingFrame, syncClipGroup } from "../render/clip.js";
5
5
  import { DEFAULT_BOARD_RENDER_LABELS, getBoardCardRenderer } from "../render/renderers/board-renderer-registry.js";
6
6
  import { resolveBoardRenderStyle } from "../render/style.js";
7
7
  import { Container, Graphics, Sprite, TilingSprite } from "pixi.js";
@@ -29,6 +29,7 @@ function buildContext(input) {
29
29
  themeKey: "export",
30
30
  rendererType: "canvas",
31
31
  zoom: input.scale,
32
+ textResolution: input.scale,
32
33
  reducedMotion: true,
33
34
  labels: DEFAULT_BOARD_RENDER_LABELS,
34
35
  taskRun: () => null,
@@ -11,7 +11,6 @@ type CardTag = {
11
11
  icon: Graphics;
12
12
  text: Text | null;
13
13
  sig: string;
14
- resolution: number;
15
14
  width: number;
16
15
  };
17
16
  declare function createCardTag(): CardTag;
@@ -1,5 +1,5 @@
1
1
  import { fitTextToWidth } from "./text-fit.js";
2
- import { syncTextResolution, textResolutionForZoom } from "./text-resolution.js";
2
+ import { syncTextResolution } from "./text-resolution.js";
3
3
  import { Container, Graphics, Text } from "pixi.js";
4
4
  //#region src/board/render/card-tag.ts
5
5
  const CARD_TAG_SIZE = 11;
@@ -177,13 +177,15 @@ function createCardTag() {
177
177
  icon,
178
178
  text: null,
179
179
  sig: "",
180
- resolution: 0,
181
180
  width: 0
182
181
  };
183
182
  }
183
+ function tagScale(zoom) {
184
+ return Math.min(MAX_GROWTH, Math.max(1, 1 / Math.max(zoom, .01)));
185
+ }
184
186
  function zoomCardTag(tag, zoom) {
185
187
  if (!tag.text || !tag.sig) return;
186
- const scale = Math.min(MAX_GROWTH, Math.max(1, 1 / Math.max(zoom, .01)));
188
+ const scale = tagScale(zoom);
187
189
  const span = 17 + tag.text.width;
188
190
  tag.root.scale.set(scale);
189
191
  tag.root.position.set(0, -18 * scale);
@@ -197,7 +199,6 @@ function syncCardTag(tag, input, context) {
197
199
  return;
198
200
  }
199
201
  if (!tag.text) {
200
- tag.resolution = textResolutionForZoom(context.zoom);
201
202
  tag.text = new Text({
202
203
  text: "",
203
204
  style: {
@@ -205,30 +206,27 @@ function syncCardTag(tag, input, context) {
205
206
  fontSize: 11,
206
207
  fontWeight: "500"
207
208
  },
208
- resolution: tag.resolution,
209
209
  roundPixels: true
210
210
  });
211
211
  tag.root.addChild(tag.text);
212
212
  }
213
- syncTextResolution(tag.text, tag, context.zoom);
214
213
  const sig = [
215
214
  input.glyph,
216
215
  input.text,
217
216
  input.width,
218
217
  context.themeKey
219
218
  ].join("|");
220
- if (sig === tag.sig) {
221
- zoomCardTag(tag, context.zoom);
222
- return;
219
+ if (sig !== tag.sig) {
220
+ tag.sig = sig;
221
+ const color = context.palette.muted;
222
+ const pixel = context.style.icons === "pixel";
223
+ drawCardGlyph(tag.icon.clear(), input.glyph, color, pixel);
224
+ tag.text.style.fill = color;
225
+ tag.text.style.fontFamily = context.style.font;
226
+ fitTextToWidth(tag.text, input.text, Math.max(1, input.width - ICON - GAP));
227
+ tag.text.position.set(17, (ICON - tag.text.height) / 2);
223
228
  }
224
- tag.sig = sig;
225
- const color = context.palette.muted;
226
- const pixel = context.style.icons === "pixel";
227
- drawCardGlyph(tag.icon.clear(), input.glyph, color, pixel);
228
- tag.text.style.fill = color;
229
- tag.text.style.fontFamily = context.style.font;
230
- fitTextToWidth(tag.text, input.text, Math.max(1, input.width - ICON - GAP));
231
- tag.text.position.set(17, (ICON - tag.text.height) / 2);
229
+ syncTextResolution(tag.text, context.textResolution * tagScale(context.zoom));
232
230
  zoomCardTag(tag, context.zoom);
233
231
  }
234
232
  //#endregion
@@ -15,6 +15,6 @@ import { CardCrop, CardElevation, CardRect, CardSurface, PIXEL_STEP, cardElevati
15
15
  import { BOARD_EFFECT_MAX_PARTICLES } from "./renderers/effect-card-renderer.js";
16
16
  import { ellipsizeWrappedLines, fitLineWithEllipsis, fitTextToLines, fitTextToWidth } from "./text-fit.js";
17
17
  import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
18
- import { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket } from "./text-resolution.js";
18
+ import { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket } from "./text-resolution.js";
19
19
  import { VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, loadVideoThumbnailTexture, videoTextureNaturalSize, videoThumbnailSize } from "./video-thumbnail.js";
20
- export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, BoardBackgroundContext, BoardCardRenderer, BoardCardRendererResolver, BoardCssPaint, BoardMediaAction, BoardRenderContext, BoardRenderDiagnostic, BoardRenderLabels, BoardRenderPalette, BoardRenderStyle, BoardScriptHost, BoardScriptHostOptions, BoardSelectionStyle, BoardShadowStyle, BrowserBoardScriptHost, CARD_TAG_SIZE, CardCrop, CardElevation, CardGlyph, CardRect, CardSurface, CardTag, ClipGroup, DEFAULT_BOARD_RENDER_LABELS, PIXEL_STEP, VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, boardCardDetailed, boardMediaActionAt, cardElevation, cardOutlinePoints, cardShadow, clippingAncestor, containRect, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, createCardTag, dashPattern, defaultBoardPalette, defaultBoardRenderStyle, drawCardGlyph, drawCardShadow, drawCardSurface, ellipsizeWrappedLines, endAngle, ensureBoardTextMeasurement, fillCardPicture, fitLineWithEllipsis, fitTextToLines, fitTextToWidth, formatBoardDuration, getBoardCardRenderer, getBoardResolution, inspectBoardItems, inspectBoardScene, installBoardTextMeasurement, isBoardCard, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, resolveBoardRenderStyle, roundRectPoints, syncCardTag, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceCardOutline, traceCube, traceFrameOutline, tracePixelRect, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize, zoomCardTag };
20
+ export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, BoardBackgroundContext, BoardCardRenderer, BoardCardRendererResolver, BoardCssPaint, BoardMediaAction, BoardRenderContext, BoardRenderDiagnostic, BoardRenderLabels, BoardRenderPalette, BoardRenderStyle, BoardScriptHost, BoardScriptHostOptions, BoardSelectionStyle, BoardShadowStyle, BrowserBoardScriptHost, CARD_TAG_SIZE, CardCrop, CardElevation, CardGlyph, CardRect, CardSurface, CardTag, ClipGroup, DEFAULT_BOARD_RENDER_LABELS, PIXEL_STEP, VIDEO_THUMBNAIL_MAX_EDGE, VideoNaturalSize, boardCardDetailed, boardMediaActionAt, boardTextResolution, cardElevation, cardOutlinePoints, cardShadow, clippingAncestor, containRect, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, createCardTag, dashPattern, defaultBoardPalette, defaultBoardRenderStyle, drawCardGlyph, drawCardShadow, drawCardSurface, ellipsizeWrappedLines, endAngle, ensureBoardTextMeasurement, fillCardPicture, fitLineWithEllipsis, fitTextToLines, fitTextToWidth, formatBoardDuration, getBoardCardRenderer, getBoardResolution, inspectBoardItems, inspectBoardScene, installBoardTextMeasurement, isBoardCard, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, resolveBoardRenderStyle, roundRectPoints, syncCardTag, syncClipGroup, syncTextResolution, syncTextWrapWidth, textZoomBucket, traceArrowhead, traceCardOutline, traceCube, traceFrameOutline, tracePixelRect, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize, zoomCardTag };
@@ -1,13 +1,13 @@
1
1
  import { parseBoardCssColor, parseBoardCssPaint } from "./css-color.js";
2
+ import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
3
+ import { createBoardRenderSampler } from "./sampling.js";
4
+ import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
5
+ import { inspectBoardItems, inspectBoardScene, measureBoardText } from "./diagnostics.js";
2
6
  import { createBoardBackground, updateBoardBackground } from "./board-background.js";
3
7
  import { ellipsizeWrappedLines, fitLineWithEllipsis, fitTextToLines, fitTextToWidth } from "./text-fit.js";
4
- import { boardCardDetailed, getBoardResolution, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket } from "./text-resolution.js";
8
+ import { boardCardDetailed, boardTextResolution, getBoardResolution, syncTextResolution, syncTextWrapWidth, textZoomBucket } from "./text-resolution.js";
5
9
  import { CARD_TAG_SIZE, createCardTag, drawCardGlyph, formatBoardDuration, syncCardTag, traceCube, zoomCardTag } from "./card-tag.js";
6
10
  import { clippingAncestor, isClippingFrame, syncClipGroup, traceFrameOutline } from "./clip.js";
7
- import { BOARD_FONT_STACKS, ensureBoardTextMeasurement, installBoardTextMeasurement } from "./text-measurement.js";
8
- import { inspectBoardItems, inspectBoardScene, measureBoardText } from "./diagnostics.js";
9
- import { defaultBoardPalette, itemAlpha, itemColor } from "./palette.js";
10
- import { createBoardRenderSampler } from "./sampling.js";
11
11
  import { dashPattern, endAngle, roundRectPoints, traceArrowhead, tracePolyline, trimPolyline } from "./stroke.js";
12
12
  import { PIXEL_STEP, cardElevation, cardOutlinePoints, cardShadow, containRect, drawCardShadow, drawCardSurface, fillCardPicture, isBoardCard, traceCardOutline, tracePixelRect } from "./surface.js";
13
13
  import { boardMediaActionAt, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter } from "./media-interaction.js";
@@ -17,4 +17,4 @@ import { createBoardScriptHost } from "./script-host.js";
17
17
  import { prepareBrowserBoardScripts } from "./script-export.js";
18
18
  import { defaultBoardRenderStyle, resolveBoardRenderStyle } from "./style.js";
19
19
  import { VIDEO_THUMBNAIL_MAX_EDGE, loadVideoThumbnailTexture, videoTextureNaturalSize, videoThumbnailSize } from "./video-thumbnail.js";
20
- export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, CARD_TAG_SIZE, DEFAULT_BOARD_RENDER_LABELS, PIXEL_STEP, VIDEO_THUMBNAIL_MAX_EDGE, boardCardDetailed, boardMediaActionAt, cardElevation, cardOutlinePoints, cardShadow, clippingAncestor, containRect, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, createCardTag, dashPattern, defaultBoardPalette, defaultBoardRenderStyle, drawCardGlyph, drawCardShadow, drawCardSurface, ellipsizeWrappedLines, endAngle, ensureBoardTextMeasurement, fillCardPicture, fitLineWithEllipsis, fitTextToLines, fitTextToWidth, formatBoardDuration, getBoardCardRenderer, getBoardResolution, inspectBoardItems, inspectBoardScene, installBoardTextMeasurement, isBoardCard, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, resolveBoardRenderStyle, roundRectPoints, syncCardTag, syncClipGroup, syncTextResolution, syncTextWrapWidth, textResolutionForZoom, textZoomBucket, traceArrowhead, traceCardOutline, traceCube, traceFrameOutline, tracePixelRect, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize, zoomCardTag };
20
+ export { BOARD_EFFECT_MAX_PARTICLES, BOARD_FONT_STACKS, CARD_TAG_SIZE, DEFAULT_BOARD_RENDER_LABELS, PIXEL_STEP, VIDEO_THUMBNAIL_MAX_EDGE, boardCardDetailed, boardMediaActionAt, boardTextResolution, cardElevation, cardOutlinePoints, cardShadow, clippingAncestor, containRect, createBoardBackground, createBoardCardRendererResolver, createBoardRenderSampler, createBoardScriptHost, createCardTag, dashPattern, defaultBoardPalette, defaultBoardRenderStyle, drawCardGlyph, drawCardShadow, drawCardSurface, ellipsizeWrappedLines, endAngle, ensureBoardTextMeasurement, fillCardPicture, fitLineWithEllipsis, fitTextToLines, fitTextToWidth, formatBoardDuration, getBoardCardRenderer, getBoardResolution, inspectBoardItems, inspectBoardScene, installBoardTextMeasurement, isBoardCard, isClippingFrame, itemAlpha, itemColor, loadVideoThumbnailTexture, measureBoardText, mediaPlayBadgeHit, mediaPlayBadgeVisible, mediaPlayKeyCenter, parseBoardCssColor, parseBoardCssPaint, prepareBrowserBoardScripts, resolveBoardRenderStyle, roundRectPoints, syncCardTag, syncClipGroup, syncTextResolution, syncTextWrapWidth, textZoomBucket, traceArrowhead, traceCardOutline, traceCube, traceFrameOutline, tracePixelRect, tracePolyline, trimPolyline, updateBoardBackground, videoTextureNaturalSize, videoThumbnailSize, zoomCardTag };
@@ -1,7 +1,7 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { pathPointAt, resolveSceneArrow } from "../../model/arrow-geometry.js";
3
- import { syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
4
3
  import { itemAlpha, itemColor } from "../palette.js";
4
+ import { syncTextResolution } from "../text-resolution.js";
5
5
  import { dashPattern, endAngle, traceArrowhead, tracePolyline, trimPolyline } from "../stroke.js";
6
6
  import { drawCardSurface } from "../surface.js";
7
7
  import { drawFarStroke } from "./far-plate.js";
@@ -18,7 +18,6 @@ function sync(container, item, context) {
18
18
  parts.line.alpha = itemAlpha(context, style.stroke);
19
19
  const color = itemColor(context, style.stroke, "brand");
20
20
  const size = style.strokeWidth ?? 2.5;
21
- syncTextResolution(parts.label, parts, context.zoom);
22
21
  const resolved = resolveSceneArrow(item, context.scene);
23
22
  const path = trimPolyline(resolved.path, style.trim);
24
23
  const lineSig = [
@@ -85,6 +84,7 @@ function sync(container, item, context) {
85
84
  border: context.palette.border
86
85
  });
87
86
  }
87
+ syncTextResolution(parts.label, context.textResolution);
88
88
  const visible = props.label.length > 0 && (style.trim === void 0 || style.trim >= .5);
89
89
  parts.label.visible = visible;
90
90
  parts.backdrop.visible = visible;
@@ -98,7 +98,6 @@ const arrowCardRenderer = {
98
98
  create: (item, context) => {
99
99
  const root = new Container();
100
100
  const line = new Graphics();
101
- const resolution = textResolutionForZoom(context.zoom);
102
101
  const label = new Text({
103
102
  text: "",
104
103
  style: {
@@ -107,7 +106,7 @@ const arrowCardRenderer = {
107
106
  fontSize: 14,
108
107
  fontWeight: "500"
109
108
  },
110
- resolution,
109
+ resolution: context.textResolution,
111
110
  roundPixels: true
112
111
  });
113
112
  label.anchor.set(.5);
@@ -119,8 +118,7 @@ const arrowCardRenderer = {
119
118
  backdrop,
120
119
  label,
121
120
  lineSig: "",
122
- labelSig: "",
123
- resolution
121
+ labelSig: ""
124
122
  });
125
123
  if (item.type === "arrow") sync(root, item, context);
126
124
  return root;
@@ -130,7 +128,7 @@ const arrowCardRenderer = {
130
128
  },
131
129
  resolution: (container, _item, context) => {
132
130
  const parts = partsByContainer.get(container);
133
- if (parts) syncTextResolution(parts.label, parts, context.zoom);
131
+ if (parts) syncTextResolution(parts.label, context.textResolution);
134
132
  },
135
133
  renderFar: (graphics, item, context) => {
136
134
  if (item.type !== "arrow") return;
@@ -1,8 +1,8 @@
1
1
  import { getMediaResourceTitle } from "../../model/media.js";
2
- import { drawAudioWaveform } from "../audio-waveform.js";
3
2
  import { fitTextToWidth } from "../text-fit.js";
4
- import { boardCardDetailed, syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
3
+ import { boardCardDetailed, syncTextResolution } from "../text-resolution.js";
5
4
  import { formatBoardDuration } from "../card-tag.js";
5
+ import { drawAudioWaveform } from "../audio-waveform.js";
6
6
  import { cardElevation, drawCardSurface } from "../surface.js";
7
7
  import { positionShell } from "./base-card-renderer.js";
8
8
  import { drawFarPlate } from "./far-plate.js";
@@ -25,12 +25,15 @@ function audioKeyCenter(width, height) {
25
25
  function sync(container, item, context) {
26
26
  const parts = partsByContainer.get(container);
27
27
  if (!parts) return;
28
+ syncContent(parts, item, context);
29
+ syncTextResolution(parts.title, context.textResolution);
30
+ }
31
+ function syncContent(parts, item, context) {
28
32
  positionShell(parts.root, item);
29
33
  const { width, height } = item.frame;
30
34
  const title = item.props.snapshot?.title ?? getMediaResourceTitle(item.props.src);
31
35
  const durationMs = item.props.snapshot?.durationMs;
32
36
  const detailed = boardCardDetailed(context.zoom);
33
- syncTextResolution(parts.title, parts, context.zoom);
34
37
  const elevation = cardElevation(context, item.id);
35
38
  const sig = [
36
39
  width,
@@ -83,14 +86,13 @@ const audioCardRenderer = {
83
86
  const root = new Container();
84
87
  const plate = new Graphics();
85
88
  const wave = new Graphics();
86
- const resolution = textResolutionForZoom(context.zoom);
87
89
  const title = new Text({
88
90
  text: "",
89
91
  style: {
90
92
  fontSize: 13,
91
93
  fontWeight: "600"
92
94
  },
93
- resolution,
95
+ resolution: context.textResolution,
94
96
  roundPixels: true
95
97
  });
96
98
  root.addChild(plate, wave, title);
@@ -99,8 +101,7 @@ const audioCardRenderer = {
99
101
  plate,
100
102
  wave,
101
103
  title,
102
- sig: "",
103
- resolution
104
+ sig: ""
104
105
  });
105
106
  if (item.type === "audio") sync(root, item, context);
106
107
  return root;
@@ -2,7 +2,7 @@ import { BoardFrame } from "../../../protocol/dist/board-layout.js";
2
2
  import "../../../protocol/dist/index.js";
3
3
  import { Container, Text } from "pixi.js";
4
4
  //#region src/board/render/renderers/base-card-renderer.d.ts
5
- declare function createLabel(text: string, style: Record<string, unknown>): Text;
5
+ declare function createLabel(text: string, style: Record<string, unknown>, resolution: number): Text;
6
6
  declare function positionShell(root: Container, item: {
7
7
  frame: BoardFrame;
8
8
  }): void;
@@ -1,10 +1,9 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
- import { getBoardResolution } from "../text-resolution.js";
3
2
  import { Text } from "pixi.js";
4
3
  //#region src/board/render/renderers/base-card-renderer.ts
5
- function createLabel(text, style) {
4
+ function createLabel(text, style, resolution) {
6
5
  return new Text({
7
- resolution: getBoardResolution(),
6
+ resolution,
8
7
  roundPixels: true,
9
8
  text,
10
9
  style: {
@@ -48,6 +48,8 @@ type BoardRenderContext = {
48
48
  themeKey: string;
49
49
  rendererType: "gpu" | "canvas";
50
50
  zoom: number;
51
+ /** Texture pixels per world unit for text; see `boardTextResolution`. */
52
+ textResolution: number;
51
53
  time: number;
52
54
  reducedMotion: boolean;
53
55
  labels: BoardRenderLabels;
@@ -73,7 +75,7 @@ type BoardCardRenderer = {
73
75
  tick?: (container: Container, clock: number) => number | null;
74
76
  create: (item: BoardSceneItem, context: BoardRenderContext) => Container;
75
77
  update: (container: Container, item: BoardSceneItem, context: BoardRenderContext) => void;
76
- /** Called only when the zoom bucket or display density changes. */
78
+ /** Called when the zoom bucket or text resolution changes. */
77
79
  resolution?: (container: Container, item: BoardSceneItem, context: BoardRenderContext) => void;
78
80
  /** Runs on every zoom step; only update transforms and visibility. */
79
81
  zoom?: (container: Container, zoom: number) => void;
@@ -1,5 +1,5 @@
1
1
  import { BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
- import { getBoardResolution, syncTextResolution } from "../text-resolution.js";
2
+ import { syncTextResolution } from "../text-resolution.js";
3
3
  import { positionShell } from "./base-card-renderer.js";
4
4
  import { drawFarPlate } from "./far-plate.js";
5
5
  import { Container, Graphics, Sprite, Text, Texture } from "pixi.js";
@@ -13,7 +13,6 @@ function sync(container, item, context) {
13
13
  parts.id = item.id;
14
14
  }
15
15
  positionShell(parts.root, item);
16
- syncTextResolution(parts.label, parts.label, context.zoom);
17
16
  const { width, height } = item.frame;
18
17
  const texture = context.scripts?.frame(item, context.time) ?? null;
19
18
  const error = context.scripts?.error(item.id) ?? null;
@@ -31,19 +30,21 @@ function sync(container, item, context) {
31
30
  message,
32
31
  context.themeKey
33
32
  ].join("|");
34
- if (sig === parts.sig) return;
35
- parts.sig = sig;
36
- parts.placeholder.clear();
37
- if (!ready || error) parts.placeholder.rect(0, 0, width, height).stroke({
38
- color: context.palette.border,
39
- width: 1,
40
- alpha: .8
41
- });
42
- parts.label.visible = !ready || Boolean(error);
43
- parts.label.text = message;
44
- parts.label.style.fill = error ? context.colors.rose.stroke : context.palette.muted;
45
- parts.label.style.wordWrapWidth = Math.max(40, width - 16);
46
- parts.label.position.set(width / 2, height / 2);
33
+ if (sig !== parts.sig) {
34
+ parts.sig = sig;
35
+ parts.placeholder.clear();
36
+ if (!ready || error) parts.placeholder.rect(0, 0, width, height).stroke({
37
+ color: context.palette.border,
38
+ width: 1,
39
+ alpha: .8
40
+ });
41
+ parts.label.visible = !ready || Boolean(error);
42
+ parts.label.text = message;
43
+ parts.label.style.fill = error ? context.colors.rose.stroke : context.palette.muted;
44
+ parts.label.style.wordWrapWidth = Math.max(40, width - 16);
45
+ parts.label.position.set(width / 2, height / 2);
46
+ }
47
+ syncTextResolution(parts.label, context.textResolution);
47
48
  }
48
49
  const customCardRenderer = {
49
50
  id: "custom-card",
@@ -62,7 +63,8 @@ const customCardRenderer = {
62
63
  wordWrap: true,
63
64
  breakWords: true
64
65
  },
65
- resolution: getBoardResolution()
66
+ resolution: context.textResolution,
67
+ roundPixels: true
66
68
  });
67
69
  label.anchor.set(.5);
68
70
  root.addChild(sprite, placeholder, label);
@@ -82,7 +84,7 @@ const customCardRenderer = {
82
84
  },
83
85
  resolution: (container, _item, context) => {
84
86
  const parts = partsByContainer.get(container);
85
- if (parts) syncTextResolution(parts.label, parts.label, context.zoom);
87
+ if (parts) syncTextResolution(parts.label, context.textResolution);
86
88
  },
87
89
  renderFar: (graphics, item, context) => {
88
90
  drawFarPlate(graphics, item.frame, {
@@ -47,6 +47,14 @@ function syncClip(parts, rect, style) {
47
47
  function sync(container, item, context) {
48
48
  const parts = partsByContainer.get(container);
49
49
  if (!parts) return;
50
+ syncContent(parts, item, context);
51
+ for (const text of [
52
+ parts.title,
53
+ parts.excerpt,
54
+ parts.typeMark
55
+ ]) syncTextResolution(text, context.textResolution);
56
+ }
57
+ function syncContent(parts, item, context) {
50
58
  positionShell(parts.root, item);
51
59
  const { width, height } = item.frame;
52
60
  const detail = detailFor(context.zoom);
@@ -64,9 +72,6 @@ function sync(container, item, context) {
64
72
  const kind = filePreviewKind(item.props.snapshot);
65
73
  const category = fileCategory(item.props.src, item.props.snapshot?.mimeType);
66
74
  const accent = fileCategoryAccent(category, context.palette);
67
- syncTextResolution(parts.title, parts.titleRes, context.zoom);
68
- syncTextResolution(parts.excerpt, parts.excerptRes, context.zoom);
69
- syncTextResolution(parts.typeMark, parts.typeMarkRes, context.zoom);
70
75
  const visualSig = [
71
76
  key ?? "",
72
77
  width,
@@ -190,7 +195,7 @@ const fileCardRenderer = {
190
195
  const cover = new Sprite(Texture.EMPTY);
191
196
  const coverMask = new Graphics();
192
197
  cover.mask = coverMask;
193
- const resolution = 1;
198
+ const resolution = context.textResolution;
194
199
  const title = new Text({
195
200
  text: "",
196
201
  style: {
@@ -247,10 +252,7 @@ const fileCardRenderer = {
247
252
  title,
248
253
  excerpt,
249
254
  visualSig: "",
250
- textSig: "",
251
- titleRes: { resolution },
252
- excerptRes: { resolution },
253
- typeMarkRes: { resolution }
255
+ textSig: ""
254
256
  });
255
257
  if (item.type === "file") sync(root, item, context);
256
258
  return root;
@@ -1,6 +1,6 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
- import { syncTextResolution } from "../text-resolution.js";
3
2
  import { itemAlpha, itemColor } from "../palette.js";
3
+ import { syncTextResolution } from "../text-resolution.js";
4
4
  import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
5
5
  import { createLabel, positionShell } from "./base-card-renderer.js";
6
6
  import { drawFarPlate } from "./far-plate.js";
@@ -13,7 +13,6 @@ function sync(container, item, context) {
13
13
  if (!parts) return;
14
14
  const { frame, style, props, locked } = item;
15
15
  positionShell(parts.root, item);
16
- syncTextResolution(parts.label, parts, context.zoom);
17
16
  const fill = itemColor(context, style.fill ?? style.stroke, "neutral", "fill");
18
17
  const fillAlpha = (style.fillOpacity ?? (style.fill ? 1 : DEFAULT_WASH)) * itemAlpha(context, style.fill ?? style.stroke);
19
18
  const baseStroke = itemColor(context, style.stroke, "neutral");
@@ -69,6 +68,7 @@ function sync(container, item, context) {
69
68
  parts.label.style.fill = labelColor;
70
69
  parts.label.style.fontFamily = context.style.font;
71
70
  }
71
+ syncTextResolution(parts.label, context.textResolution);
72
72
  parts.label.position.set(4, -18);
73
73
  }
74
74
  const frameCardRenderer = {
@@ -82,15 +82,14 @@ const frameCardRenderer = {
82
82
  fontFamily: BOARD_FONT_STACK,
83
83
  fontSize: 12,
84
84
  fontWeight: "600"
85
- });
85
+ }, context.textResolution);
86
86
  root.addChild(box, label);
87
87
  partsByContainer.set(root, {
88
88
  root,
89
89
  box,
90
90
  label,
91
91
  visualSig: "",
92
- textSig: "",
93
- resolution: label.resolution
92
+ textSig: ""
94
93
  });
95
94
  if (item.type === "frame") sync(root, item, context);
96
95
  return root;
@@ -100,7 +99,7 @@ const frameCardRenderer = {
100
99
  },
101
100
  resolution: (container, _item, context) => {
102
101
  const parts = partsByContainer.get(container);
103
- if (parts) syncTextResolution(parts.label, parts, context.zoom);
102
+ if (parts) syncTextResolution(parts.label, context.textResolution);
104
103
  },
105
104
  renderFar: (graphics, item, context) => {
106
105
  if (item.type !== "frame" || item.props.decoration === "none") return;
@@ -1,7 +1,7 @@
1
1
  import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { worldPoint } from "../../model/geometry.js";
3
- import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
4
3
  import { itemAlpha, itemColor } from "../palette.js";
4
+ import { syncTextResolution, syncTextWrapWidth } from "../text-resolution.js";
5
5
  import { dashPattern, roundRectPoints, tracePolyline, trimPolyline } from "../stroke.js";
6
6
  import { positionShell } from "./base-card-renderer.js";
7
7
  import { drawFarPlate } from "./far-plate.js";
@@ -100,7 +100,6 @@ function sync(container, item, context) {
100
100
  const fillOpacity = (style.fillOpacity ?? (style.fill ? 1 : 0)) * itemAlpha(context, style.fill ?? style.stroke);
101
101
  const strokeAlpha = itemAlpha(context, style.stroke ?? style.fill);
102
102
  const strokeWidth = style.strokeWidth ?? 1.75;
103
- syncTextResolution(parts.label, parts, context.zoom);
104
103
  const visualSig = [
105
104
  width,
106
105
  height,
@@ -159,6 +158,7 @@ function sync(container, item, context) {
159
158
  parts.label.style.align = props.align;
160
159
  }
161
160
  syncTextWrapWidth(parts.label, parts, Math.max(1, width - 16), context.resizingIds.has(item.id));
161
+ syncTextResolution(parts.label, context.textResolution);
162
162
  parts.label.position.set(width / 2, height / 2);
163
163
  }
164
164
  const shapeCardRenderer = {
@@ -167,7 +167,6 @@ const shapeCardRenderer = {
167
167
  create: (item, context) => {
168
168
  const root = new Container();
169
169
  const shape = new Graphics();
170
- const resolution = textResolutionForZoom(context.zoom);
171
170
  const label = new Text({
172
171
  text: "",
173
172
  style: {
@@ -179,7 +178,7 @@ const shapeCardRenderer = {
179
178
  wordWrap: true,
180
179
  lineHeight: 28
181
180
  },
182
- resolution,
181
+ resolution: context.textResolution,
183
182
  roundPixels: true
184
183
  });
185
184
  label.anchor.set(.5);
@@ -190,8 +189,7 @@ const shapeCardRenderer = {
190
189
  label,
191
190
  visualSig: "",
192
191
  textSig: "",
193
- wrapWidth: 0,
194
- resolution
192
+ wrapWidth: 0
195
193
  });
196
194
  if (item.type === "shape") sync(root, item, context);
197
195
  return root;
@@ -201,7 +199,7 @@ const shapeCardRenderer = {
201
199
  },
202
200
  resolution: (container, _item, context) => {
203
201
  const parts = partsByContainer.get(container);
204
- if (parts) syncTextResolution(parts.label, parts, context.zoom);
202
+ if (parts) syncTextResolution(parts.label, context.textResolution);
205
203
  },
206
204
  renderFar: (graphics, item, context) => {
207
205
  if (item.type !== "shape") return;
@@ -1,9 +1,9 @@
1
1
  import { BOARD_FONT_STACK, BOARD_MONO_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
2
  import { featuredTaskArtifact } from "../../../protocol/dist/board-task.js";
3
- import { drawAudioWaveform } from "../audio-waveform.js";
4
3
  import { fitTextToLines } from "../text-fit.js";
5
- import { boardCardDetailed, syncTextResolution, textResolutionForZoom } from "../text-resolution.js";
4
+ import { boardCardDetailed, syncTextResolution } from "../text-resolution.js";
6
5
  import { createCardTag, formatBoardDuration, syncCardTag, zoomCardTag } from "../card-tag.js";
6
+ import { drawAudioWaveform } from "../audio-waveform.js";
7
7
  import { cardElevation, containRect, drawCardSurface, traceCardOutline } from "../surface.js";
8
8
  import { positionShell } from "./base-card-renderer.js";
9
9
  import { drawFarPlate } from "./far-plate.js";
@@ -283,10 +283,6 @@ function sync(container, item, context) {
283
283
  const detailText = !full || !surface ? "" : status === "failed" ? snapshot.errorExcerpt ?? "" : working ? snapshot.promptExcerpt ?? "" : "";
284
284
  const textArtifact = kind === "text" && artifact?.type === "text";
285
285
  const bodyText = textArtifact ? artifact.textExcerpt : counting ? `${label} · ${elapsed}` : label;
286
- syncTextResolution(parts.body, parts, context.zoom);
287
- syncTextResolution(parts.detail, parts, context.zoom);
288
- syncTextResolution(parts.meta, parts, context.zoom);
289
- syncTextResolution(parts.badge, parts, context.zoom);
290
286
  const textSig = [
291
287
  counting ? label : bodyText,
292
288
  detailText,
@@ -317,6 +313,12 @@ function sync(container, item, context) {
317
313
  parts.badge.style.fill = context.palette.text;
318
314
  if (parts.badge.text !== count) parts.badge.text = count;
319
315
  }
316
+ for (const text of [
317
+ parts.body,
318
+ parts.detail,
319
+ parts.meta,
320
+ parts.badge
321
+ ]) syncTextResolution(text, context.textResolution);
320
322
  const badgeWidth = Math.max(22, parts.badge.width + 12);
321
323
  const visualSig = [
322
324
  width,
@@ -434,7 +436,6 @@ const taskCardRenderer = {
434
436
  const previewMask = new Graphics();
435
437
  const shimmer = new Graphics();
436
438
  const previewArt = new Graphics();
437
- const resolution = textResolutionForZoom(context.zoom);
438
439
  const text = (fontFamily, fontSize, fontWeight) => new Text({
439
440
  text: "",
440
441
  style: {
@@ -444,7 +445,7 @@ const taskCardRenderer = {
444
445
  wordWrap: true,
445
446
  breakWords: true
446
447
  },
447
- resolution,
448
+ resolution: context.textResolution,
448
449
  roundPixels: true
449
450
  });
450
451
  const body = text(BOARD_FONT_STACK, 11.5, "500");
@@ -478,7 +479,6 @@ const taskCardRenderer = {
478
479
  assetKey: null,
479
480
  visualSig: "",
480
481
  textSig: "",
481
- resolution,
482
482
  motion: {
483
483
  itemId: "",
484
484
  width: 0,