/** Public linked-cell contract of a shared Loom. */ import type { Cell, Default, JSONValue, NAME, PerSession, PerSpace, PerUser, Stream, UI, VNode, Writable, } from "commonfabric"; import type { FileStoreOutput } from "../fabrifile/schemas.tsx"; import type { FileStoreRecord, FileStoreRecordCell, } from "../fabrifile/store.tsx"; import type { PanelAdderDid, PanelAdderProfile } from "./admission.tsx"; import type { ParticipantProfile, ParticipantRoster, ParticipantRosterCell, } from "./participants.tsx"; export type { PanelAdderDid, PanelAdderProfile, ParticipantProfile, ParticipantRoster, ParticipantRosterCell, }; /** Allowlisted public contact information supplied by the publisher. */ export interface PublishedChannel { kind: string; value: string; label?: string; } /** Allowlisted source projection, refreshed only by the producer. */ export type PublishedSource = | { kind: "page-excerpt"; title: string; body: string } | { kind: "person-card"; name: string; photo?: string; channels: PublishedChannel[]; }; /** Producer source and collaborative notes have separate write locations. */ export interface PublishedDocument { source: PublishedSource; notes: string | Default<"">; } /** * One occurrence in the Loom, retaining the complete target cell reference. * * `addedByProfile` is the profile under which the person who added the * occurrence acted, and the label entry the panel declares at that field, as * opposed to the copies of the profile's own label, names the principal who * acted. `addedBy` is the DID of the principal who added the occurrence, and * the label entry the panel declares there names the same principal when the * root wrote it. Of the root's handlers only `admitPanel` writes either, and * once it has written a panel no other handler may. An occurrence it creates * records `addedByProfile` when its event names a profile of its actor's own, * and otherwise `addedBy` when the event acted for a principal, so a run that * acts for no one records neither. An occurrence a caller made may hold an * `addedBy` the root never wrote: the value is its writer's claim, and a label * entry there names the writer, not the principal the value names. A panel * with neither names no adder. */ export type Panel = | { kind: "piece"; piece: Writable; titleOverride?: string; addedBy?: PanelAdderDid; addedByProfile?: PanelAdderProfile; } | { kind: "document"; content: Writable; titleOverride?: string; addedBy?: PanelAdderDid; addedByProfile?: PanelAdderProfile; } | { kind: "url"; url: string; titleOverride?: string; addedBy?: PanelAdderDid; addedByProfile?: PanelAdderProfile; }; /** Shared staging and focus, independent of each viewer's session selection. */ export interface Presentation { stagedPanels: Writable[]; focusedPanel?: Writable; } /** * A chat room as the Loom links it: its name, and nothing of its conversation, * so a reader of the Loom loads none of the room's messages. A client opens * the room through the link, under its own access. */ export interface LinkedChatRoom { [NAME]?: string; } /** * Where the Loom keeps its chat room: a link to a room piece in the Loom's own * space, in `room`, absent while the Loom names none. */ export interface ChatRoomRecord { room?: Writable; } /** * The shared chat-room cell; a Loom naming no room holds `{}`. It holds a * record rather than the link itself because a handler's cell for a field * holding a link writes through the link, so replacing the link there would * write into the room it names. */ export type ChatRoomCell = Writable< ChatRoomRecord | Default> >; /** Caller-computed portable keys for one iCalendar occurrence. */ export interface PortableMeetingIdentity { /** The UID and typed original-slot hash contract. */ scheme: "ical-uid-slot"; /** Canonical lowercase SHA-256 item key. */ item_key: string; /** Canonical lowercase SHA-256 occurrence key. */ occurrence_key: string; [field: string]: JSONValue; } /** The meeting occurrence associated with this Loom, absent while unbound. */ export interface MeetingAssociation { /** Immutable lowercase 64-character hexadecimal legacy meeting key. */ key?: string; /** Portable occurrence evidence supplied by an authorized caller. */ portableIdentity?: PortableMeetingIdentity; [field: string]: unknown; } /** Shared durable meeting association, initialized empty. */ export type MeetingAssociationCell = Writable< MeetingAssociation | Default> >; /** The outcome of one binding request; a conflict names the stored key. */ export type BindMeetingResult = | { status: "bound"; key: string } | { status: "already-bound"; key: string } | { status: "conflict"; key: string } | { status: "invalid-key" } | { status: "invalid-state" }; /** A meeting key and an optional cell receiving this request's outcome. */ export interface BindMeetingRequest { key: string; /** Raw portable identity evidence that bindMeeting validates. */ portableIdentity?: JSONValue; reply?: Writable; } /** Confirmed portable appearance shared by every participant; null clears it. */ export type LoomStyle = { /** Catalog identifier interpreted by the viewer. */ id: string; /** Explicit rendering mode, or the catalog's native mode. */ mode: "native" | "light" | "dark"; } | null; /** Shared state supplied when a Loom is instantiated. */ export interface LoomInput { title?: PerSpace>>; /** Confirmed appearance; transient previews stay in the viewer. */ style?: PerSpace>>; panels?: PerSpace[] | Default<[]>>>; presentation?: PerSpace< Writable> >; participants?: PerSpace; chatRoom?: PerSpace; /** The Loom's space's file store, absent until it is first needed. */ fileStore?: PerSpace; meeting?: PerSpace; hiddenPanels?: PerUser[] | Default<[]>>>; privatePanels?: PerUser>>; } /** The room `setChatRoom` names as the Loom's chat. Omission clears it. */ export interface ChatRoomChoice { room?: Writable; } /** A panel and the title it shows in place of its target's; `""` clears it. */ export interface PanelTitle { panel: Writable; titleOverride: string; } /** Where a URL or piece panel points: a URL, or a piece by its complete link. */ export type PanelTarget = | { kind: "url"; url: string } | { kind: "piece"; piece: Writable }; /** A URL or piece panel, and the target `retargetPanel` points it at. */ export interface PanelRetarget { panel: Writable; target: PanelTarget; } /** An occurrence and its optional insertion anchor. Omission appends. */ export interface PanelPosition { panel: Writable; before?: Writable; } /** * One of a viewer's private panels: an occurrence in a space other than the * Loom's, and the shared occurrence it shows just ahead of. Without `before`, * or while `before` is not shown to the viewer, it shows after every shared * one. */ export interface PrivatePanel { panel: Writable; before?: Writable; } /** * The profile under which the person adding a panel acts, which is one of * their own: the root refuses a profile whose label does not attest the * principal the event acts for. Without one, the root records that principal; * an event never names it. */ export interface PanelAdder { as?: ParticipantProfile; } /** * The event of every stream that adds a panel. Each is a binding of the one * handler that writes a panel's adder, so all three take this shape: * `addPiece` requires `piece`, and `addPanel` and `duplicatePanel` require * `panel` and accept `before`. `addPanel` with `as` adds a copy of `panel`, * never recording the profile on the document passed. */ export interface PanelAdmission extends PanelAdder { piece?: Writable; panel?: Writable; before?: Writable; } /** * Ephemeral state local to one viewer session: its selection, and the profile * the session acts under when it adds a panel from the root's UI. Without * `actingProfile`, the viewer's `#profile` is used. */ export interface ViewerState { selectedPanel?: Writable; actingProfile?: ParticipantProfile; } /** Standard default-pattern exports and the public composition actions. */ export interface LoomOutput { [NAME]: string; [UI]: VNode; title: string; /** Confirmed appearance for all participants. */ style: LoomStyle; /** Replaces the complete style and mode atomically; null clears it. */ setStyle: Stream<{ style: LoomStyle }>; viewerState: PerSession>; panels: Writable[]; presentation: Presentation; pieceRegistry: Writable[]; addPiece: Stream; removePiece: Stream<{ piece: Writable }>; addPanel: Stream; removePanel: Stream<{ panel: Writable }>; movePanel: Stream; duplicatePanel: Stream; setPresentation: Stream; participants: ParticipantProfile[]; addParticipant: Stream<{ profile: ParticipantProfile }>; chatRoom?: Writable; setChatRoom: Stream; ensureChatRoom: Stream; /** The Loom's space's file store, absent until `ensureFileStore` names one. */ fileStore?: Cell; /** Names a new store as the Loom's space's, unless it names one already. */ ensureFileStore: Stream; /** * Where the Loom keeps its space's file store, which its chat room, as a * FabriChat container's, names a store in too. */ fileStoreRecord: Cell; meeting: MeetingAssociation; bindMeeting: Stream; retitleLoom: Stream<{ title: string }>; retitlePanel: Stream; retargetPanel: Stream; hiddenPanels: PerUser[]>>; privatePanels: PerUser>; viewerPanels: Writable[]; hidePanel: Stream<{ panel: Writable }>; unhidePanel: Stream<{ panel: Writable }>; addPrivatePanel: Stream; removePrivatePanel: Stream<{ panel: Writable }>; movePrivatePanel: Stream; }