Member Junction
    Preparing search index...

    LIVE WHITEBOARD host (mj-realtime-whiteboard-host): board header (title / saved chip / ownership legend / "What [Agent] sees" popover / Focus toggle), the board surface with its floating toolbar + zoom cluster, the agent action toast with Undo, and the status footer — everything EXCEPT the overlay/tab chrome, which the session shell owns.

    Integration contract:

    • State is created by the integration layer (channel state of record) and passed in;
    • SceneDelta emits the debounced (750 ms), coalesced scene-delta JSON on user changes — the perception feed the integration pipes into the live agent context;
    • ApplyAgentTool(toolName, argsJson) executes one agent channel tool (Whiteboard_AddNote … see WHITEBOARD_TOOL_DEFINITIONS) with the violet pop-in, toast and presence-cursor garnish, returning the result JSON for the tool round-trip;
    • AgentUndo fires when the user clicks Undo on the agent-action toast;
    • FocusModeChange asks the shell to collapse/restore the call rail ("Focus board").

    Extensibility (before/after events): the host mirrors the engine's cancelable BEFORE / AFTER mutation pairs as outputs (ItemAdding/ItemAdded, ItemUpdating/ItemUpdated, ItemRemoving/ItemRemoved) and forwards the board's editor + widget pairs (ContentApplying/ContentApplied, WidgetSubmitting/WidgetSubmitted). All BEFORE events honor Cancel = true set synchronously by a handler.

    Implements

    • OnInit
    • OnDestroy
    Index

    Constructors

    Properties

    AgentName: string = 'Agent'

    Display name of the session's agent (legend, chips, popover, toast).

    AgentPresence: WhiteboardAgentPresence = null
    AgentUndo: EventEmitter<void> = ...

    The user clicked Undo on the agent-action toast (the undo itself already applied).

    BoardTitle: string = 'Whiteboard'

    Board title shown in the header.

    ContentApplied: EventEmitter<WhiteboardContentAppliedEventArgs> = ...

    AFTER event (forwarded from the board): an editor commit applied.

    ContentApplying: EventEmitter<WhiteboardContentApplyingEventArgs> = ...

    Cancelable BEFORE event (forwarded from the board): an in-board editor commit is about to write to the state engine. Set Cancel = true synchronously to discard it.

    EnableGlobalShortcuts: boolean = false

    Opt back in to DOCUMENT-WIDE keyboard shortcuts.

    The host binds eleven bare single-character tool shortcuts (v h p r s t m w i c e). While those listened on document unconditionally they fired wherever the user was on the page, which fails WCAG 2.1.4 (Character Key Shortcuts): a speech-input user saying an ordinary word, or anyone typing in a non-input control elsewhere, silently switched the board tool. They are now scoped to focus being inside this host, which satisfies the criterion's "active only on focus" branch.

    Scoping covers the host's ENTIRE keydown handler, not only the letters: undo/redo (Cmd/Ctrl+Z, +Y), Escape and Delete/Backspace are focus-gated as well.

    Set true only for a surface where the whiteboard is the entire page AND the 2.1.4 exposure has been accepted — it is an accessibility regression, not a convenience flag.

    ExportOpen: boolean = false
    FocusMode: boolean = false
    FocusModeChange: EventEmitter<boolean> = ...

    Focus-board toggle — the session shell collapses/restores the call rail.

    HostTabIndex: "-1" = '-1'

    The board is made focusable so its shortcuts have a focus scope to be bound to. -1, not 0: the canvas has no keyboard interaction model yet (see the whiteboard keyboard- operability work), so adding a real Tab stop would put a stop in the page that a keyboard user can reach and do nothing with. Click focuses it; that is the whole contract today.

    ItemAdded: EventEmitter<WhiteboardItemAddedEventArgs> = ...

    AFTER mirror of WhiteboardState.ItemAdded$.

    ItemAdding: EventEmitter<WhiteboardItemAddingEventArgs> = ...

    Cancelable BEFORE mirror of WhiteboardState.ItemAdding$.

    ItemRemoved: EventEmitter<WhiteboardItemRemovedEventArgs> = ...
    ItemRemoving: EventEmitter<WhiteboardItemRemovingEventArgs> = ...

    Cancelable BEFORE mirror of WhiteboardState.ItemRemoving$.

    ItemUpdated: EventEmitter<WhiteboardItemUpdatedEventArgs> = ...
    ItemUpdating: EventEmitter<WhiteboardItemUpdatingEventArgs> = ...

    Cancelable BEFORE mirror of WhiteboardState.ItemUpdating$ (update / move / reorder).

    LastDeltaJson: string = ''
    NowTick: number = ...
    PenColor: string = ...
    PenWidth: number = 4
    ReadOnly: boolean = false

    Render the board read-only: nothing on it can be created, edited, moved or deleted.

    This is the OTHER axis from ToolRoster, and the two do not substitute for each other. The roster answers "which tools does this surface offer" — it narrows a palette the user still authors with. ReadOnly answers "may anything change at all" — it is about mutation, not about choice, so an empty roster is not a read-only board and a full roster on a read-only board still mutates nothing.

    The board component already guards every mutation entry point and hides the page strip's add / rename / delete affordances; the host adds the chrome that only it owns — the floating toolbar is not rendered (matching WhiteboardSnapshotComponent, which draws a read-only board with no toolbar), the agent toast's Undo is hidden because Undo applies a mutation, and the keyboard handler returns before any key can act. Pan and zoom stay live: reading a board you cannot change still means moving around it.

    SavedLabel: string = 'Saved to session'

    Persistence chip text (e.g. "Saved to session · v14").

    SaveToArtifactsRequested: EventEmitter<void> = ...

    Emitted when the user picks "Save to artifacts" — the channel plugin persists via its host context.

    SceneDelta: EventEmitter<string> = ...

    Debounced (750 ms), coalesced scene-delta JSON — the live perception feed.

    SeesOpen: boolean = false
    ShapeKind: "rect" | "ellipse" | "diamond" = 'rect'

    Shared board state engine, owned by the integration layer.

    TextBold: boolean = true
    TextColor: string = null
    TextFamily: WhiteboardFontFamily = 'sans'
    TextSize: number = 12
    ToastText: string = null
    WidgetInteraction: EventEmitter<WhiteboardWidgetInteractionEvent> = ...

    AMBIENT widget telemetry (forwarded from the board): the injected recorder observed passive form activity (clicks / changes / typing / focus) inside a sandboxed HTML widget, validated + pre-summarized. Integration layers forward it to their agent runtime as throttled, low-priority background context — e.g. MJ's channel plugin sends an "ambient activity" [whiteboard] note at most once per widget per few seconds.

    WidgetSubmitted: EventEmitter<WhiteboardWidgetSubmitEvent> = ...

    AFTER event: a sandboxed HTML widget submitted user input via MJWhiteboard.submit (validated + size-capped by the board, not canceled). Integration layers forward it to their agent runtime — e.g. MJ's channel plugin sends a [whiteboard] context note.

    WidgetSubmitting: EventEmitter<WhiteboardWidgetSubmittingEventArgs> = ...

    Cancelable BEFORE event (forwarded from the board): a sandboxed HTML widget submitted input via MJWhiteboard.submit and it passed validation. Set Cancel = true synchronously to drop it before WidgetSubmitted fires.

    Accessors

    • get HasMultiplePages(): boolean

      Whether the board has more than one page (gates the "all pages" export entries).

      Returns boolean

    • get LastDeltaLabel(): string

      "2 s ago" style age of the last emitted delta (the ticker keeps it fresh).

      Returns string

    • get Tool(): | "sticky"
      | "shape"
      | "text"
      | "image"
      | "connector"
      | "markdown"
      | "html"
      | "select"
      | "pan"
      | "pen"
      | "eraser"

      The active tool, and the ONE place the roster invariant is enforced on a write.

      "The active tool is always one the roster allows" is a property of every write, not of the handful of call sites that exist today (the roster setter, Escape, the key map, and the two template Tool = $event bindings). Guarding each of those would leave the next writer — a new shortcut, a gesture, a host API — to remember the rule. Guarding here closes them all by construction, and makes assignment order irrelevant: setting ToolRoster and Tool in either order still lands on an allowed tool.

      A DISALLOWED request is ignored rather than clamped: pressing Escape under ['pen','eraser'] should leave you on pen, not bounce you to the roster's fallback. The clamp is only for the case where the tool you are already holding stopped being allowed.

      ONE EXCEPTION to "always one the roster allows": a roster that allows nothing at all — empty, or naming no real tool. The board must hold some tool, so ClampToolToRoster floors it at select, which is then held even though the roster does not list it. That is deliberate and is the safe answer precisely because select can create nothing; the alternative — keeping the tool the roster just revoked — left a creating tool live with no toolbar to see it and no key to change it. See ClampToolToRoster for why the floor is select rather than the roster's first entry.

      Returns
          | "sticky"
          | "shape"
          | "text"
          | "image"
          | "connector"
          | "markdown"
          | "html"
          | "select"
          | "pan"
          | "pen"
          | "eraser"

    • set Tool(
          value:
              | "sticky"
              | "shape"
              | "text"
              | "image"
              | "connector"
              | "markdown"
              | "html"
              | "select"
              | "pan"
              | "pen"
              | "eraser",
      ): void

      Parameters

      • value:
            | "sticky"
            | "shape"
            | "text"
            | "image"
            | "connector"
            | "markdown"
            | "html"
            | "select"
            | "pan"
            | "pen"
            | "eraser"

      Returns void

    • get ToolRoster(): readonly (
          | "sticky"
          | "shape"
          | "text"
          | "image"
          | "connector"
          | "markdown"
          | "html"
          | "select"
          | "pan"
          | "pen"
          | "eraser"
      )[]

      Returns readonly (
          | "sticky"
          | "shape"
          | "text"
          | "image"
          | "connector"
          | "markdown"
          | "html"
          | "select"
          | "pan"
          | "pen"
          | "eraser"
      )[]

    • set ToolRoster(
          value: readonly (
              | "sticky"
              | "shape"
              | "text"
              | "image"
              | "connector"
              | "markdown"
              | "html"
              | "select"
              | "pan"
              | "pen"
              | "eraser"
          )[],
      ): void

      Which tools this surface offers. null (default) is all eleven: today's rendering.

      The roster governs which tools are AVAILABLE, closing every door to a tool it leaves out: the toolbar button, the single-letter shortcut, and the canvas "add … here" action. It is deliberately not a content policy — it does not restrict what already exists on the board, what the agent places, or authoring on existing items (Duplicate, z-order, Delete, paste). The one exception is Restyle…, which is hidden when the roster omits text, because it opens the TEXT tool's style flyout and would otherwise be a menu entry that does nothing.

      An empty roster (or one naming no real tool) narrows the palette to nothing and clamps the active tool to select. It does NOT make the board read-only — that is ReadOnly.

      A setter rather than ngOnChanges (packages/Angular/CLAUDE.md): a realtime channel's BindSurface assigns this property directly on a dynamically-created component, and Angular fires no ngOnChanges for a plain assignment, so a clamp implemented there would silently never run on the path real consumers use.

      Parameters

      • value: readonly (
            | "sticky"
            | "shape"
            | "text"
            | "image"
            | "connector"
            | "markdown"
            | "html"
            | "select"
            | "pan"
            | "pen"
            | "eraser"
        )[]

      Returns void

    Methods

    • Execute one agent whiteboard tool (author 'agent', one undo batch), with the UI garnish: presence cursor glides to the mutation point, the new item pops in violet, and the action toast (with Undo) appears. Returns the result JSON string that the integration layer feeds back to the realtime model as the tool_response.

      Parameters

      • toolName: string
      • argsJson: string

      Returns string

    • Download ONE self-contained HTML document containing EVERY page (titled sections).

      Returns void

    • Download EVERY page as its own standalone SVG document (SVG is single-image, so "all pages" is one file per page, downloaded sequentially with the page name slug-suffixed into each filename).

      Returns void

    • Print the board: open a blank window, write the self-contained export document and invoke the print dialog (same precedent as the artifact viewer's print path). When the popup is blocked this is a logged no-op — nothing breaks, the user can retry.

      Returns void

    • Request a first-class artifact snapshot of the board (handled by the channel plugin).

      Returns void

    • A callback method that performs custom clean-up, invoked immediately before a directive, pipe, or service instance is destroyed.

      Returns void

    • A callback method that is invoked immediately after the default change detector has checked the directive's data-bound properties for the first time, and before any of the view or content children have been checked. It is invoked only once when the directive is instantiated.

      Returns void

    • Clicking the board takes focus, so the shortcuts below apply. The browser would usually do this on its own by focusing the nearest focusable ancestor, but the board's pointer pipeline is elaborate enough (drag, marquee, handles, transient shapes) that relying on that default is fragile — one preventDefault() added later would silently kill every shortcut. This makes it explicit.

      Returns void