{"version":3,"sources":["../react-directives/directive-host.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * THE DIRECTIVE HOST CONTRACT — what the directive tier refuses to decide.\n *\n * The package owns the DECISIONS (decode → renderer → card → floor, the\n * position law, never-null rendering) and the card itself. Four things stay\n * host property and enter through named seams:\n *\n *  1. APPLYING a side-effect directive — `POST /directives/confirm` as the\n *     user, with the host's auth and base URL. Absent → the Apply control is\n *     ABSENT (honest), never a dead button.\n *  2. OPENING an item to read it before approving — a window panel, a dialog,\n *     a route. Absent → items are not clickable.\n *  3. COPY controls — host chrome (matrx-frontend's CopyButtons). Absent → none.\n *  4. The NOUN catalog + item-kind lookup — server-derived tables the host\n *     mirrors (or reads from its kind catalog's `items` edges).\n *  5. ASKING (`ask`), NAMING a record (`renderRecord`), and READING the ledger\n *     (`applyState`) — the consequence before the click, the doors, and the\n *     state that survives a reload.\n *\n * A host that has adopted `ContentIrRenderProvider` passes `directives` on its\n * ContentIrHost; a host that has NOT (Workflow Studio, 2026-09-08) mounts\n * `DirectiveHostProvider` alone — the directive tier never requires the full\n * render host, because a chat bubble must be able to draw a directive card\n * identically everywhere, today.\n */\n\nimport { createContext, useContext, type ReactNode } from \"react\";\nimport type { DecodedDirective, DirectiveItemKindLookup, DirectiveNounCatalog } from \"../directives\";\n\nexport interface DirectiveApplyResult {\n  applied: number;\n  failed: number;\n  /**\n   * The server's own receipt sentence (\"Deleted task “Ship it”.\"), shown in\n   * place of a bare count. Never composed client-side.\n   */\n  message?: string | null;\n  /**\n   * The records the apply created or changed, so the tally is a way IN to them\n   * (no dead ends). Rendered through `renderRecord`; absent → the sentence alone.\n   */\n  records?: DirectiveRecordRef[];\n  /**\n   * For an UPDATE: what each item's fields held just BEFORE the write, by item\n   * index (`null` for an item the server did not record). The card draws the\n   * applied change from this record — never from a live re-read, which after the\n   * apply answers the NEW value (G6A review, 2026-10-02: \"(no change)\" for a\n   * change that happened).\n   */\n  before?: ReadonlyArray<Record<string, unknown> | null>;\n}\n\n/**\n * What the server's action ledger says about ONE block, read on mount so a\n * reloaded page never offers \"Apply\" for work that already ran (reviewer\n * defect 5, 2026-10-02).\n *\n *  - `not_applied` — still to do (or partly done: the batch re-applies, and the\n *    server replays the members it already holds). The card offers Apply.\n *  - `in_flight` — another request is applying it right now. The card says it\n *    is finishing and asks again shortly; it offers neither Apply nor a tally.\n *  - `applied` — the ledger holds every item. The card shows the tally exactly\n *    as a fresh apply would: the ledger's own sentence and the records, as doors.\n */\nexport interface DirectiveApplyState {\n  state: \"not_applied\" | \"in_flight\" | \"applied\";\n  /** The ledger row's sentence (\"Created task.\"), never composed client-side. */\n  message?: string | null;\n  /**\n   * The records the LATEST apply wrote — the tally's doors. After \"Run again\"\n   * that is the newest copy, never the first.\n   */\n  records?: DirectiveRecordRef[];\n  /**\n   * How many times the ledger holds this block as applied: 1 for the original,\n   * one more for every \"Run again\". Absent → 1. A create that ran twice made\n   * two records, and the card says so (reviewer, 2026-10-02).\n   */\n  copies?: number;\n  /**\n   * For an applied UPDATE: each item's replaced values, by item index, from the\n   * ledger row (`null` where the ledger has none — a row from before 2026-10-02).\n   * Absent → the card says only what each field was set to.\n   */\n  before?: ReadonlyArray<Record<string, unknown> | null>;\n}\n\n/** One record a directive names: the noun it is, and its id. */\nexport interface DirectiveRecordRef {\n  noun: string;\n  id: string;\n}\n\nexport interface DirectiveRecordProps extends DirectiveRecordRef {\n  /**\n   * What to show until (or unless) the host resolves the live name —\n   * \"Task 1a2b3c4d\". Never blank, never \"Item\".\n   */\n  fallback: string;\n  /** Where it is drawn: an item row, the confirm dialog, or the applied tally. */\n  context: \"row\" | \"dialog\" | \"applied\";\n  /**\n   * True when the record is (about to be) in the trash — the host may still\n   * name it but should not promise it opens as a live record.\n   */\n  trashed?: boolean;\n}\n\n/** What the person is being asked to approve — everything the card knows. */\nexport interface DirectiveAskRequest {\n  directive: DecodedDirective;\n  /** The items, exactly as they will be sent. */\n  items: Record<string, unknown>[];\n  /** The noun's human label (\"Task\"), from the host's catalog when it has one. */\n  nounLabel: string;\n  /**\n   * True for \"Run again\": the ledger already holds this block, and a yes runs\n   * it a SECOND time (`force`) — another copy for a create, the same fields\n   * written again for an update, another delete for a restored record. The\n   * question must say so.\n   */\n  again?: boolean;\n}\n\n/**\n * The shell a card hands its host — the two keys, plus WHERE it sits.\n *\n * `conversationId` is the conversation the card renders in (a chat message),\n * absent in a person's own content (a note). It is the idempotency NAMESPACE:\n * a card in an agent's message must apply under the SAME key as that\n * proposal's Approve button, or the one action could run twice.\n */\nexport interface DirectiveShell {\n  __kind: string;\n  items: Record<string, unknown>[];\n  conversationId?: string;\n  /** \"Run again\": skip the ledger's dedup and apply a second time. */\n  force?: boolean;\n}\n\nexport interface DirectiveOpenItemOptions {\n  /** The item, `__kind`-stamped when the shape has an item kind. */\n  item: Record<string, unknown>;\n  /** The content-IR kind this item IS, or null when the shape has none. */\n  itemKind: string | null;\n  /** What the item is called — the same title the card shows. */\n  title: string;\n  /** \"Run Create agent definition · item 1 of 1\". */\n  subtitle: string | null;\n  directive: DecodedDirective;\n  index: number;\n  total: number;\n}\n\nexport interface DirectiveCopyProps {\n  /**\n   * What the control is called to a screen reader. Never an id: for a record it\n   * is type-generic (\"this task\"); a host with the record's live name replaces\n   * it through `record`.\n   */\n  label: string;\n  /** The existing record this control copies, when it copies one — to name it. */\n  record?: DirectiveRecordRef;\n  /** The value to copy — the whole shell or one item. */\n  value: unknown;\n  /** The item's kind, when copying an item. */\n  kind?: string | null;\n  size: \"icon\" | \"xs\";\n}\n\nexport interface DirectiveHost {\n  /**\n   * Apply a side-effect directive as the user (the server re-validates every\n   * item against the registered model and runs the ONE handler; idempotent).\n   * Reject with an Error whose message is safe to show.\n   */\n  confirm?: (shell: DirectiveShell) => Promise<DirectiveApplyResult>;\n  /**\n   * Ask the person BEFORE anything runs, naming the consequence (the records,\n   * what changes). Resolve `true` to go ahead, `false` to leave the card as it\n   * was. The Apply control stays idle while this is open — nothing reads as\n   * \"applying\" until the person has said yes. Absent → `confirm` is called\n   * directly (a host that asks inside `confirm` keeps working, but its card\n   * will read \"Applying…\" while its dialog is up).\n   */\n  ask?: (request: DirectiveAskRequest) => Promise<boolean>;\n  /**\n   * Has this exact block already been applied? Read ONCE when the card mounts\n   * (and again while the answer is `in_flight`), so the card survives a reload:\n   * the server ledger, not component state, is what knows. The identity is the\n   * server's (it hashes the validated items in the same namespace `confirm`\n   * applies in), so a host asks — it never computes. Batch on the host side if\n   * a page carries many cards. Reject with an Error when the ledger cannot be\n   * read: the card reports it and offers Apply, which is safe because `confirm`\n   * is idempotent. Absent → the card starts at Apply, as before.\n   */\n  applyState?: (shell: DirectiveShell) => Promise<DirectiveApplyState>;\n  /**\n   * Read the CURRENT values of ONE existing record, so an update says what it\n   * replaces — \"Status open → done\", never just \"→ done\" (reviewer,\n   * 2026-10-02). Resolve `null` when the record cannot be read (no access, no\n   * such record, a noun the host cannot read); reject only for a real failure.\n   * Absent → an update row shows the new values alone, as before.\n   */\n  readRecord?: (ref: DirectiveRecordRef) => Promise<Record<string, unknown> | null>;\n  /**\n   * Draw ONE existing record by its live name, as a way to open it. Used on\n   * update/delete rows (the record that will change), in the host's own ask\n   * dialog if it likes, and on the applied tally (what was written). Absent →\n   * the fallback text, unlinked.\n   */\n  renderRecord?: (props: DirectiveRecordProps) => ReactNode;\n  /** Open ONE item to read it properly before approving the write. */\n  openItem?: (options: DirectiveOpenItemOptions) => void;\n  /** Host copy controls. */\n  renderCopy?: (props: DirectiveCopyProps) => ReactNode;\n  /**\n   * The app's word for one stored value of one pick-list field — the SAME word\n   * the record's own screens and the write forms show (\"incomplete\" on a task\n   * reads \"Inbox\"). One value, one display word everywhere (G8A review,\n   * 2026-10-02: the card said \"Status incomplete → completed\"). Return null for\n   * a field that is not a pick-list: its value is shown as written. Absent →\n   * every value is shown as stored.\n   */\n  valueLabel?: (ref: DirectiveValueRef) => string | null;\n  /** The mirrored noun catalog (label / family / title column). */\n  nouns?: DirectiveNounCatalog;\n  /** slug → the kind ONE item is (THE DIRECTIVE⇄KIND SEAM), or null. */\n  itemKind?: DirectiveItemKindLookup;\n  /** Where a decode failure or an unhonourable shell is reported. Default: console.error. */\n  reportError?: (message: string) => void;\n  /**\n   * Plain words for a failed apply the host recognizes (\"This button can't\n   * create a task yet.\" + \"Create it from Tasks instead.\"). The card never shows\n   * the server's raw text as its sentence: it shows these words — or its own\n   * verb-aware default — and keeps the raw text behind \"Details\". Return null\n   * to use the default.\n   */\n  explainFailure?: (failure: DirectiveFailure) => DirectiveFailureWords | null;\n}\n\n/** One stored value of one field of one record type. */\nexport interface DirectiveValueRef {\n  noun: string;\n  field: string;\n  value: string;\n}\n\n/** One failed apply, as the card knows it. */\nexport interface DirectiveFailure {\n  /** The server's / host's own text — technical; shown only behind Details. */\n  raw: string;\n  directiveClass: string;\n  noun: string;\n  /** The noun's display label (\"Task\"). */\n  nounLabel: string;\n  /** Items that failed, when some applied and some did not. */\n  failed?: number;\n  /** Items in the block. */\n  total?: number;\n}\n\n/** What failed and what to do — each one short sentence. */\nexport interface DirectiveFailureWords {\n  what: string;\n  next: string;\n  /**\n   * The text behind \"Details\", when the host words it (e.g. the server's field\n   * names read as the form labels them — \"Title is required\", never\n   * \"name is required\"). Absent → the raw text.\n   */\n  details?: string;\n}\n\nconst DirectiveHostContext = createContext<DirectiveHost | null>(null);\n\n/** Where a directive renders — today, the conversation it sits in (if any). */\nexport interface DirectiveScope {\n  conversationId?: string | null;\n}\n\nconst DirectiveScopeContext = createContext<DirectiveScope>({});\n\n/**\n * Tell every card below WHERE it renders. A host draws a chat message's\n * directives inside one of these (or passes `conversationId` to\n * `DirectiveRender`), so the card applies in that conversation's namespace —\n * the same key the agent's proposal approves under. A note mounts none.\n */\nexport function DirectiveScopeProvider({\n  conversationId,\n  children,\n}: DirectiveScope & { children: ReactNode }) {\n  return (\n    <DirectiveScopeContext.Provider value={{ conversationId: conversationId ?? null }}>\n      {children}\n    </DirectiveScopeContext.Provider>\n  );\n}\n\nexport function useDirectiveScope(): DirectiveScope {\n  return useContext(DirectiveScopeContext);\n}\n\nexport interface DirectiveHostProviderProps {\n  host: DirectiveHost;\n  children: ReactNode;\n}\n\nexport function DirectiveHostProvider({ host, children }: DirectiveHostProviderProps) {\n  return <DirectiveHostContext.Provider value={host}>{children}</DirectiveHostContext.Provider>;\n}\n\nconst EMPTY_HOST: DirectiveHost = {};\n\n/**\n * The wired directive host. Unlike the render host this does NOT throw when\n * unwired: a directive still renders (named, itemised, copy-less, apply-less)\n * — degrading the chrome is honest, hiding the content is not.\n */\nexport function useDirectiveHost(): DirectiveHost {\n  return useContext(DirectiveHostContext) ?? EMPTY_HOST;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4BA,mBAA0D;AA4QtD;AApBJ,IAAM,2BAAuB,4BAAoC,IAAI;AAOrE,IAAM,4BAAwB,4BAA8B,CAAC,CAAC;AAQvD,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AACF,GAA6C;AAC3C,SACE,4CAAC,sBAAsB,UAAtB,EAA+B,OAAO,EAAE,gBAAgB,kBAAkB,KAAK,GAC7E,UACH;AAEJ;AAEO,SAAS,oBAAoC;AAClD,aAAO,yBAAW,qBAAqB;AACzC;AAOO,SAAS,sBAAsB,EAAE,MAAM,SAAS,GAA+B;AACpF,SAAO,4CAAC,qBAAqB,UAArB,EAA8B,OAAO,MAAO,UAAS;AAC/D;AAEA,IAAM,aAA4B,CAAC;AAO5B,SAAS,mBAAkC;AAChD,aAAO,yBAAW,oBAAoB,KAAK;AAC7C;","names":[]}