@neta-art/cohub 3.2.0 → 4.0.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 (34) hide show
  1. package/dist/board/codec.js +0 -9
  2. package/dist/board/core/draw-geometry.d.ts +3 -4
  3. package/dist/board/core/draw-geometry.js +21 -29
  4. package/dist/board/core/file-preview.d.ts +4 -2
  5. package/dist/board/core/file-preview.js +20 -9
  6. package/dist/board/core/palette.d.ts +2 -2
  7. package/dist/board/core/palette.js +28 -0
  8. package/dist/board/core/shape-types.d.ts +2 -6
  9. package/dist/board/core/text-metrics.d.ts +2 -2
  10. package/dist/board/core/text-metrics.js +2 -2
  11. package/dist/board/core/tool-styles.d.ts +52 -0
  12. package/dist/board/core/tool-styles.js +51 -0
  13. package/dist/board/geometry.d.ts +9 -1
  14. package/dist/board/geometry.js +25 -1
  15. package/dist/board/index.d.ts +6 -5
  16. package/dist/board/index.js +5 -4
  17. package/dist/board/render/renderers/board-renderer-registry.js +0 -2
  18. package/dist/board/render/renderers/file-card-renderer.d.ts +8 -1
  19. package/dist/board/render/renderers/file-card-renderer.js +37 -58
  20. package/dist/board/render/themes/board-theme-registry.d.ts +2 -0
  21. package/dist/board/render/themes/clean-theme.js +6 -3
  22. package/dist/chunks/http.d.ts +2 -2
  23. package/dist/chunks/http.js +4 -0
  24. package/dist/chunks/websocket.d.ts +54 -2
  25. package/dist/http.d.ts +1 -1
  26. package/dist/index.d.ts +2 -2
  27. package/dist/protocol/dist/board-constants.js +2 -1
  28. package/dist/protocol/dist/board-document.d.ts +3 -80
  29. package/dist/protocol/dist/board-document.js +4 -14
  30. package/dist/protocol/dist/realtime/board-awareness.js +3 -1
  31. package/docs/work-runtime-guide.md +68 -2
  32. package/package.json +3 -2
  33. package/dist/board/render/renderers/note-card-renderer.d.ts +0 -5
  34. package/dist/board/render/renderers/note-card-renderer.js +0 -112
@@ -110,38 +110,6 @@ declare const BoardTextItemSchema: z.ZodObject<{
110
110
  color: z.ZodDefault<z.ZodString>;
111
111
  fontSize: z.ZodDefault<z.ZodNumber>;
112
112
  }, z.core.$strip>;
113
- declare const BoardNoteItemSchema: z.ZodObject<{
114
- id: z.ZodString;
115
- frame: z.ZodObject<{
116
- x: z.ZodNumber;
117
- y: z.ZodNumber;
118
- width: z.ZodNumber;
119
- height: z.ZodNumber;
120
- rotation: z.ZodDefault<z.ZodNumber>;
121
- }, z.core.$strip>;
122
- locked: z.ZodOptional<z.ZodBoolean>;
123
- style: z.ZodOptional<z.ZodObject<{
124
- variant: z.ZodDefault<z.ZodString>;
125
- theme: z.ZodOptional<z.ZodString>;
126
- accentColor: z.ZodOptional<z.ZodString>;
127
- size: z.ZodDefault<z.ZodEnum<{
128
- lg: "lg";
129
- md: "md";
130
- sm: "sm";
131
- }>>;
132
- emphasis: z.ZodDefault<z.ZodEnum<{
133
- epic: "epic";
134
- legendary: "legendary";
135
- normal: "normal";
136
- rare: "rare";
137
- }>>;
138
- effects: z.ZodDefault<z.ZodArray<z.ZodString>>;
139
- }, z.core.$strip>>;
140
- metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
141
- type: z.ZodLiteral<"note">;
142
- text: z.ZodDefault<z.ZodString>;
143
- color: z.ZodDefault<z.ZodString>;
144
- }, z.core.$strip>;
145
113
  declare const BoardGeoItemSchema: z.ZodObject<{
146
114
  id: z.ZodString;
147
115
  frame: z.ZodObject<{
@@ -490,7 +458,7 @@ declare const BoardFileItemSchema: z.ZodObject<{
490
458
  * authored by newer clients round-trip losslessly — data is never dropped or
491
459
  * silently downgraded just because this client predates a shape type.
492
460
  */
493
- declare const KNOWN_BOARD_ITEM_TYPES: readonly ["image", "video", "file", "text", "note", "geo", "draw", "arrow", "frame"];
461
+ declare const KNOWN_BOARD_ITEM_TYPES: readonly ["image", "video", "file", "text", "geo", "draw", "arrow", "frame"];
494
462
  type KnownBoardItemType = (typeof KNOWN_BOARD_ITEM_TYPES)[number];
495
463
  /**
496
464
  * A forward-compatible carrier for shape types this client does not recognise.
@@ -637,28 +605,6 @@ declare const BoardItemSchema: z.ZodPipe<z.ZodAny, z.ZodTransform<BoardUnknownIt
637
605
  text: string;
638
606
  color: string;
639
607
  fontSize: number;
640
- } | {
641
- id: string;
642
- frame: {
643
- x: number;
644
- y: number;
645
- width: number;
646
- height: number;
647
- rotation: number;
648
- };
649
- locked?: boolean | undefined;
650
- style?: {
651
- variant: string;
652
- theme?: string | undefined;
653
- accentColor?: string | undefined;
654
- size: "lg" | "md" | "sm";
655
- emphasis: "epic" | "legendary" | "normal" | "rare";
656
- effects: string[];
657
- } | undefined;
658
- metadata?: Record<string, unknown> | undefined;
659
- type: "note";
660
- text: string;
661
- color: string;
662
608
  } | {
663
609
  id: string;
664
610
  frame: {
@@ -948,28 +894,6 @@ declare const BoardDocumentSchema: z.ZodObject<{
948
894
  text: string;
949
895
  color: string;
950
896
  fontSize: number;
951
- } | {
952
- id: string;
953
- frame: {
954
- x: number;
955
- y: number;
956
- width: number;
957
- height: number;
958
- rotation: number;
959
- };
960
- locked?: boolean | undefined;
961
- style?: {
962
- variant: string;
963
- theme?: string | undefined;
964
- accentColor?: string | undefined;
965
- size: "lg" | "md" | "sm";
966
- emphasis: "epic" | "legendary" | "normal" | "rare";
967
- effects: string[];
968
- } | undefined;
969
- metadata?: Record<string, unknown> | undefined;
970
- type: "note";
971
- text: string;
972
- color: string;
973
897
  } | {
974
898
  id: string;
975
899
  frame: {
@@ -1100,7 +1024,6 @@ type BoardItemStyle = z.infer<typeof BoardItemStyleSchema>;
1100
1024
  type SpaceFileRef = z.infer<typeof SpaceFileRefSchema>;
1101
1025
  type BoardMediaSnapshot = z.infer<typeof BoardMediaSnapshotSchema>;
1102
1026
  type BoardTextItem = z.infer<typeof BoardTextItemSchema>;
1103
- type BoardNoteItem = z.infer<typeof BoardNoteItemSchema>;
1104
1027
  type BoardGeoItem = z.infer<typeof BoardGeoItemSchema>;
1105
1028
  type DrawPoint = z.infer<typeof DrawPointSchema>;
1106
1029
  type BoardDrawItem = z.infer<typeof BoardDrawItemSchema>;
@@ -1112,7 +1035,7 @@ type BoardVideoItem = z.infer<typeof BoardVideoItemSchema>;
1112
1035
  type BoardFileSnapshot = z.infer<typeof BoardFileSnapshotSchema>;
1113
1036
  type BoardFileItem = z.infer<typeof BoardFileItemSchema>;
1114
1037
  /** Known (natively handled) item variants. */
1115
- type BoardKnownItem = BoardImageItem | BoardVideoItem | BoardFileItem | BoardTextItem | BoardNoteItem | BoardGeoItem | BoardDrawItem | BoardArrowItem | BoardFrameItem;
1038
+ type BoardKnownItem = BoardImageItem | BoardVideoItem | BoardFileItem | BoardTextItem | BoardGeoItem | BoardDrawItem | BoardArrowItem | BoardFrameItem;
1116
1039
  /** Any item, including forward-compatible unknown types. */
1117
1040
  type BoardItem = BoardKnownItem | BoardUnknownItem;
1118
1041
  type BoardDocument = z.infer<typeof BoardDocumentSchema>;
@@ -1121,4 +1044,4 @@ declare function isMediaItem(item: BoardItem): item is BoardImageItem | BoardVid
1121
1044
  /** Whether an item references a workspace file (image, video or file card). */
1122
1045
  declare function isFileBackedItem(item: BoardItem): item is BoardImageItem | BoardVideoItem | BoardFileItem;
1123
1046
  //#endregion
1124
- export { ArrowEndpoint, ArrowEndpointSchema, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAppearance, BoardAppearanceSchema, BoardArrowItem, BoardArrowItemSchema, BoardDocument, BoardDocumentSchema, BoardDrawItem, BoardDrawItemSchema, BoardFileItem, BoardFileItemSchema, BoardFileSnapshot, BoardFileSnapshotSchema, BoardFrame, BoardFrameItem, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItem, BoardGeoItemSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemSchema, BoardItemStyle, BoardItemStyleSchema, BoardKnownItem, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardNoteItem, BoardNoteItemSchema, BoardTextItem, BoardTextItemSchema, BoardUnknownItem, BoardVideoItem, BoardVideoItemSchema, BoardViewport, BoardViewportSchema, DrawPoint, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, KnownBoardItemType, SpaceFileRef, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, parseBoardItemLoose, unknownRealType };
1047
+ export { ArrowEndpoint, ArrowEndpointSchema, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAppearance, BoardAppearanceSchema, BoardArrowItem, BoardArrowItemSchema, BoardDocument, BoardDocumentSchema, BoardDrawItem, BoardDrawItemSchema, BoardFileItem, BoardFileItemSchema, BoardFileSnapshot, BoardFileSnapshotSchema, BoardFrame, BoardFrameItem, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItem, BoardGeoItemSchema, BoardImageItem, BoardImageItemSchema, BoardItem, BoardItemSchema, BoardItemStyle, BoardItemStyleSchema, BoardKnownItem, BoardMediaSnapshot, BoardMediaSnapshotSchema, BoardTextItem, BoardTextItemSchema, BoardUnknownItem, BoardVideoItem, BoardVideoItemSchema, BoardViewport, BoardViewportSchema, DrawPoint, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, KnownBoardItemType, SpaceFileRef, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, parseBoardItemLoose, unknownRealType };
@@ -1,4 +1,4 @@
1
- import "./board-constants.js";
1
+ import { BOARD_ARROW_STROKE_SIZE } from "./board-constants.js";
2
2
  import { BOARD_DOCUMENT_KIND, BOARD_EXTENSION } from "./board.js";
3
3
  import { z } from "zod";
4
4
  //#region ../protocol/dist/board-document.js
@@ -95,12 +95,7 @@ const BoardTextItemSchema = BoardItemBaseSchema.extend({
95
95
  type: z.literal("text"),
96
96
  text: z.string().default(""),
97
97
  color: z.string().min(1).default("neutral"),
98
- fontSize: z.number().finite().min(2).max(512).default(18)
99
- });
100
- const BoardNoteItemSchema = BoardItemBaseSchema.extend({
101
- type: z.literal("note"),
102
- text: z.string().default(""),
103
- color: z.string().min(1).default("amber")
98
+ fontSize: z.number().finite().min(2).max(512).default(24)
104
99
  });
105
100
  const BoardGeoItemSchema = BoardItemBaseSchema.extend({
106
101
  type: z.literal("geo"),
@@ -139,7 +134,7 @@ const BoardArrowItemSchema = BoardItemBaseSchema.extend({
139
134
  end: ArrowEndpointSchema,
140
135
  bend: z.number().finite().default(0),
141
136
  color: z.string().min(1).default("brand"),
142
- size: z.number().finite().positive().default(2.5),
137
+ size: z.number().finite().positive().default(BOARD_ARROW_STROKE_SIZE),
143
138
  arrowStart: z.boolean().default(false),
144
139
  arrowEnd: z.boolean().default(true),
145
140
  label: z.string().default("")
@@ -215,7 +210,6 @@ const KNOWN_BOARD_ITEM_TYPES = [
215
210
  "video",
216
211
  "file",
217
212
  "text",
218
- "note",
219
213
  "geo",
220
214
  "draw",
221
215
  "arrow",
@@ -258,10 +252,6 @@ function parseBoardItemLoose(raw) {
258
252
  const parsed = BoardTextItemSchema.safeParse(raw);
259
253
  return parsed.success ? parsed.data : makeUnknownItem(raw);
260
254
  }
261
- case "note": {
262
- const parsed = BoardNoteItemSchema.safeParse(raw);
263
- return parsed.success ? parsed.data : makeUnknownItem(raw);
264
- }
265
255
  case "geo": {
266
256
  const parsed = BoardGeoItemSchema.safeParse(raw);
267
257
  return parsed.success ? parsed.data : makeUnknownItem(raw);
@@ -328,4 +318,4 @@ function isFileBackedItem(item) {
328
318
  return item.type === "image" || item.type === "video" || item.type === "file";
329
319
  }
330
320
  //#endregion
331
- export { ArrowEndpointSchema, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAppearanceSchema, BoardArrowItemSchema, BoardDocumentSchema, BoardDrawItemSchema, BoardFileItemSchema, BoardFileSnapshotSchema, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItemSchema, BoardImageItemSchema, BoardItemSchema, BoardItemStyleSchema, BoardMediaSnapshotSchema, BoardNoteItemSchema, BoardTextItemSchema, BoardVideoItemSchema, BoardViewportSchema, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, parseBoardItemLoose, unknownRealType };
321
+ export { ArrowEndpointSchema, BOARD_DOCUMENT_KIND, BOARD_EXTENSION, BoardAppearanceSchema, BoardArrowItemSchema, BoardDocumentSchema, BoardDrawItemSchema, BoardFileItemSchema, BoardFileSnapshotSchema, BoardFrameItemSchema, BoardFrameSchema, BoardGeoItemSchema, BoardImageItemSchema, BoardItemSchema, BoardItemStyleSchema, BoardMediaSnapshotSchema, BoardTextItemSchema, BoardVideoItemSchema, BoardViewportSchema, DrawPointSchema, KNOWN_BOARD_ITEM_TYPES, SpaceFileRefSchema, UNKNOWN_BOARD_ITEM_TYPE, isFileBackedItem, isMediaItem, isUnknownItem, parseBoardItemLoose, unknownRealType };
@@ -1,3 +1,4 @@
1
+ import { BOARD_ARROW_STROKE_SIZE } from "../board-constants.js";
1
2
  import { z } from "zod";
2
3
  //#region ../protocol/dist/realtime/board-awareness.js
3
4
  const idSchema = z.string().min(1).max(160);
@@ -66,7 +67,8 @@ const BoardAwarenessGestureSchema = z.discriminatedUnion("kind", [
66
67
  nodeId: idSchema,
67
68
  start: BoardAwarenessPointSchema,
68
69
  current: BoardAwarenessPointSchema,
69
- color: z.string().min(1).max(64)
70
+ color: z.string().min(1).max(64),
71
+ size: finiteSchema.positive().max(256).default(BOARD_ARROW_STROKE_SIZE)
70
72
  }),
71
73
  z.object({
72
74
  kind: z.literal("box"),
@@ -205,7 +205,8 @@ result needs `taskrun.view` (a work scope).
205
205
  |---|---|---|---|
206
206
  | Read space config | `space.get()` / `space.getConfig()` | `space.view` | work |
207
207
  | List models | `client.models.list()` / `listMultimodal()` | *(none — just authenticated)* | — |
208
- | Send a prompt | `space.prompt({ content, ... })` | `session.prompt.fullaccess` (or `.readonly`) | viewer |
208
+ | Send a prompt (full) | `space.prompt({ accessMode: "full_access", content, ... })` | `session.prompt.fullaccess` | viewer |
209
+ | Send a prompt (read-only) | `space.prompt({ accessMode: "read_only", content, ... })` | `session.prompt.readonly` | viewer |
209
210
  | Read turn result | `session.turns.get(turnId)` | `session.view` | work |
210
211
  | Stream generation | `session.subscribeGeneration({ state, finalized })` | `session.view` | work |
211
212
  | Read file tree | `space.files.tree()` | `file.view` | work |
@@ -385,14 +386,23 @@ Assume `client` and `space` are already initialized per [§4](#4-initialization-
385
386
  ### LLM chat (`space.prompt` + `subscribeGeneration`)
386
387
 
387
388
  **Scopes:** viewer `session.prompt.fullaccess` (to send) + work `session.view` (to read/stream).
389
+ For a read-only prompt (no side effects), use viewer `session.prompt.readonly`
390
+ instead — but you **must** pass `accessMode: "read_only"` in the call (see the
391
+ read-only recipe below).
392
+
393
+ > **`accessMode` defaults to `full_access`.** If you omit it, the backend
394
+ treats the call as full-access and requires `session.prompt.fullaccess`. This
395
+ is the #1 cause of "I requested `session.prompt.readonly` but still got 403".
396
+ Always set `accessMode` explicitly to match the scope you requested.
388
397
 
389
398
  `space.prompt()` is **asynchronous** — it returns immediately with a turn
390
399
  whose `assistantText` is `null`. You must either stream the reply via
391
400
  `subscribeGeneration` or poll `turns.get()`.
392
401
 
393
402
  ```js
394
- // Send a prompt (creates or continues a session)
403
+ // Send a prompt (creates or continues a session) — full access
395
404
  const result = await space.prompt({
405
+ accessMode: "full_access", // default; needs session.prompt.fullaccess
396
406
  content: [{ type: "text", text: "Describe a shiba inu on Mars." }],
397
407
  sessionId: null, // null → creates a new session; pass an id to continue
398
408
  model: "gpt-5.5", // optional; omit for default
@@ -449,6 +459,58 @@ const reply = turn.assistantText;
449
459
  ignore it, your code silently degrades to polling — which will also 403.
450
460
  Surface the error so you can diagnose the missing scope.
451
461
 
462
+ #### Read-only prompt (`accessMode: "read_only"`)
463
+
464
+ Use this when your Work only needs to **generate** a reply without persisting
465
+ any side effects (no new session is written, no turn stored on the space's
466
+ history). It requires the lighter viewer scope `session.prompt.readonly`
467
+ instead of `session.prompt.fullaccess`.
468
+
469
+ The critical detail: you **must** pass `accessMode: "read_only"` explicitly
470
+ in the `space.prompt()` call. The scope you request via `auth.request` and
471
+ the `accessMode` you send must match — the backend picks the permission check
472
+ based on `accessMode`, defaulting to `full_access` when omitted.
473
+
474
+ ```js
475
+ // 1. Request ONLY the read-only scope from a user gesture
476
+ await client.auth.request({
477
+ scopes: ["session.prompt.readonly"],
478
+ reason: "Generate a one-off character reply (read-only).",
479
+ });
480
+
481
+ // 2. Send the prompt with accessMode matching the granted scope
482
+ const result = await space.prompt({
483
+ accessMode: "read_only", // ← required; omitting it → full_access → 403
484
+ sessionId: null, // read-only prompts use a throwaway session
485
+ content: [{ type: "text", text: prompt }],
486
+ });
487
+ const sessionId = result.session.id;
488
+ const turnId = result.turn.id;
489
+
490
+ // 3. Read the reply — still needs the work scope: session.view
491
+ const stop = space.session(sessionId).subscribeGeneration({
492
+ finalized: (event) => {
493
+ const reply = event.turn.assistantText
494
+ ?? (event.turn.assistantContent ?? [])
495
+ .filter(b => b.type === "text").map(b => b.text).join("");
496
+ console.log("reply:", reply);
497
+ stop();
498
+ },
499
+ error: (event) => console.error("stream error:", event),
500
+ });
501
+ ```
502
+
503
+ Publish the Work with:
504
+ - workScopes: `["space.view", "session.view"]` (still needed to read the reply)
505
+ - allowedViewerScopes: `["session.prompt.readonly"]`
506
+
507
+ > **Scope/accessMode mismatch → 403.** Requesting `session.prompt.readonly`
508
+ but calling `space.prompt({ content })` (no `accessMode`) fails because the
509
+ backend defaults to `full_access` and checks `session.prompt.fullaccess`.
510
+ Symmetrically, requesting `session.prompt.fullaccess` while passing
511
+ `accessMode: "read_only"` also works only if `session.prompt.readonly` is
512
+ additionally granted — otherwise 403. Always keep them in sync.
513
+
452
514
  ### Image / media generation (`generations.createAndWait`)
453
515
 
454
516
  **Scopes:** viewer `generation.create` (to create) + work `taskrun.view` (to poll).
@@ -895,6 +957,10 @@ Before publishing your Work, verify each item:
895
957
  (for file reads). Missing any of these → 403 on reads.
896
958
  - [ ] **Viewer scopes include all action operations**: `session.prompt.fullaccess`
897
959
  (or `.readonly`) for prompts, `generation.create` for generation.
960
+ - [ ] **`session.prompt.*` scope matches `space.prompt({ accessMode })`.**
961
+ Omitting `accessMode` defaults to `full_access`, so requesting only
962
+ `session.prompt.readonly` and then calling `space.prompt({ content })` → 403.
963
+ Set `accessMode: "read_only"` explicitly when using the readonly scope.
898
964
  - [ ] **`session.prompt.fullaccess` does NOT include `session.view`** — they
899
965
  are separate. Sending a prompt succeeds but reading the reply 403s without
900
966
  `session.view`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub",
3
- "version": "3.2.0",
3
+ "version": "4.0.1",
4
4
  "description": "Cohub SDK for spaces, sessions, boards, and realtime agent collaboration.",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
@@ -71,6 +71,7 @@
71
71
  "NOTICE"
72
72
  ],
73
73
  "dependencies": {
74
+ "perfect-freehand": "1.2.3",
74
75
  "zod": "^4.4.3"
75
76
  },
76
77
  "devDependencies": {
@@ -99,7 +100,7 @@
99
100
  "build": "tsdown",
100
101
  "lint": "biome lint .",
101
102
  "lint:fix": "biome lint --write .",
102
- "test": "tsx --test 'src/**/*.test.ts' 'tests/**/*.test.ts'",
103
+ "test": "node ../../scripts/test/run.mjs 'src/**/*.test.ts' 'tests/**/*.test.ts'",
103
104
  "typecheck": "tsc -p tsconfig.json --noEmit"
104
105
  }
105
106
  }
@@ -1,5 +0,0 @@
1
- import { BoardCardRenderer } from "./board-renderer-registry.js";
2
- //#region src/board/render/renderers/note-card-renderer.d.ts
3
- declare const noteCardRenderer: BoardCardRenderer;
4
- //#endregion
5
- export { noteCardRenderer };
@@ -1,112 +0,0 @@
1
- import { BOARD_FONT_STACK } from "../../../protocol/dist/board-constants.js";
2
- import { pickBoardColor } from "../../core/palette.js";
3
- import { syncTextResolution, syncTextWrapWidth, textResolutionForZoom } from "../text-resolution.js";
4
- import { drawFarPlate } from "./far-plate.js";
5
- import { positionShell } from "./base-card-renderer.js";
6
- import { Container, Graphics, Text } from "pixi.js";
7
- //#region src/board/render/renderers/note-card-renderer.ts
8
- const RADIUS = 8;
9
- const PADDING = 12;
10
- const partsByContainer = /* @__PURE__ */ new WeakMap();
11
- function sync(container, item, context) {
12
- const parts = partsByContainer.get(container);
13
- if (!parts) return;
14
- positionShell(parts.root, item);
15
- const { width, height } = item.frame;
16
- const selected = context.selectedIds.has(item.id);
17
- const hovered = context.hoveredId === item.id;
18
- const color = pickBoardColor(context.colors, item.color, context.colorScheme);
19
- syncTextResolution(parts.body, parts, context.zoom);
20
- const resizing = context.resizingIds.has(item.id);
21
- const visualSig = [
22
- width,
23
- height,
24
- selected,
25
- hovered,
26
- color.fill,
27
- color.stroke,
28
- context.palette.surface,
29
- context.palette.muted,
30
- context.palette.border
31
- ].join("|");
32
- if (visualSig !== parts.visualSig) {
33
- parts.visualSig = visualSig;
34
- parts.background.clear();
35
- parts.background.roundRect(0, 0, width, height, RADIUS).fill({
36
- color: color.fill,
37
- alpha: .16
38
- }).roundRect(0, 0, width, height, RADIUS).fill({
39
- color: context.palette.surface,
40
- alpha: .6
41
- }).rect(1, 1, width - 2, 4).fill({
42
- color: color.stroke,
43
- alpha: .95
44
- });
45
- parts.background.roundRect(0, 0, width, height, RADIUS).stroke({
46
- color: selected ? color.stroke : hovered ? context.palette.muted : context.palette.border,
47
- width: selected ? 2 : 1,
48
- alpha: selected ? .95 : .8
49
- });
50
- }
51
- const textSig = [item.text, color.label].join("|");
52
- if (textSig !== parts.textSig) {
53
- parts.textSig = textSig;
54
- parts.body.text = item.text;
55
- parts.body.style.fill = color.label;
56
- }
57
- syncTextWrapWidth(parts.body, parts, Math.max(1, width - PADDING * 2), resizing);
58
- parts.body.position.set(PADDING, 16);
59
- }
60
- const noteCardRenderer = {
61
- id: "note-card",
62
- canRender: (item) => item.type === "note",
63
- create: (item, context) => {
64
- const root = new Container();
65
- const background = new Graphics();
66
- const resolution = textResolutionForZoom(context.zoom);
67
- const body = new Text({
68
- text: "",
69
- style: {
70
- fill: 16777215,
71
- fontFamily: BOARD_FONT_STACK,
72
- fontSize: 14,
73
- fontWeight: "500",
74
- wordWrap: true,
75
- lineHeight: 20
76
- },
77
- resolution,
78
- roundPixels: true
79
- });
80
- root.addChild(background, body);
81
- partsByContainer.set(root, {
82
- root,
83
- background,
84
- body,
85
- visualSig: "",
86
- textSig: "",
87
- wrapWidth: 0,
88
- resolution
89
- });
90
- if (item.type === "note") sync(root, item, context);
91
- return root;
92
- },
93
- update: (container, item, context) => {
94
- if (item.type === "note") sync(container, item, context);
95
- },
96
- renderFar: (graphics, item, context) => {
97
- if (item.type !== "note") return;
98
- const color = pickBoardColor(context.colors, item.color, context.colorScheme);
99
- drawFarPlate(graphics, item.frame, {
100
- fill: color.fill,
101
- fillAlpha: .28,
102
- accent: color.stroke,
103
- accentAlpha: .95
104
- });
105
- },
106
- destroy: (container) => {
107
- partsByContainer.get(container)?.root.destroy({ children: true });
108
- partsByContainer.delete(container);
109
- }
110
- };
111
- //#endregion
112
- export { noteCardRenderer };