@fieldnotes/core 0.64.0 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -267,7 +267,7 @@ interface Tool {
267
267
  setOptions?(options: object): void;
268
268
  onOptionsChange?(listener: () => void): () => void;
269
269
  }
270
- type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'path' | 'template' | 'laser' | 'ping';
270
+ type ToolName = 'hand' | 'select' | 'pencil' | 'eraser' | 'arrow' | 'note' | 'image' | 'text' | 'shape' | 'measure' | 'path' | 'template' | 'laser' | 'ping' | 'fog';
271
271
 
272
272
  declare function snapPoint(point: Point, gridSize: number): Point;
273
273
  declare function snapToHexCenter(point: Point, cellSize: number, orientation: HexOrientation): Point;
@@ -341,6 +341,61 @@ interface Layer {
341
341
  opacity: number;
342
342
  }
343
343
 
344
+ declare const FOG_STATE_VERSION = 1;
345
+ declare const FOG_TILE_CELLS = 128;
346
+ declare const FOG_MAX_TILES = 256;
347
+ type FogBase = 'covered' | 'revealed';
348
+ interface FogDefinitionV1 {
349
+ readonly version: 1;
350
+ readonly generation: string;
351
+ readonly bounds: Bounds;
352
+ readonly cellSize: number;
353
+ readonly tileCells: 128;
354
+ readonly base: FogBase;
355
+ }
356
+ interface FogTileV1 {
357
+ readonly x: number;
358
+ readonly y: number;
359
+ readonly data: string;
360
+ }
361
+ interface FogStateV1 {
362
+ readonly definition: FogDefinitionV1;
363
+ readonly tiles: readonly FogTileV1[];
364
+ }
365
+ type FogViewMode = 'off' | 'editor' | 'player';
366
+ type FogOperation = 'reveal' | 'conceal';
367
+ type FogRegion = {
368
+ kind: 'brush';
369
+ points: readonly Point[];
370
+ radius: number;
371
+ } | {
372
+ kind: 'rectangle';
373
+ from: Point;
374
+ to: Point;
375
+ } | {
376
+ kind: 'polygon';
377
+ points: readonly Point[];
378
+ };
379
+ interface FogToolOptions {
380
+ operation?: FogOperation;
381
+ shape?: 'brush' | 'rectangle' | 'polygon';
382
+ radius?: number;
383
+ }
384
+ interface FogPatch {
385
+ readonly tiles: readonly FogTileV1[];
386
+ }
387
+ interface FogChangeEvent {
388
+ readonly kind: 'tiles' | 'definition' | 'reset' | 'disable';
389
+ readonly tiles?: readonly {
390
+ readonly x: number;
391
+ readonly y: number;
392
+ }[];
393
+ readonly origin?: string;
394
+ }
395
+ interface FogViewEvent {
396
+ readonly mode: FogViewMode;
397
+ }
398
+
344
399
  interface CanvasState {
345
400
  version: number;
346
401
  camera: {
@@ -350,6 +405,7 @@ interface CanvasState {
350
405
  elements: CanvasElement[];
351
406
  layers?: Layer[];
352
407
  activeLayerId?: string;
408
+ fog?: FogStateV1;
353
409
  }
354
410
 
355
411
  interface LayerManagerEvents {
@@ -392,6 +448,55 @@ declare class LayerManager {
392
448
  private findFallbackLayer;
393
449
  }
394
450
 
451
+ interface Command {
452
+ execute(store: ElementStore): void;
453
+ undo(store: ElementStore): void;
454
+ }
455
+
456
+ type FogIdFactory = () => string;
457
+ interface FogManagerOptions {
458
+ idFactory?: FogIdFactory;
459
+ onCommand?: (command: Command) => void;
460
+ }
461
+ type ChangeListener = (event: FogChangeEvent) => void;
462
+ type ViewListener = (event: FogViewEvent) => void;
463
+ declare class FogManager {
464
+ private state;
465
+ private viewMode;
466
+ private readonly idFactory;
467
+ private readonly onCommand;
468
+ private readonly changeListeners;
469
+ private readonly viewListeners;
470
+ constructor(options?: FogManagerOptions);
471
+ getState(): FogStateV1 | null;
472
+ getViewMode(): FogViewMode;
473
+ initialize(options: {
474
+ bounds: Bounds;
475
+ base?: FogBase;
476
+ cellSize?: number;
477
+ }): FogStateV1;
478
+ loadState(state: FogStateV1 | null, meta?: {
479
+ origin?: string;
480
+ }): void;
481
+ /** Restores a historical visual state without reusing its causal generation id. */
482
+ restoreHistoryState(state: FogStateV1 | null): void;
483
+ setBounds(bounds: Bounds): void;
484
+ reset(base: FogBase): void;
485
+ disable(): void;
486
+ setViewMode(mode: FogViewMode): void;
487
+ applyRegion(region: FogRegion, operation: FogOperation): void;
488
+ applyPatchDirect(patch: FogPatch, meta?: {
489
+ origin?: string;
490
+ }): void;
491
+ applyTilesDirect(tiles: readonly FogTileV1[]): void;
492
+ on(event: 'change', listener: ChangeListener): () => void;
493
+ on(event: 'view', listener: ViewListener): () => void;
494
+ dispose(): void;
495
+ private collectTiles;
496
+ private notifyChange;
497
+ private notifyView;
498
+ }
499
+
395
500
  interface StorageAdapter {
396
501
  load(key: string): Promise<string | null>;
397
502
  save(key: string, value: string): Promise<void>;
@@ -402,6 +507,7 @@ interface AutoSaveOptions {
402
507
  key?: string;
403
508
  debounceMs?: number;
404
509
  layerManager?: LayerManager;
510
+ fogManager?: FogManager;
405
511
  adapter?: StorageAdapter;
406
512
  onError?: (error: Error) => void;
407
513
  }
@@ -411,6 +517,7 @@ declare class AutoSave {
411
517
  private readonly key;
412
518
  private readonly debounceMs;
413
519
  private readonly layerManager?;
520
+ private readonly fogManager?;
414
521
  private readonly adapter;
415
522
  private timerId;
416
523
  private unsubscribers;
@@ -502,11 +609,6 @@ declare class ToolManager {
502
609
  onRegister(listener: (tool: Tool) => void): () => void;
503
610
  }
504
611
 
505
- interface Command {
506
- execute(store: ElementStore): void;
507
- undo(store: ElementStore): void;
508
- }
509
-
510
612
  interface HistoryStackOptions {
511
613
  maxSize?: number;
512
614
  }
@@ -635,6 +737,11 @@ interface ExportImageOptions extends ExportResourceOptions, HtmlExportOptions {
635
737
  * a non-fatal `'unsupported'` diagnostic regardless of this flag.
636
738
  */
637
739
  strictMissingCanvasHtml?: boolean;
740
+ fog?: {
741
+ state: FogStateV1;
742
+ mode: 'editor' | 'player';
743
+ color?: string;
744
+ } | false;
638
745
  }
639
746
  type ExportAssetErrorReason = 'load' | 'timeout' | 'encode';
640
747
  interface ExportAssetError {
@@ -660,20 +767,14 @@ interface ExportSvgOptions extends ExportResourceOptions, HtmlExportOptions {
660
767
  background?: string;
661
768
  filter?: (el: CanvasElement) => boolean;
662
769
  rasterScale?: number;
663
- /** Registry of canvas-backed html painters, keyed by `htmlType`. When absent (or when
664
- * an element's `htmlType` isn't claimed), html elements fall back to the legacy
665
- * DOM-raster path (`renderHtml`). */
666
770
  htmlPainters?: HtmlPainterRegistry;
667
- /** `htmlType`s that must route to canvas even before a painter for them is
668
- * registered — lets a host declare intent up front (mirrors `HtmlPainterRegistry.expect`). */
669
771
  expectedCanvasTypes?: ReadonlySet<string>;
670
- /**
671
- * When true, a canvas-routed html element with no active painter throws
672
- * `HtmlPainterMissingError` instead of reporting `onHtmlError` and continuing.
673
- * DOM-routed html elements are never affected — a missing `renderHtml` stays
674
- * a non-fatal `'unsupported'` diagnostic regardless of this flag.
675
- */
676
772
  strictMissingCanvasHtml?: boolean;
773
+ fog?: {
774
+ state: FogStateV1;
775
+ mode: 'editor' | 'player';
776
+ color?: string;
777
+ } | false;
677
778
  }
678
779
  declare function exportSvg(store: ElementStore, options?: ExportSvgOptions, layerManager?: LayerManager): Promise<string>;
679
780
 
@@ -699,6 +800,33 @@ interface RenderStatsSnapshot {
699
800
  frameCount: number;
700
801
  }
701
802
 
803
+ interface FogRendererOptions {
804
+ editorColor?: string;
805
+ playerColor?: string;
806
+ }
807
+ declare class FogRenderer {
808
+ private tileCache;
809
+ private state;
810
+ private viewMode;
811
+ private dirty;
812
+ private editorColor;
813
+ private playerColor;
814
+ constructor(options?: FogRendererOptions);
815
+ setState(state: FogStateV1 | null): void;
816
+ setViewMode(mode: FogViewMode): void;
817
+ getState(): FogStateV1 | null;
818
+ getViewMode(): FogViewMode;
819
+ markDirty(): void;
820
+ isDirty(): boolean;
821
+ isVisible(): boolean;
822
+ render(ctx: CanvasRenderingContext2D, camera: Camera, viewportWidth: number, viewportHeight: number, _dpr: number): void;
823
+ renderForExport(ctx: CanvasRenderingContext2D, state: FogStateV1, mode: 'editor' | 'player', color?: string): void;
824
+ dispose(): void;
825
+ private tileRaster;
826
+ private renderTile;
827
+ private renderTileForExport;
828
+ }
829
+
702
830
  /**
703
831
  * A world-space draw callback rendered above elements on every frame,
704
832
  * regardless of which tool is active. The context arrives with the camera
@@ -789,6 +917,11 @@ interface ViewportOptions {
789
917
  panInertia?: boolean;
790
918
  /** Show an overview minimap (bottom-right) with tap/drag-to-navigate. Default `false`. */
791
919
  minimap?: boolean;
920
+ /** Fog-of-war presentation options. Enables fog rendering and the `fog` accessor. */
921
+ fog?: {
922
+ editorColor?: string;
923
+ playerColor?: string;
924
+ };
792
925
  }
793
926
  interface HitTestOptions {
794
927
  /** Skip elements on locked layers. Default `true` (selection semantics). */
@@ -825,6 +958,8 @@ declare class Viewport {
825
958
  private _smartGuides;
826
959
  private readonly _gridSize;
827
960
  private readonly renderLoop;
961
+ private readonly fogManager;
962
+ private readonly fogRenderer;
828
963
  private readonly domNodeManager;
829
964
  private readonly interactMode;
830
965
  private readonly onHtmlElementMount?;
@@ -850,6 +985,7 @@ declare class Viewport {
850
985
  private unsubRecorderEnd;
851
986
  constructor(container: HTMLElement, options?: ViewportOptions);
852
987
  get ctx(): CanvasRenderingContext2D | null;
988
+ get fog(): FogManager;
853
989
  get snapToGrid(): boolean;
854
990
  setSnapToGrid(enabled: boolean): void;
855
991
  get smartGuides(): boolean;
@@ -2087,6 +2223,8 @@ declare class MinimapController {
2087
2223
  private readonly renderer;
2088
2224
  private readonly htmlPainters;
2089
2225
  private scene;
2226
+ private fogRenderer;
2227
+ private fogUnsub;
2090
2228
  private frameId;
2091
2229
  private debounceTimer;
2092
2230
  private dragging;
@@ -2094,6 +2232,7 @@ declare class MinimapController {
2094
2232
  private readonly unsubs;
2095
2233
  constructor(viewport: Viewport, canvas: HTMLCanvasElement, options?: MinimapControllerOptions);
2096
2234
  setSize(width: number, height: number): void;
2235
+ setFogRenderer(renderer: FogRenderer | null): void;
2097
2236
  requestDraw(): void;
2098
2237
  /**
2099
2238
  * Invalidates the cached scene bitmap in response to html-painter registry
@@ -2205,6 +2344,434 @@ declare class RemoteFocusReceiver {
2205
2344
  dispose(): void;
2206
2345
  }
2207
2346
 
2347
+ /**
2348
+ * Sender identity carried on every awareness frame. `id` is the app's stable
2349
+ * peer id (a character id, a user id); `name`, `color`, and `role` are
2350
+ * self-asserted display data. Receivers MUST treat `name` as untrusted text
2351
+ * (render through canvas text or `textContent`, never as HTML) and MUST NOT
2352
+ * gate anything security-relevant on any of these fields — the relay does not
2353
+ * authenticate presence payloads.
2354
+ */
2355
+ interface AwarenessIdentity {
2356
+ readonly id: string;
2357
+ readonly name?: string;
2358
+ readonly color?: string;
2359
+ readonly role?: string;
2360
+ }
2361
+ /**
2362
+ * The wire shape of one awareness frame. Every frame is the sender's COMPLETE
2363
+ * state (identity + whatever it publishes), so any coalescer that keeps only
2364
+ * the newest frame — the relay's per-kind throttle lane, a slow consumer — is
2365
+ * correct by construction: latest wins. Absent fields mean "none / not
2366
+ * published"; there is no delta encoding. A `cleared` frame says the sender is
2367
+ * going away (receivers drop it now instead of waiting for stale expiry).
2368
+ * Presence only: never elements, undo history, persisted canvas state, or
2369
+ * durable operations.
2370
+ */
2371
+ interface AwarenessPresence extends AwarenessIdentity {
2372
+ readonly kind: 'awareness';
2373
+ /** World-space pointer position; absent = no cursor to show. */
2374
+ readonly cursor?: Point;
2375
+ /** Selected element ids the sender chose to publish; absent = none. */
2376
+ readonly selection?: readonly string[];
2377
+ /** Active tool name; absent = not published. */
2378
+ readonly tool?: string;
2379
+ readonly cleared?: true;
2380
+ }
2381
+ declare const AWARENESS_PRESENCE_KIND = "awareness";
2382
+ /** Cap on `selection` entries; longer payloads are rejected outright. */
2383
+ declare const AWARENESS_MAX_SELECTION = 256;
2384
+ /**
2385
+ * Wire-boundary guard, fail closed: any violated cap rejects the whole frame
2386
+ * (nothing is clamped or sanitised). Unknown extra fields are tolerated so a
2387
+ * newer sender can add fields without breaking older receivers. `cleared`,
2388
+ * when present, must be exactly `true`; `cleared: false` rejects the whole
2389
+ * frame.
2390
+ */
2391
+ declare function isAwarenessPresence(data: unknown): data is AwarenessPresence;
2392
+
2393
+ /** One remote peer as last seen. Liveness timestamps are deliberately not exposed. */
2394
+ interface Peer extends AwarenessIdentity {
2395
+ /** The relay's server-owned per-socket sender key (the envelope `from`). */
2396
+ readonly from: string;
2397
+ readonly cursor: Point | null;
2398
+ readonly selection: readonly string[];
2399
+ readonly tool: string | null;
2400
+ }
2401
+ type PeerLeaveReason = 'left' | 'cleared' | 'stale';
2402
+ interface PeerRosterOptions {
2403
+ /**
2404
+ * A sender with no valid frame for this long is dropped (row and discovery
2405
+ * entry) with reason `'stale'`. Default `45000` (3× the publisher heartbeat);
2406
+ * `0` disables expiry.
2407
+ */
2408
+ staleMs?: number;
2409
+ /** Clock seam for tests; default `Date.now`. */
2410
+ now?: () => number;
2411
+ }
2412
+ /**
2413
+ * Last-known awareness state per remote sender, keyed by the relay's
2414
+ * server-owned `from`. Two books are kept deliberately separate:
2415
+ *
2416
+ * - **Rows** are visible membership: created by any valid non-cleared frame,
2417
+ * removed by a `cleared` frame, by `remove()` (the server-authored
2418
+ * presence-leave), or by stale expiry.
2419
+ * - **Discovery entries** are the re-announce budget: touched by EVERY valid
2420
+ * frame (cleared included) and dropped ONLY by `remove()` or by `staleMs`
2421
+ * without any valid frame. `onDiscover` fires when a valid frame arrives from
2422
+ * a sender with no entry — at most once per socket lifetime, so a client
2423
+ * cycling full → cleared → full cannot make every peer re-announce.
2424
+ *
2425
+ * `getPeers()` returns the same array until a visible field or membership
2426
+ * changes (a heartbeat carrying identical state is silent), which makes it a
2427
+ * valid `useSyncExternalStore` snapshot. Remote clocks are never trusted; all
2428
+ * timing is local.
2429
+ */
2430
+ declare class PeerRoster {
2431
+ private readonly staleMs;
2432
+ private readonly now;
2433
+ private readonly rows;
2434
+ private readonly discovered;
2435
+ private readonly changeListeners;
2436
+ private readonly discoverListeners;
2437
+ private readonly leaveListeners;
2438
+ private snapshot;
2439
+ private snapshotDirty;
2440
+ private staleTimer;
2441
+ private isDisposed;
2442
+ constructor(options?: PeerRosterOptions);
2443
+ get disposed(): boolean;
2444
+ /**
2445
+ * Applies a presence payload from `from`. Non-awareness or malformed payloads
2446
+ * return `false` untouched, so hosts can feed every presence frame through.
2447
+ */
2448
+ apply(from: string, data: unknown): boolean;
2449
+ /** Server-authored presence-leave: drops the row AND the discovery entry. */
2450
+ remove(from: string): void;
2451
+ getPeers(): readonly Peer[];
2452
+ getPeer(from: string): Peer | undefined;
2453
+ /** Fires only when `getPeers()` would return a new reference. */
2454
+ onChange(listener: () => void): () => void;
2455
+ /** First valid frame from a sender since its discovery entry was last dropped. */
2456
+ onDiscover(listener: (from: string) => void): () => void;
2457
+ onLeave(listener: (peer: Peer, reason: PeerLeaveReason) => void): () => void;
2458
+ dispose(): void;
2459
+ private dropRow;
2460
+ private changed;
2461
+ private emit;
2462
+ private armStaleTimer;
2463
+ private expireStale;
2464
+ }
2465
+
2466
+ /** The viewport capabilities the publisher reads; `Viewport` satisfies it. */
2467
+ interface LocalAwarenessHost {
2468
+ readonly camera: {
2469
+ screenToWorld(screen: Point): Point;
2470
+ };
2471
+ /** The pointer listener attaches to `domLayer.parentElement` (the wrapper). */
2472
+ readonly domLayer: HTMLElement;
2473
+ onSelectionChange(listener: () => void): () => void;
2474
+ getSelectedIds(): string[];
2475
+ readonly toolManager: {
2476
+ onChange(listener: (name: string) => void): () => void;
2477
+ readonly activeTool: {
2478
+ readonly name: string;
2479
+ } | null;
2480
+ };
2481
+ }
2482
+ interface AwarenessFields {
2483
+ cursor?: boolean;
2484
+ /** Off by default: selection ids are a disclosure the moment they are sent. */
2485
+ selection?: boolean;
2486
+ tool?: boolean;
2487
+ }
2488
+ interface LocalAwarenessOptions {
2489
+ identity: AwarenessIdentity;
2490
+ /** Default `{ cursor: true, selection: false, tool: true }`. */
2491
+ fields?: AwarenessFields;
2492
+ /**
2493
+ * Host projection applied to selected ids before they leave the client.
2494
+ * Fails closed: if it throws or returns anything but an array of strings,
2495
+ * frames carry NO selection (never the unfiltered ids) until it next
2496
+ * succeeds, and the error goes to `onError`.
2497
+ */
2498
+ selectionFilter?: (ids: readonly string[]) => readonly string[];
2499
+ onError?: (error: unknown) => void;
2500
+ /** Minimum spacing between frames. Default `50` (the relay lane throttle). */
2501
+ intervalMs?: number;
2502
+ /** Full-state re-send while idle, measured from the last send. Default `15000`; `0` disables. */
2503
+ heartbeatMs?: number;
2504
+ /** Pointer listener target override; default `host.domLayer.parentElement`. */
2505
+ element?: HTMLElement;
2506
+ send: (data: AwarenessPresence) => void;
2507
+ }
2508
+ /**
2509
+ * Publishes this client's awareness state (identity, pointer in world space,
2510
+ * selection, active tool) as full-snapshot frames through `send`. Sources are
2511
+ * a passive primary-pointer listener on the viewport wrapper, the viewport's
2512
+ * selection-change event, and the tool manager's change event. Any change
2513
+ * marks the state dirty; a leading-edge frame goes out at once when idle,
2514
+ * otherwise one trailing frame per `intervalMs` carries every change made in
2515
+ * the window. A heartbeat re-sends the state while idle. `dispose` sends one
2516
+ * `cleared` frame. Nothing here touches elements, history, or the camera.
2517
+ *
2518
+ * Frames are valid by construction: identity is truncated to the wire caps
2519
+ * (`name`/`color` to 64 characters, `role` to 32) so a sender can never
2520
+ * publish a frame the wire guard would reject outright; truncation is
2521
+ * reported through `onError` (a `RangeError`) rather than silent. An
2522
+ * invalid `id` (empty or over 128 characters) throws instead of truncating.
2523
+ * `tool` is never truncated: a tool name over 64 characters is omitted from
2524
+ * the frame rather than corrupted. An over-long or empty selection id fails
2525
+ * the selection closed (like a non-string filter entry) rather than being
2526
+ * sent. `intervalMs` and `heartbeatMs` are normalised so a non-finite or
2527
+ * out-of-range value (e.g. `Infinity`) can never arm a near-zero busy-loop
2528
+ * timer. The selection filter is re-applied before every frame that carries
2529
+ * a selection, so a policy change takes effect on the next frame without a
2530
+ * selection event.
2531
+ */
2532
+ declare class LocalAwareness {
2533
+ private readonly host;
2534
+ private readonly element;
2535
+ private readonly send;
2536
+ private readonly selectionFilter;
2537
+ private readonly onError;
2538
+ private readonly intervalMs;
2539
+ private readonly heartbeatMs;
2540
+ private identity;
2541
+ private fields;
2542
+ private lastPointer;
2543
+ private selection;
2544
+ private selectionFailed;
2545
+ private tool;
2546
+ private dirty;
2547
+ private lastSentAt;
2548
+ private throttleTimer;
2549
+ private heartbeatTimer;
2550
+ private readonly unsubscribers;
2551
+ private isDisposed;
2552
+ private readonly handlePointerMove;
2553
+ private readonly handlePointerEnd;
2554
+ constructor(host: LocalAwarenessHost, options: LocalAwarenessOptions);
2555
+ get disposed(): boolean;
2556
+ getFields(): Readonly<Required<AwarenessFields>>;
2557
+ setIdentity(identity: AwarenessIdentity): void;
2558
+ /** Merges the given flags into the current policy; `undefined` keys are ignored. */
2559
+ setFields(fields: AwarenessFields): void;
2560
+ /**
2561
+ * Requests a full frame: immediate when idle, otherwise folded into the
2562
+ * pending trailing frame (so N simultaneous requests cost one frame). Hosts
2563
+ * call it when the connection becomes live or reconnects.
2564
+ */
2565
+ announce(): void;
2566
+ /**
2567
+ * The complete state a frame carries right now. Side-effecting when
2568
+ * selection publishing is on: re-reads `getSelectedIds()`, re-runs
2569
+ * `selectionFilter`, updates the fail-closed selection state, and may call
2570
+ * `onError`. A no-op with respect to selection while publishing is off.
2571
+ */
2572
+ getState(): AwarenessPresence;
2573
+ dispose(): void;
2574
+ private now;
2575
+ private onPointerMove;
2576
+ private onPointerEnd;
2577
+ private refreshSelection;
2578
+ private schedule;
2579
+ private flush;
2580
+ private armHeartbeat;
2581
+ private safeSend;
2582
+ private report;
2583
+ /**
2584
+ * Truncates identity strings to the wire caps and rejects an invalid id, so a
2585
+ * sender can never publish a frame that the wire guard would drop outright.
2586
+ * A truncated field is reported through `onError` (a `RangeError`) rather
2587
+ * than silently shortened, so a caller passing an over-long name finds out.
2588
+ */
2589
+ private normalizeIdentity;
2590
+ }
2591
+
2592
+ /** The viewport capabilities the overlay needs; `Viewport` satisfies it. */
2593
+ interface RemoteCursorOverlayHost {
2594
+ registerOverlay(draw: OverlayRenderer): () => void;
2595
+ requestRender(): void;
2596
+ /** Read at draw time so glyph and label keep a constant screen size. */
2597
+ readonly camera: {
2598
+ readonly zoom: number;
2599
+ };
2600
+ }
2601
+ interface RemoteCursorOverlayOptions {
2602
+ /**
2603
+ * Host colour resolver consulted first (e.g. a campaign's per-player colour
2604
+ * table keyed by `peer.id`). Return `undefined` to fall through to the wire
2605
+ * `color`, then to `defaultPeerColor(peer.id)`.
2606
+ */
2607
+ colorFor?: (peer: Peer) => string | undefined;
2608
+ /** Draw the name chip next to the arrow. Default `true`. */
2609
+ showLabels?: boolean;
2610
+ /** Default `'12px sans-serif'`. */
2611
+ labelFont?: string;
2612
+ }
2613
+ /** Twelve well-separated hues so peers stay distinguishable without a colour on the wire. */
2614
+ declare const PEER_COLORS: readonly string[];
2615
+ /**
2616
+ * Deterministic palette colour for a stable peer id (FNV-1a over UTF-16 code
2617
+ * units), so every client shows the same peer in the same colour without any
2618
+ * colour travelling on the wire.
2619
+ */
2620
+ declare function defaultPeerColor(seed: string): string;
2621
+ /**
2622
+ * Renders every roster peer that has a cursor as an arrow glyph plus a name
2623
+ * chip, in world space but at constant screen size (scaled by `1 / zoom`).
2624
+ * Names are drawn as canvas text, so untrusted display text is inert. Reads
2625
+ * the roster only; never re-parses payloads, never moves the camera, never
2626
+ * touches the store.
2627
+ */
2628
+ declare class RemoteCursorOverlay {
2629
+ private readonly host;
2630
+ private readonly roster;
2631
+ private readonly colorFor;
2632
+ private readonly showLabels;
2633
+ private readonly labelFont;
2634
+ private readonly labelWidths;
2635
+ private unregister;
2636
+ private unsubscribe;
2637
+ private isDisposed;
2638
+ constructor(host: RemoteCursorOverlayHost, roster: PeerRoster, options?: RemoteCursorOverlayOptions);
2639
+ get disposed(): boolean;
2640
+ resolveColor(peer: Peer): string;
2641
+ dispose(): void;
2642
+ private render;
2643
+ private drawLabel;
2644
+ }
2645
+
2646
+ /** The viewport capabilities the overlay needs; `Viewport` satisfies it. */
2647
+ interface RemoteSelectionOverlayHost {
2648
+ registerOverlay(draw: OverlayRenderer): () => void;
2649
+ requestRender(): void;
2650
+ readonly camera: {
2651
+ readonly zoom: number;
2652
+ };
2653
+ readonly store: ElementStore;
2654
+ readonly layerManager: LayerManager;
2655
+ }
2656
+ interface RemoteSelectionOverlayOptions {
2657
+ /** Same precedence as `RemoteCursorOverlay`: resolver → wire colour → `defaultPeerColor`. */
2658
+ colorFor?: (peer: Peer) => string | undefined;
2659
+ /** Outline opacity. Default `0.6`. */
2660
+ alpha?: number;
2661
+ /** Outline width in screen pixels. Default `2`. */
2662
+ lineWidthPx?: number;
2663
+ }
2664
+ /**
2665
+ * Outlines the elements other peers have selected. Only ids that exist in the
2666
+ * LOCAL store AND sit on a locally visible layer are drawn; this is a
2667
+ * rendering courtesy on top of the sender-side opt-in (`fields.selection`) and
2668
+ * `selectionFilter` — privacy is decided at publish time, not here. The store
2669
+ * is rescanned only when some peer's selection reference or resolved colour
2670
+ * changed, or when the store or layer visibility changed; cursor-only roster
2671
+ * updates never scan. When two peers select the same element, the peer
2672
+ * earlier in `roster.getPeers()` order (insertion order: whoever the roster
2673
+ * saw first) supplies the outline colour for it.
2674
+ */
2675
+ declare class RemoteSelectionOverlay {
2676
+ private readonly host;
2677
+ private readonly roster;
2678
+ private readonly colorFor;
2679
+ private readonly alpha;
2680
+ private readonly lineWidthPx;
2681
+ private signatures;
2682
+ private outlines;
2683
+ private storeDirty;
2684
+ private unregister;
2685
+ private readonly unsubscribers;
2686
+ private isDisposed;
2687
+ constructor(host: RemoteSelectionOverlayHost, roster: PeerRoster, options?: RemoteSelectionOverlayOptions);
2688
+ get disposed(): boolean;
2689
+ dispose(): void;
2690
+ private resolveColor;
2691
+ /** Recomputes outlines only when the selection signature or the store/layers changed. */
2692
+ private rebuild;
2693
+ private render;
2694
+ }
2695
+
2696
+ /**
2697
+ * The three presence primitives every Field Notes transport exposes. The raw
2698
+ * `SyncClient`, `ManagedSyncConnection`, and host wrappers all satisfy it
2699
+ * structurally, so core never depends on `@fieldnotes/sync`. `from` is the
2700
+ * relay's opaque per-sender key.
2701
+ */
2702
+ interface PresenceChannel {
2703
+ sendPresence(data: unknown): void;
2704
+ onPresence(handler: (from: string, data: unknown) => void): () => void;
2705
+ onPresenceLeave(handler: (from: string) => void): () => void;
2706
+ }
2707
+ /**
2708
+ * What `attachAwareness` needs from a viewport; `Viewport` satisfies it.
2709
+ *
2710
+ * This does not `extends LocalAwarenessHost, RemoteCursorOverlayHost,
2711
+ * RemoteSelectionOverlayHost`: those three declare `camera` with different
2712
+ * member sets (`screenToWorld` vs `zoom`), and TypeScript rejects an
2713
+ * interface that extends multiple parents whose same-named property types
2714
+ * are not identical or mutually assignable. Listing the members directly
2715
+ * (with `camera` widened to carry both) keeps `AwarenessViewport`
2716
+ * structurally assignable to all three host types.
2717
+ */
2718
+ interface AwarenessViewport {
2719
+ readonly camera: {
2720
+ readonly zoom: number;
2721
+ screenToWorld(screen: Point): Point;
2722
+ };
2723
+ /** The pointer listener attaches to `domLayer.parentElement` (the wrapper). */
2724
+ readonly domLayer: HTMLElement;
2725
+ readonly store: ElementStore;
2726
+ readonly layerManager: LayerManager;
2727
+ onSelectionChange(listener: () => void): () => void;
2728
+ getSelectedIds(): string[];
2729
+ readonly toolManager: {
2730
+ onChange(listener: (name: string) => void): () => void;
2731
+ readonly activeTool: {
2732
+ readonly name: string;
2733
+ } | null;
2734
+ };
2735
+ registerOverlay(draw: OverlayRenderer): () => void;
2736
+ requestRender(): void;
2737
+ }
2738
+ interface AttachAwarenessOptions extends Omit<LocalAwarenessOptions, 'send'> {
2739
+ roster?: PeerRosterOptions;
2740
+ /** Named cursor overlay options, or `false` to render no cursors. Default on. */
2741
+ cursors?: RemoteCursorOverlayOptions | false;
2742
+ /** Selection outline overlay: options or `true` to enable. Default off. */
2743
+ selections?: RemoteSelectionOverlayOptions | boolean;
2744
+ /**
2745
+ * `false` builds a receive-only attachment (no `LocalAwareness`). Default
2746
+ * `true`. When `false`, `identity` is ignored: there is no publisher to
2747
+ * carry it.
2748
+ */
2749
+ publish?: boolean;
2750
+ }
2751
+ interface AwarenessHandle {
2752
+ readonly roster: PeerRoster;
2753
+ /** `null` when `publish: false`. */
2754
+ readonly local: LocalAwareness | null;
2755
+ readonly cursors: RemoteCursorOverlay | null;
2756
+ readonly selections: RemoteSelectionOverlay | null;
2757
+ /** Call when the connection becomes live or reconnects. No-op when receive-only. */
2758
+ announce(): void;
2759
+ /** Forwards to `LocalAwareness.setFields` (merge). No-op when receive-only. */
2760
+ setFields(fields: AwarenessFields): void;
2761
+ dispose(): void;
2762
+ }
2763
+ /**
2764
+ * Binds the awareness lifecycle to a presence channel: incoming frames feed
2765
+ * the roster, presence-leave drops rows and discovery budget, and each newly
2766
+ * discovered sender makes this client re-announce once, coalesced by the
2767
+ * publisher's interval: from a cold start the first discovery goes out on
2768
+ * the leading edge and later ones fold into a single trailing frame, so N
2769
+ * simultaneous joiners cost at most two frames; while already active, one.
2770
+ * Dispose order: publisher (sends `cleared`) → overlays → roster → channel
2771
+ * unsubscribes.
2772
+ */
2773
+ declare function attachAwareness(viewport: AwarenessViewport, channel: PresenceChannel, options: AttachAwarenessOptions): AwarenessHandle;
2774
+
2208
2775
  interface ActiveFormats {
2209
2776
  bold: boolean;
2210
2777
  italic: boolean;
@@ -2611,6 +3178,43 @@ declare class TemplateTool implements Tool {
2611
3178
  private notifyOptionsChange;
2612
3179
  }
2613
3180
 
2614
- declare const VERSION = "0.64.0";
3181
+ declare function encodeBase64(bytes: Uint8Array): string;
3182
+ declare function decodeBase64(str: string): Uint8Array;
3183
+ declare function canonicalizeFogTile(tile: FogTileV1, def: FogDefinitionV1): FogTileV1 | null;
3184
+ declare function validateFogDefinition(def: unknown): asserts def is FogDefinitionV1;
3185
+ declare function validateFogTile(tile: unknown, def: FogDefinitionV1): asserts tile is FogTileV1;
3186
+ declare function validateFogState(state: unknown): asserts state is FogStateV1;
3187
+ declare function recommendedFogCellSize(bounds: Bounds): number;
3188
+
3189
+ declare class FogTool implements Tool {
3190
+ readonly name = "fog";
3191
+ private drawing;
3192
+ private points;
3193
+ private startPoint;
3194
+ private operation;
3195
+ private shape;
3196
+ private radius;
3197
+ private readonly manager;
3198
+ private optionListeners;
3199
+ constructor(manager: FogManager, options?: FogToolOptions);
3200
+ onActivate(ctx: ToolContext): void;
3201
+ onDeactivate(ctx: ToolContext): void;
3202
+ getOptions(): FogToolOptions;
3203
+ setOptions(options: FogToolOptions): void;
3204
+ onOptionsChange(listener: () => void): () => void;
3205
+ onPointerDown(state: PointerState, ctx: ToolContext): void;
3206
+ onPointerMove(state: PointerState, ctx: ToolContext): void;
3207
+ onPointerUp(_state: PointerState, ctx: ToolContext): void;
3208
+ onPointerCancel(_state: PointerState, ctx: ToolContext): void;
3209
+ onKeyDown(event: KeyboardEvent, ctx: ToolContext): boolean;
3210
+ renderOverlay(ctx: CanvasRenderingContext2D): void;
3211
+ private buildRegion;
3212
+ private cancelGesture;
3213
+ private renderBrushPreview;
3214
+ private renderRectanglePreview;
3215
+ private renderPolygonPreview;
3216
+ }
3217
+
3218
+ declare const VERSION = "0.66.0";
2615
3219
 
2616
- export { type ActivationOptions, type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, AutoSave, type AutoSaveOptions, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraAnimationEndReason, CameraAnimator, type CameraAnimatorOptions, type CameraChangeInfo, type CameraOptions, type CameraView, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DiagonalRule, type DistributeAxis, type ElementActivationEvent, type ElementChangeMeta, type ElementRect, type ElementRectMatch, type ElementRectMatchError, ElementRectTracker, type ElementRectTrackerOptions, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportAssetError, type ExportAssetErrorReason, type ExportImageOptions, type ExportResourceOptions, type ExportSvgOptions, FOCUS_PRESENCE_KIND, type FocusAudience, type FocusPresence, type FocusRole, type FontSizePreset, type Footprint, type FrameScheduler, type GridElement, type GridInfo, type GridMetric, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HitTestOptions, type HtmlElement, type HtmlExportError, type HtmlExportErrorReason, type HtmlExportOptions, type HtmlExportRenderer, type HtmlPaintContext, type HtmlPaintDiagnostic, type HtmlPainter, HtmlPainterMissingError, HtmlPainterRegistry, type HtmlRenderTarget, type HtmlRouting, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LASER_TRAIL_PRESENCE_KIND, LaserTool, type LaserToolOptions, type LaserTrailEmission, type LaserTrailPresence, type Layer, LayerManager, LocalStorageAdapter, MEASURE_PRESENCE_KIND, type MeasureEmission, type MeasurePresence, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, MinimapController, type MinimapControllerOptions, type NoteElement, NoteTool, type NoteToolOptions, type OverlayRenderer, PATH_PRESENCE_KIND, PATH_PRESENCE_MAX_POINTS, PING_PRESENCE_KIND, type PathAnchor, type PathDistance, type PathEmission, type PathPresence, type PathRangeBand, type PathSegment, PathTool, type PathToolOptions, PencilTool, type PencilToolOptions, type PingEmission, PingInput, type PingInputHost, type PingInputOptions, type PingPresence, PingTool, type PingToolOptions, type Point, type PointerState, type RectTrackerHost, RemoteFocusReceiver, type RemoteFocusReceiverHost, type RemoteFocusReceiverOptions, RemoteLaserOverlay, type RemoteLaserOverlayHost, type RemoteLaserOverlayOptions, RemoteMeasureOverlay, type RemoteMeasureOverlayHost, type RemoteMeasureOverlayOptions, RemotePathOverlay, type RemotePathOverlayHost, type RemotePathOverlayOptions, RemotePingOverlay, type RemotePingOverlayHost, type RemotePingOverlayOptions, type RenderStatsSnapshot, type RotateDirection, SelectTool, type SelectionStyleDetails, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, applyCameraView, boundsIntersect, cameraOriginForView, captureCameraView, computeElementRects, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, drawHexPath, elementRectsEqual, exportImage, exportSvg, fitZoomForView, footprintFromSize, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, gridDistanceCells, isFocusPresence, isLaserTrailPresence, isMeasurePresence, isNearBezier, isPathPresence, isPingPresence, pathDistanceCells, resolveHtmlRouting, setFontSize, smartSnap, snapFootprintCenter, snapPoint, snapToCellCenter, snapToHexCenter, styleToPatch, toFocusPresence, toLaserTrailPresence, toMeasurePresence, toPathPresence, toPingPresence, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline };
3220
+ export { AWARENESS_MAX_SELECTION, AWARENESS_PRESENCE_KIND, type ActivationOptions, type ActiveFormats, type AlignEdge, type ArrowElement, type ArrowStrokeStyle, ArrowTool, type ArrowToolOptions, type AttachAwarenessOptions, AutoSave, type AutoSaveOptions, type AwarenessFields, type AwarenessHandle, type AwarenessIdentity, type AwarenessPresence, type AwarenessViewport, type BackgroundOptions, type BackgroundPattern, type Binding, type Bounds, Camera, type CameraAnimationEndReason, CameraAnimator, type CameraAnimatorOptions, type CameraChangeInfo, type CameraOptions, type CameraView, type CanvasElement, type CanvasState, type Command, DEFAULT_NOTE_FONT_SIZE, type DiagonalRule, type DistributeAxis, type ElementActivationEvent, type ElementChangeMeta, type ElementRect, type ElementRectMatch, type ElementRectMatchError, ElementRectTracker, type ElementRectTrackerOptions, ElementStore, type ElementStyle, type ElementType, type ElementUpdateEvent, EraserTool, type EraserToolOptions, type ExportAssetError, type ExportAssetErrorReason, type ExportImageOptions, type ExportResourceOptions, type ExportSvgOptions, FOCUS_PRESENCE_KIND, FOG_MAX_TILES, FOG_STATE_VERSION, FOG_TILE_CELLS, type FocusAudience, type FocusPresence, type FocusRole, type FogBase, type FogChangeEvent, type FogDefinitionV1, type FogIdFactory, FogManager, type FogManagerOptions, type FogOperation, type FogPatch, type FogRegion, FogRenderer, type FogRendererOptions, type FogStateV1, type FogTileV1, FogTool, type FogToolOptions, type FogViewEvent, type FogViewMode, type FontSizePreset, type Footprint, type FrameScheduler, type GridElement, type GridInfo, type GridMetric, HandTool, type HexOrientation, HistoryStack, type HistoryStackOptions, type HitTestOptions, type HtmlElement, type HtmlExportError, type HtmlExportErrorReason, type HtmlExportOptions, type HtmlExportRenderer, type HtmlPaintContext, type HtmlPaintDiagnostic, type HtmlPainter, HtmlPainterMissingError, HtmlPainterRegistry, type HtmlRenderTarget, type HtmlRouting, type ImageElement, ImageTool, type ImageToolOptions, IndexedDBAdapter, type IndexedDBAdapterOptions, LASER_TRAIL_PRESENCE_KIND, LaserTool, type LaserToolOptions, type LaserTrailEmission, type LaserTrailPresence, type Layer, LayerManager, LocalAwareness, type LocalAwarenessHost, type LocalAwarenessOptions, LocalStorageAdapter, MEASURE_PRESENCE_KIND, type MeasureEmission, type MeasurePresence, MeasureTool, type MeasureToolOptions, type Measurement, MemoryAdapter, MinimapController, type MinimapControllerOptions, type NoteElement, NoteTool, type NoteToolOptions, type OverlayRenderer, PATH_PRESENCE_KIND, PATH_PRESENCE_MAX_POINTS, PEER_COLORS, PING_PRESENCE_KIND, type PathAnchor, type PathDistance, type PathEmission, type PathPresence, type PathRangeBand, type PathSegment, PathTool, type PathToolOptions, type Peer, type PeerLeaveReason, PeerRoster, type PeerRosterOptions, PencilTool, type PencilToolOptions, type PingEmission, PingInput, type PingInputHost, type PingInputOptions, type PingPresence, PingTool, type PingToolOptions, type Point, type PointerState, type PresenceChannel, type RectTrackerHost, RemoteCursorOverlay, type RemoteCursorOverlayHost, type RemoteCursorOverlayOptions, RemoteFocusReceiver, type RemoteFocusReceiverHost, type RemoteFocusReceiverOptions, RemoteLaserOverlay, type RemoteLaserOverlayHost, type RemoteLaserOverlayOptions, RemoteMeasureOverlay, type RemoteMeasureOverlayHost, type RemoteMeasureOverlayOptions, RemotePathOverlay, type RemotePathOverlayHost, type RemotePathOverlayOptions, RemotePingOverlay, type RemotePingOverlayHost, type RemotePingOverlayOptions, RemoteSelectionOverlay, type RemoteSelectionOverlayHost, type RemoteSelectionOverlayOptions, type RenderStatsSnapshot, type RotateDirection, SelectTool, type SelectionStyleDetails, type ShapeElement, type ShapeKind, ShapeTool, type ShapeToolOptions, type ShortcutBindings, type ShortcutOptions, type ShortcutsApi, type Size, type StorageAdapter, type StrokeElement, type StrokePoint, type TemplateElement, type TemplateRenderStyle, type TemplateShape, TemplateTool, type TemplateToolOptions, type TextElement, TextTool, type TextToolOptions, type Tool, type ToolContext, ToolManager, type ToolName, VERSION, Viewport, type ViewportOptions, applyCameraView, attachAwareness, boundsIntersect, cameraOriginForView, canonicalizeFogTile, captureCameraView, computeElementRects, createArrow, createGrid, createHtmlElement, createImage, createNote, createShape, createStroke, createTemplate, createText, defaultPeerColor, drawHexPath, elementRectsEqual, exportImage, exportSvg, fitZoomForView, decodeBase64 as fogDecodeBase64, encodeBase64 as fogEncodeBase64, footprintFromSize, getActiveFormats, getArrowBounds, getArrowControlPoint, getArrowMidpoint, getArrowTangentAngle, getBendFromPoint, getElementBounds, getElementStyle, getElementsBoundingBox, getHexCellsInCone, getHexCellsInLine, getHexCellsInRadius, getHexCellsInRectangle, getHexCellsInSquare, getHexDistance, gridDistanceCells, isAwarenessPresence, isFocusPresence, isLaserTrailPresence, isMeasurePresence, isNearBezier, isPathPresence, isPingPresence, pathDistanceCells, recommendedFogCellSize, resolveHtmlRouting, setFontSize, smartSnap, snapFootprintCenter, snapPoint, snapToCellCenter, snapToHexCenter, styleToPatch, toFocusPresence, toLaserTrailPresence, toMeasurePresence, toPathPresence, toPingPresence, toggleBold, toggleItalic, toggleStrikethrough, toggleUnderline, validateFogDefinition, validateFogState, validateFogTile };