@agimon-ai/doompi-author 0.0.1-alpha.11 → 0.0.1-alpha.14

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.
@@ -1,4 +1,4 @@
1
- import { apiResponse, createApiClient, defineApiRoutes, defineChannelFile, defineDockFace, defineFileLinks, defineGlobalStore, defineLeaderBinding, defineMinorModeFile, defineSessionStore, defineToolRenderer, defineWebLifecycle, defineWebPlugin, sessionApiAddress, toolResultTextLines } from "@agimon-ai/doompi-core/web";
1
+ import { apiResponse, createApiClient, defineApiRoutes, defineChannelFile, defineDockFace, defineFileLinks, defineGlobalStore, defineLeaderBinding, defineMinorModeFile, defineSessionStore, defineSlotFile, defineToolRenderer, defineWebLifecycle, defineWebPlugin, sessionApiAddress, toolResultTextLines } from "@agimon-ai/doompi-core/web";
2
2
  import { Store } from "@tanstack/store";
3
3
  import { Button, CodeEditor, Markdown, MediaPreview, MessageItem, MessageItemBody, MessageItemHeader, MessageItemStatus, MessageLines, collapseLines, toolTone } from "@agimon-ai/doompi-web-components";
4
4
  import { useStore } from "@tanstack/react-store";
@@ -501,7 +501,7 @@ function putAuthorDocument(sessionId, input) {
501
501
  },
502
502
  regions: [],
503
503
  candidate: void 0,
504
- requests: session.requests.map((request) => request.documentPath === path && (request.status === "REQUESTED" || request.status === "CHANGING" || request.status === "CHANGED") ? {
504
+ requests: session.requests.map((request) => input.kind !== "story-preview" && request.documentPath === path && (request.status === "REQUESTED" || request.status === "CHANGING" || request.status === "CHANGED") ? {
505
505
  ...request,
506
506
  status: "FAILED",
507
507
  currentOperation: void 0,
@@ -1033,6 +1033,7 @@ function utf8Prefix(value, limit) {
1033
1033
  }
1034
1034
  function createAuthorCapturePacket(captureId, capturedAt, document, regions) {
1035
1035
  if (regions.length === 0 || regions.length > 16) throw new Error("Capture requires between 1 and 16 regions.");
1036
+ if (document.kind === "story-preview" && document.storyPreview === void 0) throw new Error("Story preview capture requires source provenance.");
1036
1037
  const packet = {
1037
1038
  version: 1,
1038
1039
  captureId,
@@ -1041,7 +1042,8 @@ function createAuthorCapturePacket(captureId, capturedAt, document, regions) {
1041
1042
  path: document.path,
1042
1043
  kind: document.kind,
1043
1044
  revision: document.version,
1044
- ...document.sourceSha256 === void 0 ? {} : { sourceSha256: document.sourceSha256 }
1045
+ ...document.sourceSha256 === void 0 ? {} : { sourceSha256: document.sourceSha256 },
1046
+ ...document.storyPreview === void 0 ? {} : { storyPreview: structuredClone(document.storyPreview) }
1045
1047
  },
1046
1048
  regions: regions.map((region, index) => {
1047
1049
  if (region.documentPath !== document.path || region.revision !== document.version || region.sourceSha256 !== document.sourceSha256) throw new Error(`Region ${String(index + 1)} is stale for the focused document.`);
@@ -1073,12 +1075,30 @@ function anchorLocation(anchor) {
1073
1075
  case "image-rect": return `image ${rectLocation(anchor.rect)}`;
1074
1076
  case "pdf-page-rect": return `page ${String(anchor.page)}, ${rectLocation(anchor.rect)}`;
1075
1077
  case "video-time-rect": return `time ${anchor.timeSeconds.toFixed(3)}s${anchor.frame === void 0 ? "" : `, frame ${String(anchor.frame)}`}, ${rectLocation(anchor.rect)}`;
1078
+ case "story-preview-rect": return `story preview ${rectLocation(anchor.rect)}`;
1076
1079
  }
1077
1080
  }
1081
+ function storyPreviewProvenance(packet) {
1082
+ const preview = packet.document.storyPreview;
1083
+ if (preview === void 0) return void 0;
1084
+ const sources = preview.sources.map((source) => `- ${source.path} (sha256 ${source.sha256})`).join("\n");
1085
+ return [
1086
+ "Story preview source (edit these sources, never the generated preview artifact):",
1087
+ `Project: ${preview.projectPath}`,
1088
+ `Story: ${preview.storyPath}#${preview.storyExport}`,
1089
+ `Build revision: ${preview.buildRevision}`,
1090
+ `Viewport: ${String(preview.viewport.width)}x${String(preview.viewport.height)}`,
1091
+ "Sources:",
1092
+ sources,
1093
+ "After editing the underlying story or component source, rebuild the preview before treating it as visually verified."
1094
+ ].join("\n");
1095
+ }
1078
1096
  function authorCaptureContext(packet) {
1079
1097
  const metadata = JSON.stringify(packet);
1080
1098
  if (bytes(metadata) > 65536) throw new Error("Author capture packet exceeds 64 KiB.");
1081
- const content = packet.regions.map((region) => `(${String(region.ordinal)}) ${region.comment} [${anchorLocation(region.anchor)}]${region.quote ? `\nQuoted text: ${region.quote}` : ""}`).join("\n\n");
1099
+ const annotations = packet.regions.map((region) => `(${String(region.ordinal)}) ${region.comment} [${anchorLocation(region.anchor)}]${region.quote ? `\nQuoted text: ${region.quote}` : ""}`).join("\n\n");
1100
+ const provenance = storyPreviewProvenance(packet);
1101
+ const content = provenance === void 0 ? annotations : `${provenance}\n\nAnnotations:\n${annotations}`;
1082
1102
  return {
1083
1103
  kind: "author-capture",
1084
1104
  source: "author",
@@ -1356,6 +1376,7 @@ function anchorLabel(anchor) {
1356
1376
  case "image-rect": return "Image region";
1357
1377
  case "pdf-page-rect": return `Page ${anchor.page}`;
1358
1378
  case "video-time-rect": return `Video ${Math.floor(anchor.timeSeconds / 60)}:${Math.floor(anchor.timeSeconds % 60).toString().padStart(2, "0")}`;
1379
+ case "story-preview-rect": return `${anchor.preview.storyPath}#${anchor.preview.storyExport}`;
1359
1380
  }
1360
1381
  }
1361
1382
  function progressLabel(request) {
@@ -1613,7 +1634,7 @@ function AuthorToolPalette({ sessionId, kind, activeTool }) {
1613
1634
  }
1614
1635
  //#endregion
1615
1636
  //#region src/extensions/workspaces/sessions/(frontend)/dock/_components/AuthorPanel.tsx
1616
- function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses }) {
1637
+ function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses, renderSlot }) {
1617
1638
  const [captureStatus, setCaptureStatus] = useState();
1618
1639
  const [capturing, setCapturing] = useState(false);
1619
1640
  const documents = useStore(authorWorkspace.store, (state) => {
@@ -1624,82 +1645,89 @@ function AuthorPanel({ sessionId, activeMinorModes, submitCapture, statuses }) {
1624
1645
  const workspace = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
1625
1646
  const grid = useStore(authorGrid.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
1626
1647
  const focused = documents.find((document) => document.path === workspace?.focusedDocument?.path);
1627
- if (!activeMinorModes?.includes("author") || focused === void 0) return null;
1648
+ if (!activeMinorModes?.includes("author")) return null;
1628
1649
  return /* @__PURE__ */ jsxs("section", {
1629
1650
  "data-testid": "author-panel",
1630
1651
  className: "flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto p-3",
1631
- children: [sessionId !== null && workspace !== void 0 && focused !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1632
- /* @__PURE__ */ jsx("h2", {
1633
- className: "text-base font-semibold text-doom-text",
1634
- children: "Annotations"
1635
- }),
1636
- /* @__PURE__ */ jsx("p", {
1637
- className: "text-base leading-normal text-doom-dim sm:text-sm",
1638
- children: "Select a region, add a comment, then submit here. Requests queue while the agent is working."
1639
- }),
1640
- focused.kind === "video" ? /* @__PURE__ */ jsx("p", {
1641
- className: "text-base text-doom-dim sm:text-sm",
1642
- children: "Annotations reference video frames. They do not save changes to the source video."
1643
- }) : null,
1644
- /* @__PURE__ */ jsx(AuthorToolPalette, {
1645
- sessionId,
1646
- kind: focused.kind,
1647
- activeTool: workspace.activeTool
1652
+ children: [
1653
+ /* @__PURE__ */ jsx("div", {
1654
+ "data-testid": "author-preview-providers",
1655
+ children: renderSlot("author.preview-provider")
1648
1656
  }),
1649
- autonomousVoiceGridVisible(statuses) && grid !== void 0 ? /* @__PURE__ */ jsxs("div", {
1650
- "data-testid": "author-grid-snapshot",
1651
- className: "rounded border border-doom-red/40 bg-doom-red/5 p-2 text-base text-doom-dim",
1652
- children: [/* @__PURE__ */ jsx("strong", {
1653
- className: "text-doom-red",
1654
- children: "VOICE GRID A1–H8"
1655
- }), /* @__PURE__ */ jsxs("p", {
1656
- className: "mt-1 truncate",
1657
- children: ["token ", grid.geometryToken]
1658
- })]
1659
- }) : null,
1660
- /* @__PURE__ */ jsx(AuthorRegionDrafts, {
1661
- sessionId,
1662
- workspace
1663
- }, focused.path),
1664
- workspace.regions.length > 0 ? /* @__PURE__ */ jsxs("div", {
1665
- className: "space-y-1.5 border-b border-doom-border-soft pb-3",
1666
- children: [/* @__PURE__ */ jsx(Button, {
1667
- className: "min-h-11 min-w-11 w-full text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
1668
- variant: "outline",
1669
- "data-testid": "author-attach-capture",
1670
- disabled: capturing || workspace.candidate !== void 0 || submitCapture === void 0,
1671
- onClick: async () => {
1672
- if (capturing || workspace.candidate || submitCapture === void 0) return;
1673
- setCapturing(true);
1674
- setCaptureStatus("Submitting annotations…");
1675
- try {
1676
- const packet = createAuthorCapturePacket(crypto.randomUUID(), Date.now(), focused, workspace.regions);
1677
- await submitCapture({
1678
- ...await multiRegionCaptureProvider(workspace.regions).capture(),
1679
- context: authorCaptureContext(packet)
1680
- });
1681
- setCaptureStatus("Request submitted. You can keep annotating here.");
1682
- } catch (reason) {
1683
- setCaptureStatus(reason instanceof Error ? reason.message : String(reason));
1684
- } finally {
1685
- setCapturing(false);
1686
- }
1687
- },
1688
- children: capturing ? "Submitting…" : `Submit ${workspace.regions.length} annotation${workspace.regions.length === 1 ? "" : "s"}`
1689
- }), workspace.candidate ? /* @__PURE__ */ jsx("p", {
1657
+ sessionId !== null && workspace !== void 0 && focused !== void 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1658
+ /* @__PURE__ */ jsx("h2", {
1659
+ className: "text-base font-semibold text-doom-text",
1660
+ children: "Annotations"
1661
+ }),
1662
+ /* @__PURE__ */ jsx("p", {
1663
+ className: "text-base leading-normal text-doom-dim sm:text-sm",
1664
+ children: "Select a region, add a comment, then submit here. Requests queue while the agent is working."
1665
+ }),
1666
+ focused.kind === "video" ? /* @__PURE__ */ jsx("p", {
1690
1667
  className: "text-base text-doom-dim sm:text-sm",
1691
- children: "Add or discard the current selection before submitting."
1692
- }) : null]
1693
- }) : null,
1694
- submitCapture === void 0 ? /* @__PURE__ */ jsx("output", {
1695
- className: "text-sm text-doom-red",
1696
- children: "Submission unavailable. Reload to update the app."
1697
- }) : null,
1698
- captureStatus ? /* @__PURE__ */ jsx("output", {
1699
- className: "block text-sm text-doom-dim",
1700
- children: captureStatus
1701
- }) : null
1702
- ] }) : null, /* @__PURE__ */ jsx(AuthorRequestLog, { requests: workspace?.requests ?? [] })]
1668
+ children: "Annotations reference video frames. They do not save changes to the source video."
1669
+ }) : null,
1670
+ /* @__PURE__ */ jsx(AuthorToolPalette, {
1671
+ sessionId,
1672
+ kind: focused.kind,
1673
+ activeTool: workspace.activeTool
1674
+ }),
1675
+ autonomousVoiceGridVisible(statuses) && grid !== void 0 ? /* @__PURE__ */ jsxs("div", {
1676
+ "data-testid": "author-grid-snapshot",
1677
+ className: "rounded border border-doom-red/40 bg-doom-red/5 p-2 text-base text-doom-dim",
1678
+ children: [/* @__PURE__ */ jsx("strong", {
1679
+ className: "text-doom-red",
1680
+ children: "VOICE GRID A1–H8"
1681
+ }), /* @__PURE__ */ jsxs("p", {
1682
+ className: "mt-1 truncate",
1683
+ children: ["token ", grid.geometryToken]
1684
+ })]
1685
+ }) : null,
1686
+ /* @__PURE__ */ jsx(AuthorRegionDrafts, {
1687
+ sessionId,
1688
+ workspace
1689
+ }, focused.path),
1690
+ workspace.regions.length > 0 ? /* @__PURE__ */ jsxs("div", {
1691
+ className: "space-y-1.5 border-b border-doom-border-soft pb-3",
1692
+ children: [/* @__PURE__ */ jsx(Button, {
1693
+ className: "min-h-11 min-w-11 w-full text-base [@media(pointer:fine)]:min-h-8 sm:text-sm",
1694
+ variant: "outline",
1695
+ "data-testid": "author-attach-capture",
1696
+ disabled: capturing || workspace.candidate !== void 0 || submitCapture === void 0,
1697
+ onClick: async () => {
1698
+ if (capturing || workspace.candidate || submitCapture === void 0) return;
1699
+ setCapturing(true);
1700
+ setCaptureStatus("Submitting annotations…");
1701
+ try {
1702
+ const packet = createAuthorCapturePacket(crypto.randomUUID(), Date.now(), focused, workspace.regions);
1703
+ await submitCapture({
1704
+ ...await multiRegionCaptureProvider(workspace.regions).capture(),
1705
+ context: authorCaptureContext(packet)
1706
+ });
1707
+ setCaptureStatus("Request submitted. You can keep annotating here.");
1708
+ } catch (reason) {
1709
+ setCaptureStatus(reason instanceof Error ? reason.message : String(reason));
1710
+ } finally {
1711
+ setCapturing(false);
1712
+ }
1713
+ },
1714
+ children: capturing ? "Submitting…" : `Submit ${workspace.regions.length} annotation${workspace.regions.length === 1 ? "" : "s"}`
1715
+ }), workspace.candidate ? /* @__PURE__ */ jsx("p", {
1716
+ className: "text-base text-doom-dim sm:text-sm",
1717
+ children: "Add or discard the current selection before submitting."
1718
+ }) : null]
1719
+ }) : null,
1720
+ submitCapture === void 0 ? /* @__PURE__ */ jsx("output", {
1721
+ className: "text-sm text-doom-red",
1722
+ children: "Submission unavailable. Reload to update the app."
1723
+ }) : null,
1724
+ captureStatus ? /* @__PURE__ */ jsx("output", {
1725
+ className: "block text-sm text-doom-dim",
1726
+ children: captureStatus
1727
+ }) : null
1728
+ ] }) : null,
1729
+ /* @__PURE__ */ jsx(AuthorRequestLog, { requests: workspace?.requests ?? [] })
1730
+ ]
1703
1731
  });
1704
1732
  }
1705
1733
  //#endregion
@@ -1712,16 +1740,31 @@ var authoring_web_default = defineDockFace({
1712
1740
  panel: AuthorPanel,
1713
1741
  requiredMinorMode: "author",
1714
1742
  visibility: {
1715
- subscribe(listener) {
1716
- const subscription = authorWorkspace.store.subscribe(listener);
1717
- return () => subscription.unsubscribe();
1743
+ subscribe() {
1744
+ return () => void 0;
1718
1745
  },
1719
1746
  isVisible(sessionId) {
1720
- return sessionId !== null && authorWorkspace.store.state.sessions[sessionId]?.focusedDocument !== void 0;
1747
+ return sessionId !== null;
1721
1748
  }
1722
1749
  }
1723
1750
  });
1724
1751
  //#endregion
1752
+ //#region src/types/authorPreview.ts
1753
+ function parseAuthorPreviewAction(input) {
1754
+ if (typeof input !== "object" || input === null) return null;
1755
+ const candidate = input;
1756
+ if (candidate.version !== 1 || typeof candidate.label !== "string" || candidate.label.trim() === "") return null;
1757
+ if (candidate.detail !== void 0 && typeof candidate.detail !== "string") return null;
1758
+ if (typeof candidate.createTab !== "function") return null;
1759
+ if (candidate.supportsSource !== void 0 && typeof candidate.supportsSource !== "function") return null;
1760
+ if (candidate.embeddedPanel !== void 0 && typeof candidate.embeddedPanel !== "function") return null;
1761
+ return candidate;
1762
+ }
1763
+ const authorPreviewActionSlot = {
1764
+ slot: "author.preview-action",
1765
+ parse: parseAuthorPreviewAction
1766
+ };
1767
+ //#endregion
1725
1768
  //#region src/types/apiRoutes.ts
1726
1769
  /**
1727
1770
  * This package's routes, as data.
@@ -1856,10 +1899,12 @@ function structuredFormat(path) {
1856
1899
  if (lower.endsWith(".xlsx")) return "xlsx";
1857
1900
  }
1858
1901
  function authorKindForPath(path) {
1902
+ const lower = path.toLowerCase();
1903
+ if (/\.stories\.(?:js|jsx|ts|tsx)$/u.test(lower)) return "story-preview";
1859
1904
  const format = structuredFormat(path);
1860
1905
  if (format === "markdown-slides") return "slides";
1861
1906
  if (format !== void 0) return format;
1862
- const extension = path.split(".").at(-1)?.toLowerCase() ?? "";
1907
+ const extension = lower.split(".").at(-1) ?? "";
1863
1908
  if (extension === "md") return "markdown";
1864
1909
  if (IMAGE_EXTENSIONS.has(extension)) return "image";
1865
1910
  if (VIDEO_EXTENSIONS.has(extension)) return "video";
@@ -1927,7 +1972,7 @@ async function loadAuthorDocument(sessionId, path, signal) {
1927
1972
  };
1928
1973
  }
1929
1974
  const mediaUrl = kind === "image" || kind === "video" || kind === "pdf" || kind === "opaque" ? url : void 0;
1930
- const content = kind === "text" || kind === "markdown" ? await response.text() : void 0;
1975
+ const content = kind === "text" || kind === "markdown" || kind === "story-preview" ? await response.text() : void 0;
1931
1976
  return {
1932
1977
  path,
1933
1978
  kind,
@@ -1985,7 +2030,7 @@ async function croppedImageBytes(documentInput, signal) {
1985
2030
  }
1986
2031
  }
1987
2032
  async function bytesToSave(sessionId, document, signal) {
1988
- if (document.kind === "text" || document.kind === "markdown") return document.content ?? "";
2033
+ if (document.kind === "text" || document.kind === "markdown" || document.kind === "story-preview") return document.content ?? "";
1989
2034
  if (document.kind === "image" && document.crop !== void 0) return await croppedImageBytes(document, signal);
1990
2035
  if (document.structuredFormat === void 0) throw new Error("This Author view cannot be saved.");
1991
2036
  const operations = operationsFor(document);
@@ -3129,14 +3174,24 @@ function AuthorDocumentPanel(props) {
3129
3174
  }
3130
3175
  return /* @__PURE__ */ jsx(ActiveAuthorDocumentPanel, { ...props });
3131
3176
  }
3132
- function ActiveAuthorDocumentPanel({ path, sessionId, statuses }) {
3177
+ function ActiveAuthorDocumentPanel(props) {
3178
+ const { path, sessionId, statuses } = props;
3133
3179
  const document = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.documents[authorDocumentKey(sessionId, path)]);
3134
3180
  const focusGeneration = useRef(void 0);
3135
3181
  const workspace = useStore(authorWorkspace.store, (state) => sessionId === null ? void 0 : state.sessions[sessionId]);
3136
3182
  const activeTool = workspace?.activeTool ?? "select";
3137
3183
  const [markdownPreview, setMarkdownPreview] = useState(true);
3138
3184
  const [status, setStatus] = useState();
3185
+ const [previewOpen, setPreviewOpen] = useState(false);
3139
3186
  const kind = document?.kind;
3187
+ const previewSource = document === void 0 ? void 0 : {
3188
+ path: document.path,
3189
+ kind: document.kind,
3190
+ hasUnsavedChanges: document.version !== document.savedVersion,
3191
+ revision: document.savedVersion,
3192
+ sourceSha256: document.sourceSha256
3193
+ };
3194
+ const PreviewPanel = (previewSource === void 0 ? void 0 : (props.slotData?.(authorPreviewActionSlot) ?? []).find(({ data }) => data.embeddedPanel !== void 0 && (data.supportsSource === void 0 || data.supportsSource(previewSource))))?.data.embeddedPanel;
3140
3195
  useEffect(() => {
3141
3196
  if (document !== void 0 || sessionId === null) return;
3142
3197
  const controller = new AbortController();
@@ -3202,7 +3257,8 @@ function ActiveAuthorDocumentPanel({ path, sessionId, statuses }) {
3202
3257
  requestAuthorSave(sessionId, path);
3203
3258
  setStatus("saving");
3204
3259
  try {
3205
- completeAuthorSave(sessionId, path, await saveAuthorDocument(sessionId, document), savedVersion, savedFragments);
3260
+ const sha256 = await saveAuthorDocument(sessionId, document);
3261
+ completeAuthorSave(sessionId, path, sha256, savedVersion, savedFragments);
3206
3262
  setStatus("saved");
3207
3263
  } catch (error) {
3208
3264
  failAuthorSave(sessionId, path, savedVersion);
@@ -3217,6 +3273,18 @@ function ActiveAuthorDocumentPanel({ path, sessionId, statuses }) {
3217
3273
  preview: document.kind === "markdown" && markdownPreview && activeTool === "select" && !gridVisible,
3218
3274
  displayedRegions
3219
3275
  });
3276
+ const sourcePanel = document.kind === "text" || document.kind === "markdown" || document.kind === "story-preview" ? textPanel : document.structuredFormat !== void 0 ? /* @__PURE__ */ jsx(AuthorStructuredView, {
3277
+ sessionId,
3278
+ document,
3279
+ displayedRegions
3280
+ }) : /* @__PURE__ */ jsx(AuthorMediaView, {
3281
+ sessionId,
3282
+ document,
3283
+ activeTool,
3284
+ displayedRegions,
3285
+ pendingCandidate: workspace?.candidate !== void 0,
3286
+ seekRequest: workspace?.videoSeekRequest
3287
+ }, document.path);
3220
3288
  return /* @__PURE__ */ jsxs("section", {
3221
3289
  "data-testid": "author-document",
3222
3290
  className: "flex min-h-0 flex-1 flex-col",
@@ -3228,6 +3296,15 @@ function ActiveAuthorDocumentPanel({ path, sessionId, statuses }) {
3228
3296
  className: "min-w-0 flex-1 truncate text-base text-doom-hi",
3229
3297
  children: document.title ?? document.path
3230
3298
  }),
3299
+ PreviewPanel !== void 0 && previewSource !== void 0 ? /* @__PURE__ */ jsx(Button, {
3300
+ size: "xs",
3301
+ variant: previewOpen ? "primary" : "outline",
3302
+ "data-testid": "author-story-preview-toggle",
3303
+ "aria-pressed": previewOpen,
3304
+ "aria-label": previewOpen ? "show story source" : "show story preview",
3305
+ onClick: () => setPreviewOpen(!previewOpen),
3306
+ children: previewOpen ? "Source" : "Preview"
3307
+ }) : null,
3231
3308
  document.kind === "markdown" ? /* @__PURE__ */ jsx(Button, {
3232
3309
  size: "xs",
3233
3310
  variant: "ghost",
@@ -3244,7 +3321,7 @@ function ActiveAuthorDocumentPanel({ path, sessionId, statuses }) {
3244
3321
  "data-testid": "author-save",
3245
3322
  disabled: document.revisions.length === 0 || document.savingVersion !== void 0,
3246
3323
  onClick: () => void save(),
3247
- children: "Save"
3324
+ children: previewOpen && previewSource?.hasUnsavedChanges ? "Save & refresh" : "Save"
3248
3325
  })
3249
3326
  ]
3250
3327
  }),
@@ -3254,18 +3331,10 @@ function ActiveAuthorDocumentPanel({ path, sessionId, statuses }) {
3254
3331
  }),
3255
3332
  /* @__PURE__ */ jsxs("div", {
3256
3333
  className: "relative flex min-h-0 flex-1 overflow-hidden",
3257
- children: [document.kind === "text" || document.kind === "markdown" ? textPanel : document.structuredFormat !== void 0 ? /* @__PURE__ */ jsx(AuthorStructuredView, {
3258
- sessionId,
3259
- document,
3260
- displayedRegions
3261
- }) : /* @__PURE__ */ jsx(AuthorMediaView, {
3262
- sessionId,
3263
- document,
3264
- activeTool,
3265
- displayedRegions,
3266
- pendingCandidate: workspace?.candidate !== void 0,
3267
- seekRequest: workspace?.videoSeekRequest
3268
- }, document.path), /* @__PURE__ */ jsx(AuthorGridOverlay, {
3334
+ children: [previewOpen && PreviewPanel !== void 0 && previewSource !== void 0 ? /* @__PURE__ */ jsx(PreviewPanel, {
3335
+ ...props,
3336
+ source: previewSource
3337
+ }) : sourcePanel, previewOpen && PreviewPanel !== void 0 && previewSource !== void 0 ? null : /* @__PURE__ */ jsx(AuthorGridOverlay, {
3269
3338
  sessionId,
3270
3339
  document,
3271
3340
  visible: gridVisible
@@ -3411,6 +3480,21 @@ var mode_web_default = defineMinorModeFile({
3411
3480
  order: 45
3412
3481
  });
3413
3482
  //#endregion
3483
+ //#region src/extensions/workspaces/sessions/(frontend)/slot/preview-action.web.ts
3484
+ /** Typed data slot for preview launchers that need Author's focused source. */
3485
+ var preview_action_web_default = defineSlotFile({
3486
+ slot: "author.preview-action",
3487
+ parse: parseAuthorPreviewAction
3488
+ });
3489
+ //#endregion
3490
+ //#region src/extensions/workspaces/sessions/(frontend)/slot/preview-provider.web.ts
3491
+ /**
3492
+ * Optional preview providers render launch controls here. The provider owns its
3493
+ * transient tab and preview lifecycle; Author owns the surrounding mode and
3494
+ * composer submission surface.
3495
+ */
3496
+ var preview_provider_web_default = defineSlotFile({});
3497
+ //#endregion
3414
3498
  //#region src/extensions/workspaces/sessions/(frontend)/tool/_lib/describeAuthorToolsToolRender.ts
3415
3499
  function describeAuthorToolsCallSummary(_args) {
3416
3500
  return "";
@@ -3621,6 +3705,9 @@ const webPlugin = defineWebPlugin({
3621
3705
  get minorModes() {
3622
3706
  return [via({ name: "author" }, mode_web_default)];
3623
3707
  },
3708
+ get slots() {
3709
+ return [via({ slot: "author.preview-action" }, preview_action_web_default), via({ slot: "author.preview-provider" }, preview_provider_web_default)];
3710
+ },
3624
3711
  get start() {
3625
3712
  return start_web_default;
3626
3713
  },