@memberjunction/ng-whiteboard 0.0.1 → 5.42.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.
Files changed (62) hide show
  1. package/README.md +302 -28
  2. package/dist/lib/whiteboard-agent-sees-popover.component.d.ts +31 -0
  3. package/dist/lib/whiteboard-agent-sees-popover.component.d.ts.map +1 -0
  4. package/dist/lib/whiteboard-agent-sees-popover.component.js +132 -0
  5. package/dist/lib/whiteboard-agent-sees-popover.component.js.map +1 -0
  6. package/dist/lib/whiteboard-board.component.d.ts +458 -0
  7. package/dist/lib/whiteboard-board.component.d.ts.map +1 -0
  8. package/dist/lib/whiteboard-board.component.js +2357 -0
  9. package/dist/lib/whiteboard-board.component.js.map +1 -0
  10. package/dist/lib/whiteboard-context-menu.d.ts +57 -0
  11. package/dist/lib/whiteboard-context-menu.d.ts.map +1 -0
  12. package/dist/lib/whiteboard-context-menu.js +69 -0
  13. package/dist/lib/whiteboard-context-menu.js.map +1 -0
  14. package/dist/lib/whiteboard-export.d.ts +94 -0
  15. package/dist/lib/whiteboard-export.d.ts.map +1 -0
  16. package/dist/lib/whiteboard-export.js +592 -0
  17. package/dist/lib/whiteboard-export.js.map +1 -0
  18. package/dist/lib/whiteboard-host.component.d.ts +174 -0
  19. package/dist/lib/whiteboard-host.component.d.ts.map +1 -0
  20. package/dist/lib/whiteboard-host.component.js +758 -0
  21. package/dist/lib/whiteboard-host.component.js.map +1 -0
  22. package/dist/lib/whiteboard-pages.component.d.ts +83 -0
  23. package/dist/lib/whiteboard-pages.component.d.ts.map +1 -0
  24. package/dist/lib/whiteboard-pages.component.js +211 -0
  25. package/dist/lib/whiteboard-pages.component.js.map +1 -0
  26. package/dist/lib/whiteboard-snapshot.component.d.ts +30 -0
  27. package/dist/lib/whiteboard-snapshot.component.d.ts.map +1 -0
  28. package/dist/lib/whiteboard-snapshot.component.js +91 -0
  29. package/dist/lib/whiteboard-snapshot.component.js.map +1 -0
  30. package/dist/lib/whiteboard-srcdoc.pipe.d.ts +60 -0
  31. package/dist/lib/whiteboard-srcdoc.pipe.d.ts.map +1 -0
  32. package/dist/lib/whiteboard-srcdoc.pipe.js +75 -0
  33. package/dist/lib/whiteboard-srcdoc.pipe.js.map +1 -0
  34. package/dist/lib/whiteboard-state.d.ts +1113 -0
  35. package/dist/lib/whiteboard-state.d.ts.map +1 -0
  36. package/dist/lib/whiteboard-state.js +1396 -0
  37. package/dist/lib/whiteboard-state.js.map +1 -0
  38. package/dist/lib/whiteboard-toolbar.component.d.ts +95 -0
  39. package/dist/lib/whiteboard-toolbar.component.d.ts.map +1 -0
  40. package/dist/lib/whiteboard-toolbar.component.js +344 -0
  41. package/dist/lib/whiteboard-toolbar.component.js.map +1 -0
  42. package/dist/lib/whiteboard-tools.d.ts +103 -0
  43. package/dist/lib/whiteboard-tools.d.ts.map +1 -0
  44. package/dist/lib/whiteboard-tools.js +748 -0
  45. package/dist/lib/whiteboard-tools.js.map +1 -0
  46. package/dist/lib/whiteboard-widget-bridge.d.ts +217 -0
  47. package/dist/lib/whiteboard-widget-bridge.d.ts.map +1 -0
  48. package/dist/lib/whiteboard-widget-bridge.js +254 -0
  49. package/dist/lib/whiteboard-widget-bridge.js.map +1 -0
  50. package/dist/lib/whiteboard-zoom.component.d.ts +55 -0
  51. package/dist/lib/whiteboard-zoom.component.d.ts.map +1 -0
  52. package/dist/lib/whiteboard-zoom.component.js +139 -0
  53. package/dist/lib/whiteboard-zoom.component.js.map +1 -0
  54. package/dist/lib/whiteboard.module.d.ts +29 -0
  55. package/dist/lib/whiteboard.module.d.ts.map +1 -0
  56. package/dist/lib/whiteboard.module.js +78 -0
  57. package/dist/lib/whiteboard.module.js.map +1 -0
  58. package/dist/public-api.d.ts +49 -0
  59. package/dist/public-api.d.ts.map +1 -0
  60. package/dist/public-api.js +59 -0
  61. package/dist/public-api.js.map +1 -0
  62. package/package.json +43 -6
package/README.md CHANGED
@@ -1,45 +1,319 @@
1
1
  # @memberjunction/ng-whiteboard
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
3
+ A generic, reusable collaborative whiteboard for any Angular application — built for **dual authorship**: a human drawing with direct-manipulation tools, and a programmatic co-author (typically an AI agent) mutating the *same* board through a JSON-described tool API. One state engine, one undo history, one perception feed.
4
4
 
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
5
+ ## Installation
6
6
 
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
7
+ ```bash
8
+ npm install @memberjunction/ng-whiteboard
9
+ ```
8
10
 
9
- ## Purpose
11
+ Peer dependencies: `@angular/common`, `@angular/core`, `@angular/platform-browser`. The package has **no Router dependency**, no MJ metadata/entity dependency, and no AI-framework dependency — it is a pure Generic-component package.
10
12
 
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@memberjunction/ng-whiteboard`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
13
+ ## Overview
15
14
 
16
- ## What is OIDC Trusted Publishing?
15
+ ```mermaid
16
+ flowchart LR
17
+ subgraph Engine["WhiteboardState (Angular-free)"]
18
+ S["Items + selection"]
19
+ U["Undo/redo snapshots"]
20
+ J["Change journal → scene deltas"]
21
+ E["Before/after events"]
22
+ end
23
+ subgraph UI["Components"]
24
+ H["RealtimeWhiteboardHostComponent"]
25
+ B["RealtimeWhiteboardBoardComponent"]
26
+ T["Toolbar / Zoom / Popover"]
27
+ SN["WhiteboardSnapshotComponent (read-only)"]
28
+ end
29
+ subgraph Agent["Programmatic co-author"]
30
+ TD["WHITEBOARD_TOOL_DEFINITIONS"]
31
+ AP["ApplyWhiteboardAgentTool"]
32
+ end
33
+ H --> B
34
+ H --> T
35
+ B --> Engine
36
+ AP --> Engine
37
+ Engine -- "BuildSceneDelta" --> Agent
38
+ Engine -- "ToJSON / LoadFromJSON" --> P["Persistence (your storage)"]
39
+ SN --> Engine
40
+ ```
17
41
 
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
42
+ The package is organized in layers, each usable on its own:
19
43
 
20
- ## Setup Instructions
44
+ | Layer | File(s) | What it is |
45
+ |---|---|---|
46
+ | **State engine** | `whiteboard-state.ts` | `WhiteboardState` — the Angular-free typed board model. The *single* mutation API for both human tools and agent tools. |
47
+ | **Tool API** | `whiteboard-tools.ts` | The `Whiteboard_*` JSON tool definitions + `ApplyWhiteboardAgentTool`, the pure execute-one-call round-trip. |
48
+ | **Components** | `whiteboard-*.component.ts` | Host (full surface), board (canvas), toolbar, zoom cluster, "What the agent sees" popover, read-only snapshot. All standalone. |
49
+ | **Export** | `whiteboard-export.ts` | Pure builders for a self-contained HTML document and a standalone SVG. |
50
+ | **Widget bridge** | `whiteboard-widget-bridge.ts` | The `MJWhiteboard.submit` postMessage contract for sandboxed HTML widgets. |
51
+ | **Context menu** | `whiteboard-context-menu.ts` | The pure right-click menu model (what is offered per item kind). |
21
52
 
22
- To properly configure OIDC trusted publishing for this package:
53
+ ## Core Concepts
23
54
 
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
55
+ ### Items
28
56
 
29
- ## DO NOT USE THIS PACKAGE
57
+ Everything on the board is a `WhiteboardItem` — a discriminated union over `Kind`:
30
58
 
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
59
+ `sticky` · `shape` (rect/ellipse/diamond) · `text` · `image` · `ink` (freehand stroke) · `connector` (item↔item or point↔point arrows) · `highlight` (transient "pointing" region) · `markdown` (rendered rich panel) · `html` (interactive sandboxed widget)
36
60
 
37
- ## More Information
61
+ Every item carries an `Author` of `'user'` or `'agent'`. Authorship drives the ownership chrome — agent items render in a **reserved violet treatment** the user palette never offers, so provenance is always visible.
38
62
 
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
63
+ ### Pages (OneNote-style)
42
64
 
43
- ---
65
+ A board is an **ordered list of named pages**, each with its own items; exactly one page is ACTIVE and *every* item operation (add/update/move/remove/connectors/clear/perception/export) targets the active page. A fresh board starts with one page named **"Page 1"**.
44
66
 
45
- **Maintained for OIDC setup purposes only**
67
+ - `AddPage(name?, author?)` — creates **and switches to** a new page; omit the name and it auto-names "Page N" from a monotonic counter (auto-names never repeat after removals).
68
+ - `SwitchPage(idOrName, author?)` — tolerant lookup: exact page ID first, then case-insensitive trimmed name. Switching journals a `'replace'` (the agent's visible scene swaps wholesale) but is **not** an undo step — it's navigation.
69
+ - `RenamePage(idOrName, newName, author?)` / `RemovePage(idOrName, author?)` — the **last remaining page can never be removed**; removing the *active* page activates a neighbor (the next page, else the previous).
70
+ - Reads: `Pages` (snapshots: `{ ID, Name, ItemCount, Active }`), `ActivePageID`, `ActivePageName`, `TotalItemCount` (across pages), `FindPage(idOrName)`.
71
+
72
+ Each page operation raises the package's standard **cancelable before / after event pair**: `PageAdding$`/`PageAdded$`, `PageSwitching$`/`PageSwitched$`, `PageRenaming$`/`PageRenamed$`, `PageRemoving$`/`PageRemoved$`. The board surface renders a compact bottom-left **page strip** (`RealtimeWhiteboardPagesComponent`): click switches, double-click renames inline (Enter commits, Esc cancels), hover "×" deletes, "+" adds. In `ReadOnly` mode the chips still switch pages (navigation) but add/rename/delete disappear.
73
+
74
+ ### The engine
75
+
76
+ `WhiteboardState` owns items, single selection, and Z-order. Callers never stamp identity — `AddItem` assigns `ID`/`Z`/`Author`. Undo/redo is snapshot-based: one entry per user gesture, or one per agent tool call via `RunBatch` (so one toast-Undo reverts a whole tool effect).
77
+
78
+ ### Journal & perception deltas
79
+
80
+ Every mutation appends to a compact change journal and emits on `Changed$`. A programmatic co-author "sees" the board through:
81
+
82
+ - **`BuildSceneDelta(sinceToken)`** — coalesces everything since a previously observed `CurrentSeq` into ONE delta with *replace-current-state* semantics: N moves of one item → one `moved` entry at the current position; add+remove → nothing; undo/redo/load/page-switch (or a too-old token) → `reset: true` plus the full compact scene. Never an append-only log.
83
+ - **`BuildSceneSummary()`** — the full compact scene + per-kind counts (what the "What the agent sees" popover renders).
84
+
85
+ Both payloads describe the **active page's** items and carry a `pages` array (`{ id, name, active, items }`) plus a summary line naming the active page — so the agent always knows pages exist and which one it is looking at.
86
+
87
+ ### Persistence
88
+
89
+ `ToJSON()` serializes the state of record; `WhiteboardState.FromJSON(json)` rehydrates a new engine (throws on malformed input); `LoadFromJSON(json)` rehydrates **in place** — tolerant (returns `false`, never throws), preserves existing subscriptions, clears undo/journal, and emits one `'replace'` change. `ParseBoardStateJson` (from the snapshot component file) is the tolerant parse-or-null helper for viewers.
90
+
91
+ The serialized shape is **version 2 (paged)**:
92
+
93
+ ```jsonc
94
+ {
95
+ "version": 2,
96
+ "seq": 12, "idCounter": 5, "zCounter": 5,
97
+ "pageCounter": 2, // monotonic — mints page ids / "Page N" auto-names
98
+ "activePageId": "page-2",
99
+ "pages": [
100
+ { "id": "page-1", "name": "Page 1", "items": [ /* WhiteboardItem[] */ ] },
101
+ { "id": "page-2", "name": "Drafts", "items": [ /* … */ ] }
102
+ ]
103
+ }
104
+ ```
105
+
106
+ **Backward compatibility:** the legacy flat shape (`version: 1` — `items` at the root, no pages) is still accepted by `FromJSON` / `LoadFromJSON` / `ParseBoardStateJson` and migrates to a single page named "Page 1". Serialization always emits the paged shape.
107
+
108
+ ## Quickstart
109
+
110
+ ### Full surface — the host component
111
+
112
+ The host renders the complete experience: header (title, saved chip, ownership legend, "What [Agent] sees" popover, Focus toggle, export menu), the canvas with floating toolbar, page strip + zoom cluster, the agent-action toast with Undo, and the status footer.
113
+
114
+ The zoom cluster supports **hold-to-zoom**: a plain click on + / − steps through the usual presets, while holding the button down zooms continuously in small smooth increments (~3.5% every 50 ms) until release — same 25%–200% clamp.
115
+
116
+ ```typescript
117
+ import { Component } from '@angular/core';
118
+ import { RealtimeWhiteboardHostComponent, WhiteboardState } from '@memberjunction/ng-whiteboard';
119
+
120
+ @Component({
121
+ standalone: true,
122
+ imports: [RealtimeWhiteboardHostComponent],
123
+ template: `
124
+ <mj-realtime-whiteboard-host
125
+ [State]="Board"
126
+ [AgentName]="'Sage'"
127
+ [BoardTitle]="'Planning board'"
128
+ (SceneDelta)="onSceneDelta($event)"
129
+ (WidgetSubmitted)="onWidgetInput($event)"
130
+ (SaveToArtifactsRequested)="saveBoard()" />
131
+ `
132
+ })
133
+ export class MyPageComponent {
134
+ /** You own the engine — create it, persist it, hand it to the surface. */
135
+ public readonly Board = new WhiteboardState();
136
+
137
+ onSceneDelta(deltaJson: string): void {
138
+ // debounced (750 ms), coalesced — feed it to your agent's live context
139
+ }
140
+ onWidgetInput(e: { ItemID: string; Title: string; DataJson: string }): void {
141
+ // a sandboxed widget called MJWhiteboard.submit(data)
142
+ }
143
+ saveBoard(): void {
144
+ const json = this.Board.ToJSON(); // persist wherever you like
145
+ }
146
+ }
147
+ ```
148
+
149
+ ### Standalone board — just the canvas
150
+
151
+ `RealtimeWhiteboardBoardComponent` (`<mj-realtime-whiteboard>`) is the canvas alone — pan/zoom, per-tool pointer interactions, inline editing, context menu. You supply the tool state (the host normally does this):
152
+
153
+ ```html
154
+ <mj-realtime-whiteboard
155
+ [State]="Board"
156
+ [Tool]="'select'"
157
+ [AgentName]="'Sage'"
158
+ [ReadOnly]="false" />
159
+ ```
160
+
161
+ ### Read-only snapshot
162
+
163
+ `WhiteboardSnapshotComponent` (`<mj-whiteboard-snapshot>`) renders a persisted payload through the real board in `ReadOnly` mode — identical visuals, pan/zoom for navigation, every mutation disabled:
164
+
165
+ ```html
166
+ <mj-whiteboard-snapshot [StateJson]="savedBoardJson" [AgentName]="'Sage'" />
167
+ ```
168
+
169
+ ### NgModule apps
170
+
171
+ `WhiteboardModule` re-exports all the standalone components for module-organized apps. There is also a `LoadWhiteboardComponents()` no-op for the rare all-dynamic-resolution bundling case.
172
+
173
+ ## The Programmatic Tool API (agent integration recipe)
174
+
175
+ The agent-facing surface is intentionally transport-agnostic — it is just *names, JSON schemas, and one pure function*:
176
+
177
+ 1. **Register the tools.** `WHITEBOARD_TOOL_DEFINITIONS` is an array of `{ Name, Description, ParametersSchema }` (structurally identical to `RealtimeToolDefinition` from `@memberjunction/ai`, so it drops straight into MJ realtime sessions — but any function-calling runtime works). All names share the `WHITEBOARD_TOOL_PREFIX` (`Whiteboard_`), so a single prefix route covers the set.
178
+ 2. **Route tool calls back.** When your runtime receives a `Whiteboard_*` call, execute it locally:
179
+
180
+ ```typescript
181
+ import { ApplyWhiteboardAgentTool } from '@memberjunction/ng-whiteboard';
182
+
183
+ const resultJson = ApplyWhiteboardAgentTool(state, toolName, argsJson);
184
+ // → feed resultJson back to the model as the tool response
185
+ ```
186
+
187
+ `ApplyWhiteboardAgentTool` never throws — malformed args, unknown tools, unknown item IDs, and host-canceled operations all return `{ success: false, error }` so the model can self-correct conversationally. Each call runs as ONE undo batch (author `'agent'`).
188
+
189
+ Prefer the host's `ApplyAgentTool(toolName, argsJson)` when a surface is bound — same round-trip plus the UI garnish (violet pop-in, action toast with Undo, gliding presence cursor).
190
+ 3. **Feed perception.** Subscribe the host's `SceneDelta` output (debounced 750 ms, coalesced) and pipe each JSON delta into the agent's context as background information. Use `CurrentSeq` / `BuildSceneDelta(sinceToken)` directly if you manage your own cadence.
191
+
192
+ The tool set: `AddNote`, `AddShape`, `AddText`, `AddMarkdown`, `AddHtml`, `UpdateContent`, `DrawConnector`, `Highlight`, `MoveItem`, `RemoveItem`, `StyleItem`, plus the page tools `AddPage` (`{ name? }` — create + switch), `SwitchPage` (`{ name }` — page name, case-insensitive, or id) and `RenamePage` (`{ name, newName }`) — see `WHITEBOARD_TOOL_NAMES` and the per-tool schemas in `whiteboard-tools.ts`. Item tools always target the **active page**; page-tool results carry `pageId` instead of `itemId`. Content sizes are capped (`WHITEBOARD_MARKDOWN_MAX_CHARS` = 32 000, `WHITEBOARD_HTML_MAX_CHARS` = 64 000).
193
+
194
+ > **Reference integration:** `@memberjunction/ng-conversations` wires this package into MJ realtime voice sessions as a pluggable channel (`RealtimeWhiteboardChannel`) and ships a saved-board artifact viewer — both are thin consumers of the APIs above.
195
+
196
+ ## Before / After Events
197
+
198
+ Every major mutation raises a **cancelable BEFORE** event and a matching **AFTER** event. Handlers run synchronously during the emit; setting `Cancel = true` on the args aborts the operation cleanly — no undo snapshot, no journal entry, no `Changed$` emission, and the AFTER event never fires. These events layer *alongside* the existing `Changed$`/journal/perception machinery.
199
+
200
+ ### Engine level (`WhiteboardState` observables)
201
+
202
+ | Before (cancelable) | After | Raised by | Args highlights |
203
+ |---|---|---|---|
204
+ | `ItemAdding$` | `ItemAdded$` | `AddItem` (incl. `Highlight`, `DuplicateItem`) | `Input` (mutable pre-stamp item), `Author` |
205
+ | `ItemUpdating$` | `ItemUpdated$` | `UpdateItem` (`Operation: 'update'`, mutable `Patch`), `MoveItem` (`'move'`, `Position`), `BringToFront`/`SendToBack` (`'reorder'`) | `Item` (live), `Operation`, `Author` |
206
+ | `ItemRemoving$` | `ItemRemoved$` | `RemoveItem` | `Item`, `Author` |
207
+ | `PageAdding$` | `PageAdded$` | `AddPage` | mutable `Name`, `Author` |
208
+ | `PageSwitching$` | `PageSwitched$` | `SwitchPage` | `FromPage`, `ToPage`, `Author` |
209
+ | `PageRenaming$` | `PageRenamed$` | `RenamePage` | `Page`, mutable `NewName`, `Author` |
210
+ | `PageRemoving$` | `PageRemoved$` | `RemovePage` | `Page` (+ `ActivatedPage` on the after event), `Author` |
211
+
212
+ Cancellation surfaces to callers: `AddItem` returns `null`; `UpdateItem`/`MoveItem`/`RemoveItem`/reorders return `false`; agent tools return a `{ success: false, error: '… canceled …' }` payload. BEFORE handlers may also *rewrite* the operation (`Input` on adds, `Patch` on updates) — moderation and clamping without cancel-and-replay.
213
+
214
+ ```typescript
215
+ // Example: block the agent from removing the user's items
216
+ board.ItemRemoving$.subscribe((e) => {
217
+ if (e.Author === 'agent' && e.Item.Author === 'user') {
218
+ e.Cancel = true;
219
+ }
220
+ });
221
+ ```
222
+
223
+ ### Component level
224
+
225
+ `RealtimeWhiteboardBoardComponent` adds two pairs of `@Output`s of its own:
226
+
227
+ | Before (cancelable) | After | Fired when |
228
+ |---|---|---|
229
+ | `ContentApplying` | `ContentApplied` | An in-board editor commit — inline sticky/text/shape edit, or the markdown/HTML rich editor's Apply/Done — is about to write to the engine. Args: `ItemID`, `Kind`, mutable `Content` (+ `Title` for HTML widgets). |
230
+ | `WidgetSubmitting` | `WidgetSubmitted` | A sandboxed widget's `MJWhiteboard.submit(data)` passed validation. Cancel to drop the submission. |
231
+
232
+ `RealtimeWhiteboardHostComponent` **mirrors** all engine pairs as outputs (`ItemAdding`/`ItemAdded`, `ItemUpdating`/`ItemUpdated`, `ItemRemoving`/`ItemRemoved`) and **forwards** the board pairs — so template-driven consumers get the entire surface in one place. Mirrors re-emit synchronously, so `Cancel = true` set in a template handler still vetoes the engine mutation.
233
+
234
+ ```html
235
+ <mj-realtime-whiteboard-host
236
+ [State]="Board"
237
+ (ItemAdding)="$event.Cancel = !allowAdds"
238
+ (ItemAdded)="audit('add', $event.Item)"
239
+ (ContentApplying)="moderate($event)"
240
+ (WidgetSubmitted)="forwardToAgent($event)" />
241
+ ```
242
+
243
+ ## Sandboxed HTML Widgets & the Input Bridge
244
+
245
+ HTML widgets are self-contained documents (inline CSS/JS) rendered inside an iframe whose `sandbox` attribute is **`allow-scripts` only**:
246
+
247
+ - The frame runs in a unique **opaque origin** — its scripts cannot reach the parent document, the app session, cookies, or any storage. `allow-same-origin` is deliberately ruled out (it would let the frame script remove its own sandbox). By design there is **no DOM sanitization inside the sandbox** — the sandbox is the boundary, so SVG/script-heavy widget documents pass through byte-for-byte.
248
+ - Angular's sanitizer is bypassed *only* for the iframe `srcdoc` (the payload never touches the app's DOM). The trusted value is produced by the view-scoped `wbWidgetSrcdoc` pure pipe (`WhiteboardWidgetSrcdocPipe`), which fixes the lifecycle contract:
249
+ - **Widget documents are rebuilt per mount.** Every time a frame is (re)created — switching whiteboard pages away and back, the viewport-lazy toggle, panel collapse/expand, whole-board re-creation — the new view gets a new pipe instance and a freshly built document, so a re-mounted widget always renders exactly what the first mount rendered. (Do not reintroduce a component-level per-item `SafeHtml` cache: a stale instance handed to a re-created frame means no `srcdoc` write and a blank widget.)
250
+ - **Still-mounted widgets never reload on unrelated board activity.** For an unchanged `Html` source the pipe returns the identical `SafeHtml` instance, so whole-scene `'replace'` ops (page rename, undo/redo of other items, `LoadFromJSON` restores) cause no `srcdoc` rewrite and no iframe reload — widget runtime state survives. Only an actual content edit reloads the frame.
251
+ - Off-screen widgets render as placeholders — live frames are only instantiated near the viewport. Re-entering the viewport re-creates the frame from a freshly built document (internal widget state resets — acceptable for board decorations).
252
+
253
+ The only way data flows out is `postMessage`, governed by the **input bridge**:
254
+
255
+ 1. `InjectWhiteboardSubmitHelper` prepends (idempotently) a tiny helper defining `window.MJWhiteboard.submit(data)` to every widget's srcdoc.
256
+ 2. The board owns a single `window` `'message'` listener and validates each event with `EvaluateWidgetSubmitMessage`: the bridge marker must be present, `event.source` must resolve to a *tracked* widget iframe (anything else is dropped), and the JSON-serialized payload is capped at `WHITEBOARD_WIDGET_SUBMIT_MAX_CHARS` (8 000 chars).
257
+ 3. Accepted submissions flow through `WidgetSubmitting` (cancelable) → `WidgetSubmitted`.
258
+
259
+ Markdown panels are the *non-executing* sibling: rendered through the shared `mj-markdown` component on the live board (sanitized, no raw-HTML passthrough) and through an inert escape-first renderer in exports.
260
+
261
+ ## Export & Print
262
+
263
+ `whiteboard-export.ts` provides pure, deterministic builders (all user/agent text HTML-escaped). Exports render the **active page** — switch pages first to export another one:
264
+
265
+ - **`BuildWhiteboardExportHtml(state, { Title, AgentName, GeneratedAt })`** — ONE fully self-contained HTML document: inline CSS only, light paper palette, ownership chips, print-friendly `@media print` rules. HTML widgets export as placeholder cards with their escaped source in a `<details>` — live widget HTML is **never** inlined (no iframe sandbox exists in the export, so it would be XSS).
266
+ - **`BuildWhiteboardExportSvg(state)`** — a standalone SVG document of the same snapshot.
267
+
268
+ The host's export menu wires these to **Download HTML / Download SVG / Print** (print opens a blank window, writes the document, and invokes the dialog), plus a `SaveToArtifactsRequested` output for integrations that persist boards as first-class artifacts.
269
+
270
+ > The export palette is intentionally literal (not design tokens): self-contained documents must stand alone. This is the documented exception to the token rule.
271
+
272
+ ## Theming
273
+
274
+ The live components style exclusively with MJ design tokens (`--mj-text-*`, `--mj-bg-surface-*`, `--mj-border-*`, …), so the board adapts to light/dark themes and white-labeling automatically. Two deliberate accents ride on top of the tokens:
275
+
276
+ - **Violet is the agent's** — agent stickies/ink/connectors/chips use the reserved violet treatment; the user palette (`WHITEBOARD_PEN_COLORS`) never offers it.
277
+ - **Amber is the user's sticky default** (two alternating tints).
278
+
279
+ Icons are Font Awesome throughout.
280
+
281
+ ## Extension Points
282
+
283
+ - **Before/after events** — veto or rewrite any mutation, audit changes, forward submissions (see above). This is the primary extensibility seam.
284
+ - **Your own surface** — the engine is Angular-free; build alternate renderers or headless automations against `WhiteboardState` directly and they share undo/journal/perception with everything else.
285
+ - **Custom tool routing** — `ApplyWhiteboardAgentTool` is pure; wrap it to pre-process args, post-process results, or expose a different tool vocabulary that lowers onto the same engine calls.
286
+ - **Context menu model** — `BuildWhiteboardContextMenu(item)` is a pure model builder; reuse it in your own chrome if you replace the board's menu rendering.
287
+ - **Snapshot/viewer reuse** — `ParseBoardStateJson` + `WhiteboardSnapshotComponent` make read-only board rendering a two-liner in any review/preview surface.
288
+
289
+ ## Public API Surface (selected)
290
+
291
+ ```typescript
292
+ // Engine
293
+ WhiteboardState, WhiteboardItem (union), WhiteboardItemInput, WhiteboardItemPatch,
294
+ WhiteboardChange, WhiteboardSceneDelta, WhiteboardSceneSummary, WHITEBOARD_DEFAULTS,
295
+ WhiteboardItemAddingEventArgs / Added / Updating / Updated / Removing / Removed,
296
+ WhiteboardPageInfo, WhiteboardScenePage,
297
+ WhiteboardPageAddingEventArgs / Added / Switching / Switched / Renaming / Renamed / Removing / Removed
298
+
299
+ // Tools
300
+ WHITEBOARD_TOOL_DEFINITIONS, WHITEBOARD_TOOL_NAMES, WHITEBOARD_TOOL_PREFIX,
301
+ WhiteboardToolDefinition, WhiteboardToolResult, ApplyWhiteboardAgentTool
302
+
303
+ // Components
304
+ RealtimeWhiteboardHostComponent, RealtimeWhiteboardBoardComponent,
305
+ RealtimeWhiteboardToolbarComponent, RealtimeWhiteboardZoomComponent,
306
+ RealtimeWhiteboardPagesComponent, RealtimeWhiteboardAgentSeesPopoverComponent,
307
+ WhiteboardSnapshotComponent, WhiteboardModule
308
+
309
+ // Export / bridge / menu
310
+ BuildWhiteboardExportHtml, BuildWhiteboardExportSvg, RenderMarkdownInert,
311
+ InjectWhiteboardSubmitHelper, EvaluateWidgetSubmitMessage, ParseBoardStateJson,
312
+ BuildWhiteboardContextMenu
313
+ ```
314
+
315
+ Everything exported carries full JSDoc — IDE hover docs are the authoritative per-member reference.
316
+
317
+ ## Conventions
318
+
319
+ This package follows the MemberJunction Generic-component rules: PascalCase public members (inputs/outputs/methods), camelCase private members, standalone components with modern `@if`/`@for` templates, `inject()` DI, no Router imports, and design-token-only styling.
@@ -0,0 +1,31 @@
1
+ import * as i0 from "@angular/core";
2
+ /**
3
+ * The "What [Agent] sees" perception popover: shows the most recent coalesced scene
4
+ * delta JSON (syntax-tinted) plus the perception-feed stats footer (debounce, element
5
+ * count, last-delta age, state of record). Anchored under the header button by the host;
6
+ * visibility is the host's concern.
7
+ */
8
+ export declare class RealtimeWhiteboardAgentSeesPopoverComponent {
9
+ /** Display name of the session's agent. */
10
+ AgentName: string;
11
+ /** Perception debounce, in ms (the host's SceneDelta debounce). */
12
+ DebounceMs: number;
13
+ /** Current element count on the board. */
14
+ ElementCount: number;
15
+ /** Friendly age of the last emitted delta ("2 s ago" / "—"). */
16
+ LastDeltaLabel: string;
17
+ /** The latest scene-delta JSON (pretty-printed). Setter pre-tints for display. */
18
+ set DeltaJson(value: string);
19
+ get DeltaJson(): string;
20
+ private _deltaJson;
21
+ /** HTML-escaped JSON wrapped in token-tint spans (keys/strings/numbers/comments). */
22
+ TintedHtml: string;
23
+ /**
24
+ * Minimal JSON syntax tinting matching the mockup's `.codeblock` token classes.
25
+ * Input is fully HTML-escaped first, so the produced markup contains only our spans.
26
+ */
27
+ private static tint;
28
+ static ɵfac: i0.ɵɵFactoryDeclaration<RealtimeWhiteboardAgentSeesPopoverComponent, never>;
29
+ static ɵcmp: i0.ɵɵComponentDeclaration<RealtimeWhiteboardAgentSeesPopoverComponent, "mj-realtime-whiteboard-agent-sees-popover", never, { "AgentName": { "alias": "AgentName"; "required": false; }; "DebounceMs": { "alias": "DebounceMs"; "required": false; }; "ElementCount": { "alias": "ElementCount"; "required": false; }; "LastDeltaLabel": { "alias": "LastDeltaLabel"; "required": false; }; "DeltaJson": { "alias": "DeltaJson"; "required": false; }; }, {}, never, never, true, never>;
30
+ }
31
+ //# sourceMappingURL=whiteboard-agent-sees-popover.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whiteboard-agent-sees-popover.component.d.ts","sourceRoot":"","sources":["../../src/lib/whiteboard-agent-sees-popover.component.ts"],"names":[],"mappings":";AAGA;;;;;GAKG;AACH,qBAOa,2CAA2C;IACtD,2CAA2C;IAClC,SAAS,SAAW;IAC7B,mEAAmE;IAC1D,UAAU,SAAO;IAC1B,0CAA0C;IACjC,YAAY,SAAK;IAC1B,gEAAgE;IACvD,cAAc,SAAO;IAE9B,kFAAkF;IAClF,IACI,SAAS,CAAC,KAAK,EAAE,MAAM,EAK1B;IACD,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,OAAO,CAAC,UAAU,CAAM;IAExB,qFAAqF;IAC9E,UAAU,SAAM;IAEvB;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,IAAI;yCA/BR,2CAA2C;2CAA3C,2CAA2C;CA2CvD"}
@@ -0,0 +1,132 @@
1
+ import { Component, Input } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from "@angular/core";
4
+ function RealtimeWhiteboardAgentSeesPopoverComponent_Conditional_9_Template(rf, ctx) { if (rf & 1) {
5
+ i0.ɵɵdomElementStart(0, "pre", 6);
6
+ i0.ɵɵdomElement(1, "span", 8);
7
+ i0.ɵɵtext(2, "\n");
8
+ i0.ɵɵdomElementStart(3, "span", 9);
9
+ i0.ɵɵtext(4, "// replace-current-state semantics \u2014 not an append-only log");
10
+ i0.ɵɵdomElementEnd()();
11
+ } if (rf & 2) {
12
+ const ctx_r0 = i0.ɵɵnextContext();
13
+ i0.ɵɵadvance();
14
+ i0.ɵɵdomProperty("innerHTML", ctx_r0.TintedHtml, i0.ɵɵsanitizeHtml);
15
+ } }
16
+ function RealtimeWhiteboardAgentSeesPopoverComponent_Conditional_10_Template(rf, ctx) { if (rf & 1) {
17
+ i0.ɵɵdomElementStart(0, "pre", 6)(1, "span", 9);
18
+ i0.ɵɵtext(2, "// no deltas yet \u2014 start sketching");
19
+ i0.ɵɵdomElementEnd()();
20
+ } }
21
+ /**
22
+ * The "What [Agent] sees" perception popover: shows the most recent coalesced scene
23
+ * delta JSON (syntax-tinted) plus the perception-feed stats footer (debounce, element
24
+ * count, last-delta age, state of record). Anchored under the header button by the host;
25
+ * visibility is the host's concern.
26
+ */
27
+ export class RealtimeWhiteboardAgentSeesPopoverComponent {
28
+ constructor() {
29
+ /** Display name of the session's agent. */
30
+ this.AgentName = 'Agent';
31
+ /** Perception debounce, in ms (the host's SceneDelta debounce). */
32
+ this.DebounceMs = 750;
33
+ /** Current element count on the board. */
34
+ this.ElementCount = 0;
35
+ /** Friendly age of the last emitted delta ("2 s ago" / "—"). */
36
+ this.LastDeltaLabel = '—';
37
+ this._deltaJson = '';
38
+ /** HTML-escaped JSON wrapped in token-tint spans (keys/strings/numbers/comments). */
39
+ this.TintedHtml = '';
40
+ }
41
+ /** The latest scene-delta JSON (pretty-printed). Setter pre-tints for display. */
42
+ set DeltaJson(value) {
43
+ if (value !== this._deltaJson) {
44
+ this._deltaJson = value;
45
+ this.TintedHtml = RealtimeWhiteboardAgentSeesPopoverComponent.tint(value);
46
+ }
47
+ }
48
+ get DeltaJson() {
49
+ return this._deltaJson;
50
+ }
51
+ /**
52
+ * Minimal JSON syntax tinting matching the mockup's `.codeblock` token classes.
53
+ * Input is fully HTML-escaped first, so the produced markup contains only our spans.
54
+ */
55
+ static tint(json) {
56
+ const escaped = json
57
+ .replace(/&/g, '&amp;')
58
+ .replace(/</g, '&lt;')
59
+ .replace(/>/g, '&gt;');
60
+ return escaped
61
+ .replace(/"([^"]+)"(\s*):/g, '<span class="ck">"$1"</span>$2:')
62
+ .replace(/:(\s*)"([^"]*)"/g, ':$1<span class="cs">"$2"</span>')
63
+ .replace(/:(\s*)(-?\d+(?:\.\d+)?)/g, ':$1<span class="cn">$2</span>')
64
+ .replace(/(\[\s*)"([^"]*)"/g, '$1<span class="cs">"$2"</span>')
65
+ .replace(/(,\s*)"([^"]*)"(\s*[,\]])/g, '$1<span class="cs">"$2"</span>$3');
66
+ }
67
+ static { this.ɵfac = function RealtimeWhiteboardAgentSeesPopoverComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || RealtimeWhiteboardAgentSeesPopoverComponent)(); }; }
68
+ static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: RealtimeWhiteboardAgentSeesPopoverComponent, selectors: [["mj-realtime-whiteboard-agent-sees-popover"]], inputs: { AgentName: "AgentName", DebounceMs: "DebounceMs", ElementCount: "ElementCount", LastDeltaLabel: "LastDeltaLabel", DeltaJson: "DeltaJson" }, decls: 28, vars: 6, consts: [[1, "sees-pop", 3, "click"], [1, "sees-pop__head"], [1, "fa-solid", "fa-feather"], [1, "pill", "pill--sage"], [1, "sees-pop__body"], [1, "sees-pop__cap"], [1, "codeblock"], [1, "sees-pop__foot"], [3, "innerHTML"], [1, "cc"]], template: function RealtimeWhiteboardAgentSeesPopoverComponent_Template(rf, ctx) { if (rf & 1) {
69
+ i0.ɵɵdomElementStart(0, "div", 0);
70
+ i0.ɵɵdomListener("click", function RealtimeWhiteboardAgentSeesPopoverComponent_Template_div_click_0_listener($event) { return $event.stopPropagation(); });
71
+ i0.ɵɵdomElementStart(1, "div", 1);
72
+ i0.ɵɵdomElement(2, "i", 2);
73
+ i0.ɵɵtext(3);
74
+ i0.ɵɵdomElementStart(4, "span", 3);
75
+ i0.ɵɵtext(5, "coalesced delta");
76
+ i0.ɵɵdomElementEnd()();
77
+ i0.ɵɵdomElementStart(6, "div", 4)(7, "p", 5);
78
+ i0.ɵɵtext(8);
79
+ i0.ɵɵdomElementEnd();
80
+ i0.ɵɵconditionalCreate(9, RealtimeWhiteboardAgentSeesPopoverComponent_Conditional_9_Template, 5, 1, "pre", 6)(10, RealtimeWhiteboardAgentSeesPopoverComponent_Conditional_10_Template, 3, 0, "pre", 6);
81
+ i0.ɵɵdomElementEnd();
82
+ i0.ɵɵdomElementStart(11, "div", 7)(12, "span");
83
+ i0.ɵɵtext(13, "debounce ");
84
+ i0.ɵɵdomElementStart(14, "b");
85
+ i0.ɵɵtext(15);
86
+ i0.ɵɵdomElementEnd()();
87
+ i0.ɵɵdomElementStart(16, "span");
88
+ i0.ɵɵtext(17, "elements ");
89
+ i0.ɵɵdomElementStart(18, "b");
90
+ i0.ɵɵtext(19);
91
+ i0.ɵɵdomElementEnd()();
92
+ i0.ɵɵdomElementStart(20, "span");
93
+ i0.ɵɵtext(21, "last delta ");
94
+ i0.ɵɵdomElementStart(22, "b");
95
+ i0.ɵɵtext(23);
96
+ i0.ɵɵdomElementEnd()();
97
+ i0.ɵɵdomElementStart(24, "span");
98
+ i0.ɵɵtext(25, "state of record ");
99
+ i0.ɵɵdomElementStart(26, "b");
100
+ i0.ɵɵtext(27, "session artifact");
101
+ i0.ɵɵdomElementEnd()()()();
102
+ } if (rf & 2) {
103
+ i0.ɵɵadvance(3);
104
+ i0.ɵɵtextInterpolate1(" Live perception feed \u2192 ", ctx.AgentName, "'s context ");
105
+ i0.ɵɵadvance(5);
106
+ i0.ɵɵtextInterpolate1(" ", ctx.AgentName, " doesn't see pixels \u2014 every change you make is serialized into a compact, debounced scene delta and fed into the live agent context. This is the most recent one: ");
107
+ i0.ɵɵadvance();
108
+ i0.ɵɵconditional(ctx.TintedHtml ? 9 : 10);
109
+ i0.ɵɵadvance(6);
110
+ i0.ɵɵtextInterpolate1("", ctx.DebounceMs, " ms");
111
+ i0.ɵɵadvance(4);
112
+ i0.ɵɵtextInterpolate(ctx.ElementCount);
113
+ i0.ɵɵadvance(4);
114
+ i0.ɵɵtextInterpolate(ctx.LastDeltaLabel);
115
+ } }, dependencies: [CommonModule], styles: ["\n\n\n\n[_nghost-%COMP%] {\n \n\n --sage: var(--mj-color-violet-400, #a78bfa);\n --sage-bg: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 14%, transparent);\n --sage-border: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 45%, transparent);\n\n display: block;\n position: absolute;\n top: calc(100% + 10px);\n right: 0;\n z-index: 60;\n width: 420px;\n cursor: default;\n text-align: left;\n}\n\n.sees-pop[_ngcontent-%COMP%] {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--sage-border);\n border-radius: var(--mj-radius-lg, 12px);\n box-shadow: var(--mj-shadow-2xl, 0 25px 50px -12px rgba(0, 0, 0, 0.45));\n animation: _ngcontent-%COMP%_wb-pop-fade 160ms ease;\n}\n@keyframes _ngcontent-%COMP%_wb-pop-fade {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.sees-pop__head[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 11px 14px;\n border-bottom: 1px solid var(--mj-border-default);\n font-size: 12px;\n font-weight: 700;\n color: var(--mj-color-violet-300, var(--mj-text-primary));\n}\n\n.pill[_ngcontent-%COMP%] {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 2px 9px;\n border-radius: var(--mj-radius-full, 9999px);\n font-size: 9.5px;\n font-weight: 700;\n border: 1px solid transparent;\n line-height: 1.6;\n white-space: nowrap;\n}\n.pill--sage[_ngcontent-%COMP%] {\n background: var(--sage-bg);\n color: var(--mj-color-violet-300, var(--mj-text-secondary));\n border-color: var(--sage-border);\n}\n\n.sees-pop__body[_ngcontent-%COMP%] {\n padding: 12px 14px;\n}\n.sees-pop__cap[_ngcontent-%COMP%] {\n font-size: 11.5px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n margin: 0 0 10px;\n font-weight: 400;\n}\n\n.codeblock[_ngcontent-%COMP%] {\n margin: 0;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface-sunken);\n font-family: var(--mj-font-mono, ui-monospace, monospace);\n font-size: 10.5px;\n line-height: 1.55;\n color: var(--mj-text-secondary);\n max-height: 190px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n\n\n\n.codeblock[_ngcontent-%COMP%] .ck { color: #9cdcfe; }\n.codeblock[_ngcontent-%COMP%] .cs { color: #ce9178; }\n.codeblock[_ngcontent-%COMP%] .cn { color: #b5cea8; }\n.codeblock[_ngcontent-%COMP%] .cc[_ngcontent-%COMP%], \n.codeblock[_ngcontent-%COMP%] .cc { color: #6a9955; }\n\n.sees-pop__foot[_ngcontent-%COMP%] {\n display: flex;\n gap: 14px;\n padding: 9px 14px;\n border-top: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n font-size: 10.5px;\n color: var(--mj-text-muted);\n font-weight: 500;\n flex-wrap: wrap;\n}\n.sees-pop__foot[_ngcontent-%COMP%] b[_ngcontent-%COMP%] {\n color: var(--mj-text-secondary);\n}"] }); }
116
+ }
117
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(RealtimeWhiteboardAgentSeesPopoverComponent, [{
118
+ type: Component,
119
+ args: [{ standalone: true, selector: 'mj-realtime-whiteboard-agent-sees-popover', imports: [CommonModule], template: "<div class=\"sees-pop\" (click)=\"$event.stopPropagation()\">\n <div class=\"sees-pop__head\">\n <i class=\"fa-solid fa-feather\"></i> Live perception feed \u2192 {{ AgentName }}'s context\n <span class=\"pill pill--sage\">coalesced delta</span>\n </div>\n <div class=\"sees-pop__body\">\n <p class=\"sees-pop__cap\">\n {{ AgentName }} doesn't see pixels \u2014 every change you make is serialized into a compact,\n debounced scene delta and fed into the live agent context. This is the most recent one:\n </p>\n @if (TintedHtml) {\n <pre class=\"codeblock\"><span [innerHTML]=\"TintedHtml\"></span>\n<span class=\"cc\">// replace-current-state semantics \u2014 not an append-only log</span></pre>\n } @else {\n <pre class=\"codeblock\"><span class=\"cc\">// no deltas yet \u2014 start sketching</span></pre>\n }\n </div>\n <div class=\"sees-pop__foot\">\n <span>debounce <b>{{ DebounceMs }} ms</b></span>\n <span>elements <b>{{ ElementCount }}</b></span>\n <span>last delta <b>{{ LastDeltaLabel }}</b></span>\n <span>state of record <b>session artifact</b></span>\n </div>\n</div>\n", styles: ["/* \"What the agent sees\" perception popover \u2014 ported pixel-faithfully from\n plans/realtime/mockups/whiteboard.html (.sees-pop) + mockups.css (.codeblock, .pill--sage). */\n\n:host {\n /* agent (violet) accent \u2014 derived the same way as the other realtime components */\n --sage: var(--mj-color-violet-400, #a78bfa);\n --sage-bg: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 14%, transparent);\n --sage-border: color-mix(in srgb, var(--mj-color-violet-400, #a78bfa) 45%, transparent);\n\n display: block;\n position: absolute;\n top: calc(100% + 10px);\n right: 0;\n z-index: 60;\n width: 420px;\n cursor: default;\n text-align: left;\n}\n\n.sees-pop {\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--sage-border);\n border-radius: var(--mj-radius-lg, 12px);\n box-shadow: var(--mj-shadow-2xl, 0 25px 50px -12px rgba(0, 0, 0, 0.45));\n animation: wb-pop-fade 160ms ease;\n}\n@keyframes wb-pop-fade {\n from { opacity: 0; transform: translateY(-4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.sees-pop__head {\n display: flex;\n align-items: center;\n gap: 9px;\n padding: 11px 14px;\n border-bottom: 1px solid var(--mj-border-default);\n font-size: 12px;\n font-weight: 700;\n color: var(--mj-color-violet-300, var(--mj-text-primary));\n}\n\n.pill {\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 5px;\n padding: 2px 9px;\n border-radius: var(--mj-radius-full, 9999px);\n font-size: 9.5px;\n font-weight: 700;\n border: 1px solid transparent;\n line-height: 1.6;\n white-space: nowrap;\n}\n.pill--sage {\n background: var(--sage-bg);\n color: var(--mj-color-violet-300, var(--mj-text-secondary));\n border-color: var(--sage-border);\n}\n\n.sees-pop__body {\n padding: 12px 14px;\n}\n.sees-pop__cap {\n font-size: 11.5px;\n color: var(--mj-text-secondary);\n line-height: 1.5;\n margin: 0 0 10px;\n font-weight: 400;\n}\n\n.codeblock {\n margin: 0;\n padding: 10px 12px;\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md, 8px);\n background: var(--mj-bg-surface-sunken);\n font-family: var(--mj-font-mono, ui-monospace, monospace);\n font-size: 10.5px;\n line-height: 1.55;\n color: var(--mj-text-secondary);\n max-height: 190px;\n overflow: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n/* token tints \u2014 matching the mockup's static code-editor palette (intentionally literal;\n these mirror VS Code's dark JSON colors and stay constant across themes) */\n.codeblock ::ng-deep .ck { color: #9cdcfe; }\n.codeblock ::ng-deep .cs { color: #ce9178; }\n.codeblock ::ng-deep .cn { color: #b5cea8; }\n.codeblock .cc,\n.codeblock ::ng-deep .cc { color: #6a9955; }\n\n.sees-pop__foot {\n display: flex;\n gap: 14px;\n padding: 9px 14px;\n border-top: 1px solid var(--mj-border-subtle, var(--mj-border-default));\n font-size: 10.5px;\n color: var(--mj-text-muted);\n font-weight: 500;\n flex-wrap: wrap;\n}\n.sees-pop__foot b {\n color: var(--mj-text-secondary);\n}\n"] }]
120
+ }], null, { AgentName: [{
121
+ type: Input
122
+ }], DebounceMs: [{
123
+ type: Input
124
+ }], ElementCount: [{
125
+ type: Input
126
+ }], LastDeltaLabel: [{
127
+ type: Input
128
+ }], DeltaJson: [{
129
+ type: Input
130
+ }] }); })();
131
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(RealtimeWhiteboardAgentSeesPopoverComponent, { className: "RealtimeWhiteboardAgentSeesPopoverComponent", filePath: "src/lib/whiteboard-agent-sees-popover.component.ts", lineNumber: 17 }); })();
132
+ //# sourceMappingURL=whiteboard-agent-sees-popover.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"whiteboard-agent-sees-popover.component.js","sourceRoot":"","sources":["../../src/lib/whiteboard-agent-sees-popover.component.ts","../../src/lib/whiteboard-agent-sees-popover.component.html"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;;;ICUzC,iCAAuB;IAAA,6BAAsC;IACnE,kBAAA;IAAA,kCAAiB;IAAA,gFAA2D;IAAO,AAAP,oBAAO,EAAM;;;IADtD,cAAwB;IAAxB,mEAAwB;;;IAG9B,AAAvB,iCAAuB,cAAiB;IAAA,uDAAkC;IAAO,AAAP,oBAAO,EAAM;;ADX7F;;;;;GAKG;AAQH,MAAM,OAAO,2CAA2C;IAPxD;QAQE,2CAA2C;QAClC,cAAS,GAAG,OAAO,CAAC;QAC7B,mEAAmE;QAC1D,eAAU,GAAG,GAAG,CAAC;QAC1B,0CAA0C;QACjC,iBAAY,GAAG,CAAC,CAAC;QAC1B,gEAAgE;QACvD,mBAAc,GAAG,GAAG,CAAC;QActB,eAAU,GAAG,EAAE,CAAC;QAExB,qFAAqF;QAC9E,eAAU,GAAG,EAAE,CAAC;KAkBxB;IAjCC,kFAAkF;IAClF,IACI,SAAS,CAAC,KAAa;QACzB,IAAI,KAAK,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YAC9B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;YACxB,IAAI,CAAC,UAAU,GAAG,2CAA2C,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5E,CAAC;IACH,CAAC;IACD,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAOD;;;OAGG;IACK,MAAM,CAAC,IAAI,CAAC,IAAY;QAC9B,MAAM,OAAO,GAAG,IAAI;aACjB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;aACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;aACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzB,OAAO,OAAO;aACX,OAAO,CAAC,kBAAkB,EAAE,iCAAiC,CAAC;aAC9D,OAAO,CAAC,kBAAkB,EAAE,iCAAiC,CAAC;aAC9D,OAAO,CAAC,0BAA0B,EAAE,+BAA+B,CAAC;aACpE,OAAO,CAAC,mBAAmB,EAAE,gCAAgC,CAAC;aAC9D,OAAO,CAAC,4BAA4B,EAAE,kCAAkC,CAAC,CAAC;IAC/E,CAAC;4IA1CU,2CAA2C;oEAA3C,2CAA2C;YChBxD,iCAAyD;YAAnC,8HAAS,wBAAwB,IAAC;YACtD,iCAA4B;YAC1B,0BAAmC;YAAC,YACpC;YAAA,kCAA8B;YAAA,+BAAe;YAC/C,AAD+C,oBAAO,EAChD;YAEJ,AADF,iCAA4B,WACD;YACvB,YAEF;YAAA,oBAAI;YAIF,AAHF,6GAAkB,yFAGT;YAGX,oBAAM;YAEJ,AADF,kCAA4B,YACpB;YAAA,0BAAS;YAAA,6BAAG;YAAA,aAAmB;YAAI,AAAJ,oBAAI,EAAO;YAChD,gCAAM;YAAA,0BAAS;YAAA,6BAAG;YAAA,aAAkB;YAAI,AAAJ,oBAAI,EAAO;YAC/C,gCAAM;YAAA,4BAAW;YAAA,6BAAG;YAAA,aAAoB;YAAI,AAAJ,oBAAI,EAAO;YACnD,gCAAM;YAAA,iCAAgB;YAAA,6BAAG;YAAA,iCAAgB;YAE7C,AADE,AAD+C,AAAJ,oBAAI,EAAO,EAChD,EACF;;YArBkC,eACpC;YADoC,oFACpC;YAIE,eAEF;YAFE,oNAEF;YACA,cAKC;YALD,yCAKC;YAGiB,eAAmB;YAAnB,gDAAmB;YACnB,eAAkB;YAAlB,sCAAkB;YAChB,eAAoB;YAApB,wCAAoB;4BDRhC,YAAY;;iFAIX,2CAA2C;cAPvD,SAAS;6BACI,IAAI,YACN,2CAA2C,WAC5C,CAAC,YAAY,CAAC;;kBAMtB,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAEL,KAAK;;kBAGL,KAAK;;kFAXK,2CAA2C","sourcesContent":["import { Component, Input } from '@angular/core';\nimport { CommonModule } from '@angular/common';\n\n/**\n * The \"What [Agent] sees\" perception popover: shows the most recent coalesced scene\n * delta JSON (syntax-tinted) plus the perception-feed stats footer (debounce, element\n * count, last-delta age, state of record). Anchored under the header button by the host;\n * visibility is the host's concern.\n */\n@Component({\n standalone: true,\n selector: 'mj-realtime-whiteboard-agent-sees-popover',\n imports: [CommonModule],\n templateUrl: './whiteboard-agent-sees-popover.component.html',\n styleUrl: './whiteboard-agent-sees-popover.component.css'\n})\nexport class RealtimeWhiteboardAgentSeesPopoverComponent {\n /** Display name of the session's agent. */\n @Input() AgentName = 'Agent';\n /** Perception debounce, in ms (the host's SceneDelta debounce). */\n @Input() DebounceMs = 750;\n /** Current element count on the board. */\n @Input() ElementCount = 0;\n /** Friendly age of the last emitted delta (\"2 s ago\" / \"—\"). */\n @Input() LastDeltaLabel = '—';\n\n /** The latest scene-delta JSON (pretty-printed). Setter pre-tints for display. */\n @Input()\n set DeltaJson(value: string) {\n if (value !== this._deltaJson) {\n this._deltaJson = value;\n this.TintedHtml = RealtimeWhiteboardAgentSeesPopoverComponent.tint(value);\n }\n }\n get DeltaJson(): string {\n return this._deltaJson;\n }\n\n private _deltaJson = '';\n\n /** HTML-escaped JSON wrapped in token-tint spans (keys/strings/numbers/comments). */\n public TintedHtml = '';\n\n /**\n * Minimal JSON syntax tinting matching the mockup's `.codeblock` token classes.\n * Input is fully HTML-escaped first, so the produced markup contains only our spans.\n */\n private static tint(json: string): string {\n const escaped = json\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n return escaped\n .replace(/\"([^\"]+)\"(\\s*):/g, '<span class=\"ck\">\"$1\"</span>$2:')\n .replace(/:(\\s*)\"([^\"]*)\"/g, ':$1<span class=\"cs\">\"$2\"</span>')\n .replace(/:(\\s*)(-?\\d+(?:\\.\\d+)?)/g, ':$1<span class=\"cn\">$2</span>')\n .replace(/(\\[\\s*)\"([^\"]*)\"/g, '$1<span class=\"cs\">\"$2\"</span>')\n .replace(/(,\\s*)\"([^\"]*)\"(\\s*[,\\]])/g, '$1<span class=\"cs\">\"$2\"</span>$3');\n }\n}\n","<div class=\"sees-pop\" (click)=\"$event.stopPropagation()\">\n <div class=\"sees-pop__head\">\n <i class=\"fa-solid fa-feather\"></i> Live perception feed → {{ AgentName }}'s context\n <span class=\"pill pill--sage\">coalesced delta</span>\n </div>\n <div class=\"sees-pop__body\">\n <p class=\"sees-pop__cap\">\n {{ AgentName }} doesn't see pixels — every change you make is serialized into a compact,\n debounced scene delta and fed into the live agent context. This is the most recent one:\n </p>\n @if (TintedHtml) {\n <pre class=\"codeblock\"><span [innerHTML]=\"TintedHtml\"></span>\n<span class=\"cc\">// replace-current-state semantics — not an append-only log</span></pre>\n } @else {\n <pre class=\"codeblock\"><span class=\"cc\">// no deltas yet — start sketching</span></pre>\n }\n </div>\n <div class=\"sees-pop__foot\">\n <span>debounce <b>{{ DebounceMs }} ms</b></span>\n <span>elements <b>{{ ElementCount }}</b></span>\n <span>last delta <b>{{ LastDeltaLabel }}</b></span>\n <span>state of record <b>session artifact</b></span>\n </div>\n</div>\n"]}