@june-ai/june-ui 0.2.8 → 0.2.10

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.
@@ -22,49 +22,120 @@ import {
22
22
  } from "./chunk-27XM6EVX.js";
23
23
 
24
24
  // src/engine/openui-devtools.tsx
25
+ import { useEffect } from "react";
25
26
  import { OpenUIDevtools } from "@openuidev/devtools";
26
27
  import { jsx } from "react/jsx-runtime";
27
28
  var AUTO_MOUNT_FLAG = /* @__PURE__ */ Symbol.for("openui.devtools.autoMount");
28
29
  var BLOCKED_BY_JUNE = /* @__PURE__ */ Symbol.for("openui.devtools.blockedByJune");
30
+ var AUTO_MOUNT_HOST = "data-openui-devtools-auto-mount";
29
31
  var flags = globalThis;
30
- var scheduledByOpenUI = flags[AUTO_MOUNT_FLAG] === true && flags[BLOCKED_BY_JUNE] !== true;
31
32
  flags[AUTO_MOUNT_FLAG] = true;
32
33
  flags[BLOCKED_BY_JUNE] = true;
33
34
  var DEVTOOLS_BUNDLE = "https://cdn.jsdelivr.net/npm/@openuidev/devtools@0/dist/devtools.browser.js";
34
35
  var ALIAS_CACHE_MS = 5 * 60 * 1e3;
35
- function claimDisabledDevtools() {
36
- if (typeof document === "undefined") return () => {
37
- };
36
+ var LOCAL_WIDGET = "../../node_modules/@openuidev/devtools/dist/devtools.browser.js";
37
+ var loadFromCdn = () => {
38
38
  const url = `${DEVTOOLS_BUNDLE}?t=${Math.floor(Date.now() / ALIAS_CACHE_MS)}`;
39
- let cancelled = false;
40
- let unmount = () => {
41
- cancelled = true;
42
- };
43
- void Promise.all([
39
+ return Promise.all([
44
40
  import(
41
+ /* webpackIgnore: true */
45
42
  /* @vite-ignore */
46
43
  url
47
44
  ),
48
45
  import("react"),
49
46
  import("react-dom"),
50
47
  import("react-dom/client")
51
- ]).then(([devtools, react, reactDom, reactDomClient]) => {
52
- if (cancelled) return;
53
- unmount = devtools.mountOpenUIDevtools({
54
- React: react,
55
- ReactDOM: reactDom,
56
- ReactDOMClient: reactDomClient,
57
- loadReactLang: () => import("@openuidev/react-lang"),
58
- props: { enabled: false }
48
+ ]);
49
+ };
50
+ var loadInstalledOrCdn = async () => {
51
+ const [react, reactDom, reactDomClient] = await Promise.all([
52
+ import("react"),
53
+ import("react-dom"),
54
+ import("react-dom/client")
55
+ ]);
56
+ try {
57
+ const devtools = await import(LOCAL_WIDGET);
58
+ return [devtools, react, reactDom, reactDomClient];
59
+ } catch {
60
+ const [devtools] = await loadFromCdn();
61
+ return [devtools, react, reactDom, reactDomClient];
62
+ }
63
+ };
64
+ function nodeEnv() {
65
+ try {
66
+ return process.env.NODE_ENV;
67
+ } catch {
68
+ return void 0;
69
+ }
70
+ }
71
+ var isAutoMountHost = (node) => node instanceof Element && node.hasAttribute(AUTO_MOUNT_HOST);
72
+ function createDevtoolsGuard(load = loadInstalledOrCdn) {
73
+ let started = false;
74
+ let claimed = false;
75
+ let observer = null;
76
+ let unmount = null;
77
+ let stopped = false;
78
+ const claim = () => {
79
+ if (claimed) return;
80
+ claimed = true;
81
+ observer?.disconnect();
82
+ observer = null;
83
+ load().then(([devtools, react, reactDom, reactDomClient]) => {
84
+ if (stopped) return;
85
+ unmount = devtools.mountOpenUIDevtools({
86
+ React: react,
87
+ ReactDOM: reactDom,
88
+ ReactDOMClient: reactDomClient,
89
+ loadReactLang: () => import("@openuidev/react-lang"),
90
+ props: { enabled: false }
91
+ });
92
+ }).catch(() => {
59
93
  });
60
- }).catch(() => {
61
- });
62
- return () => unmount();
94
+ };
95
+ const watch = () => {
96
+ const body = document.body;
97
+ if (!body) return;
98
+ if (Array.from(body.children).some(isAutoMountHost)) {
99
+ claim();
100
+ return;
101
+ }
102
+ if (typeof MutationObserver === "undefined") return;
103
+ observer = new MutationObserver((records) => {
104
+ if (records.some((r) => Array.from(r.addedNodes).some(isAutoMountHost))) claim();
105
+ });
106
+ observer.observe(body, { childList: true });
107
+ };
108
+ return {
109
+ /** Starts watching (browser only). Safe to call many times. */
110
+ start() {
111
+ if (started || typeof document === "undefined") return;
112
+ started = true;
113
+ if (document.body) watch();
114
+ else document.addEventListener("DOMContentLoaded", watch, { once: true });
115
+ },
116
+ /** True once the disabled widget has been requested. */
117
+ get claimed() {
118
+ return claimed;
119
+ },
120
+ /** Stops watching and removes the disabled widget (tests, teardown). */
121
+ stop() {
122
+ stopped = true;
123
+ observer?.disconnect();
124
+ observer = null;
125
+ unmount?.();
126
+ unmount = null;
127
+ }
128
+ };
63
129
  }
64
- if (scheduledByOpenUI && process.env.NODE_ENV === "development" && process.env.VITEST !== "true" && typeof document !== "undefined") {
65
- claimDisabledDevtools();
130
+ var guard = createDevtoolsGuard();
131
+ function hideOpenUIDevtools() {
132
+ if (nodeEnv() === "development") guard.start();
66
133
  }
134
+ hideOpenUIDevtools();
67
135
  function OpenUIDevtoolsGate() {
136
+ useEffect(() => {
137
+ hideOpenUIDevtools();
138
+ }, []);
68
139
  return /* @__PURE__ */ jsx(OpenUIDevtools, { enabled: false });
69
140
  }
70
141
 
@@ -165,7 +236,7 @@ function useDocumentValue(store, id, selector) {
165
236
  }
166
237
 
167
238
  // src/features/document-editor/use-document-bridge.ts
168
- import { useCallback, useEffect, useMemo, useRef } from "react";
239
+ import { useCallback, useEffect as useEffect2, useMemo, useRef } from "react";
169
240
 
170
241
  // src/features/document-editor/document-merge.ts
171
242
  var MAX_CELLS = 4e6;
@@ -483,7 +554,7 @@ function useDocumentBridge(options) {
483
554
  const latest = useRef({ onResponseChange, responseChangeDelay, response, onJuneError });
484
555
  latest.current = { onResponseChange, responseChangeDelay, response, onJuneError };
485
556
  const timersRef = useRef(/* @__PURE__ */ new Map());
486
- useEffect(() => {
557
+ useEffect2(() => {
487
558
  const timers = timersRef.current;
488
559
  return () => {
489
560
  timers.forEach(clearTimeout);
@@ -652,8 +723,10 @@ var ATTACHABLE = /* @__PURE__ */ new Set([
652
723
  "ImageGallery",
653
724
  "InlineHeader",
654
725
  "LineChart",
726
+ "FollowUpBlock",
655
727
  "ListBlock",
656
728
  "MarkDownRenderer",
729
+ "SectionBlock",
657
730
  "Modal",
658
731
  "OverviewCardBlock",
659
732
  "PieChart",
@@ -674,7 +747,7 @@ var ATTACHABLE = /* @__PURE__ */ new Set([
674
747
  ]);
675
748
  function normalizeProgram(source) {
676
749
  const rewritten = rewriteCalls(source);
677
- return attachOrphans(rewritten);
750
+ return liftStackRoot(attachOrphans(rewritten));
678
751
  }
679
752
  function rewriteCalls(source) {
680
753
  let out = "";
@@ -818,7 +891,7 @@ function attachOrphans(source) {
818
891
  if (orphans.length === 0) return source;
819
892
  const names = orphans.map((stmt) => stmt.name);
820
893
  const root = stmts.find((stmt) => stmt.name === "root");
821
- if (!root) return `root = Stack([${names.join(", ")}])
894
+ if (!root) return `root = Card([${names.join(", ")}])
822
895
  ${text}`;
823
896
  const rawRhs = text.slice(root.rhsStart, root.end);
824
897
  const lead = /^\s*/.exec(rawRhs)?.[0] ?? "";
@@ -827,10 +900,22 @@ ${text}`;
827
900
  return text.slice(0, root.rhsStart) + lead + spliced + text.slice(root.end);
828
901
  }
829
902
  const body = fresh(new Set(stmts.map((stmt) => stmt.name)), "rootBody");
830
- const wrapped = `root = Stack([${body}, ${names.join(", ")}])
903
+ const wrapped = `root = Card([${body}, ${names.join(", ")}])
831
904
  ${body} = ${rawRhs.trim()}`;
832
905
  return text.slice(0, root.start) + wrapped + text.slice(root.end);
833
906
  }
907
+ function liftStackRoot(source) {
908
+ const stmts = parseStatements(source);
909
+ if (!stmts) return source;
910
+ const root = stmts.find((stmt) => stmt.name === "root");
911
+ if (!root) return source;
912
+ const rhs = source.slice(root.rhsStart, root.end).trim();
913
+ if (!/^Stack\s*\(/.test(rhs)) return source;
914
+ const body = fresh(new Set(stmts.map((stmt) => stmt.name)), "rootLayout");
915
+ const wrapped = `root = Card([${body}])
916
+ ${body} = ${rhs}`;
917
+ return source.slice(0, root.start) + wrapped + source.slice(root.end);
918
+ }
834
919
  function spliceFirstArray(rhs, names) {
835
920
  const match = /^([A-Za-z_]\w*)\s*\(/.exec(rhs);
836
921
  if (!match || match[1] !== "Stack" && match[1] !== "Card") return null;
@@ -971,7 +1056,7 @@ var STATEMENT_START = /^\$?[A-Za-z_]\w*\s*=/;
971
1056
  var PENDING_IDENTIFIER = /^\s*\$?[A-Za-z_]\w*\s*$/;
972
1057
  var FENCE = /^(`{3,})\s*([\w-]*)/;
973
1058
  var OPENUI_FENCE_LANGS = /* @__PURE__ */ new Set(["", "openui", "openui-lang", "openuilang"]);
974
- function scan(line, state) {
1059
+ function scan(line, state, preserveQuote = false) {
975
1060
  for (let i = 0; i < line.length; i++) {
976
1061
  const ch = line[i];
977
1062
  if (state.quote) {
@@ -985,7 +1070,95 @@ function scan(line, state) {
985
1070
  state.depth = Math.max(0, state.depth - 1);
986
1071
  }
987
1072
  }
988
- state.quote = null;
1073
+ if (!preserveQuote) state.quote = null;
1074
+ }
1075
+ function quoteClosesLater(lines, from, state) {
1076
+ const probe = { depth: state.depth, quote: state.quote };
1077
+ for (let i = from; i < lines.length; i++) {
1078
+ const line = lines[i];
1079
+ if (probe.depth === 0 && STATEMENT_START.test(line.trim())) return false;
1080
+ scan(line, probe, true);
1081
+ if (!probe.quote) return true;
1082
+ }
1083
+ return false;
1084
+ }
1085
+ function decodeLangString(raw) {
1086
+ let out = "";
1087
+ for (let i = 0; i < raw.length; i++) {
1088
+ const ch = raw[i];
1089
+ if (ch === "\\" && i + 1 < raw.length) {
1090
+ const next = raw[i + 1];
1091
+ out += next === "n" ? "\n" : next === "t" ? " " : next === "r" ? "\r" : next === "b" ? "\b" : next === "f" ? "\f" : next;
1092
+ i++;
1093
+ continue;
1094
+ }
1095
+ out += ch;
1096
+ }
1097
+ return out.replace(/\r/g, "");
1098
+ }
1099
+ function foldMultilineStrings(source) {
1100
+ let out = "";
1101
+ let i = 0;
1102
+ while (i < source.length) {
1103
+ if (source.startsWith('"""', i)) {
1104
+ const end = source.indexOf('"""', i + 3);
1105
+ if (end < 0) {
1106
+ const nl = source.indexOf("\n", i);
1107
+ if (nl < 0) {
1108
+ out += source.slice(i);
1109
+ break;
1110
+ }
1111
+ out += source.slice(i, nl + 1);
1112
+ i = nl + 1;
1113
+ continue;
1114
+ }
1115
+ out += JSON.stringify(source.slice(i + 3, end).replace(/\r/g, ""));
1116
+ i = end + 3;
1117
+ continue;
1118
+ }
1119
+ if (source[i] !== '"') {
1120
+ out += source[i];
1121
+ i++;
1122
+ continue;
1123
+ }
1124
+ let j = i + 1;
1125
+ let closed = false;
1126
+ let hasNewline = false;
1127
+ while (j < source.length) {
1128
+ const ch = source[j];
1129
+ if (ch === "\\") {
1130
+ j += 2;
1131
+ continue;
1132
+ }
1133
+ if (ch === "\n") {
1134
+ if (STATEMENT_START.test(source.slice(j + 1))) break;
1135
+ hasNewline = true;
1136
+ }
1137
+ if (ch === '"') {
1138
+ closed = true;
1139
+ break;
1140
+ }
1141
+ j++;
1142
+ }
1143
+ if (!closed) {
1144
+ const nl = source.indexOf("\n", i);
1145
+ if (nl < 0) {
1146
+ out += source.slice(i);
1147
+ break;
1148
+ }
1149
+ out += source.slice(i, nl + 1);
1150
+ i = nl + 1;
1151
+ continue;
1152
+ }
1153
+ if (!hasNewline) {
1154
+ out += source.slice(i, j + 1);
1155
+ i = j + 1;
1156
+ continue;
1157
+ }
1158
+ out += JSON.stringify(decodeLangString(source.slice(i + 1, j)));
1159
+ i = j + 1;
1160
+ }
1161
+ return out;
989
1162
  }
990
1163
  function splitResponse(source, options = {}) {
991
1164
  const markdown = options.markdown === true;
@@ -1007,8 +1180,15 @@ function splitResponse(source, options = {}) {
1007
1180
  text = [];
1008
1181
  if (joined) (seenUi ? after : before).push(joined);
1009
1182
  };
1010
- for (const line of input.split(/\r?\n/)) {
1183
+ const lines = input.split(/\r?\n/);
1184
+ for (let index = 0; index < lines.length; index++) {
1185
+ const line = lines[index];
1011
1186
  const trimmed = line.trim();
1187
+ if (state.quote) {
1188
+ ui.push(line);
1189
+ scan(line, state, true);
1190
+ continue;
1191
+ }
1012
1192
  if (codeFence !== null) {
1013
1193
  text.push(line);
1014
1194
  if (trimmed.startsWith(codeFence)) {
@@ -1037,7 +1217,8 @@ function splitResponse(source, options = {}) {
1037
1217
  flushText();
1038
1218
  seenUi = true;
1039
1219
  ui.push(line);
1040
- scan(line, state);
1220
+ scan(line, state, true);
1221
+ if (state.quote && !quoteClosesLater(lines, index + 1, state)) state.quote = null;
1041
1222
  continue;
1042
1223
  }
1043
1224
  if (trimmed === "") {
@@ -1049,7 +1230,7 @@ function splitResponse(source, options = {}) {
1049
1230
  flushText();
1050
1231
  return {
1051
1232
  before,
1052
- ui: ui.join("\n"),
1233
+ ui: foldMultilineStrings(ui.join("\n")),
1053
1234
  after,
1054
1235
  pure: !sawFence && before.length === 0 && after.length === 0
1055
1236
  };
@@ -1059,7 +1240,7 @@ function splitResponse(source, options = {}) {
1059
1240
  function resolveMixedContent(response, options = {}) {
1060
1241
  const { mixedContent = true, isStreaming = false, repair = true, markdown = false } = options;
1061
1242
  const parts = mixedContent && response ? splitResponse(response, { streaming: isStreaming, markdown }) : null;
1062
- const raw = parts && !parts.pure ? parts.ui : response;
1243
+ const raw = parts && !parts.pure ? parts.ui : typeof response === "string" ? foldMultilineStrings(response) : response;
1063
1244
  const ui = !repair || !mixedContent || isStreaming || typeof raw !== "string" || raw.length === 0 ? raw : normalizeProgram(raw);
1064
1245
  const before = parts?.before ?? [];
1065
1246
  const after = parts?.after ?? [];
@@ -1558,7 +1739,7 @@ function defineJuneAction(definition) {
1558
1739
  }
1559
1740
 
1560
1741
  // src/features/streaming/use-june-stream.ts
1561
- import { useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo4, useRef as useRef3, useState } from "react";
1742
+ import { useCallback as useCallback3, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState } from "react";
1562
1743
 
1563
1744
  // src/features/streaming/stream-handler.ts
1564
1745
  function isReadableStream(source) {
@@ -1813,7 +1994,7 @@ function useJuneStream(options = {}) {
1813
1994
  clearTimer();
1814
1995
  setState(INITIAL);
1815
1996
  }, [clearTimer]);
1816
- useEffect2(
1997
+ useEffect3(
1817
1998
  () => () => {
1818
1999
  runIdRef.current++;
1819
2000
  controllerRef.current?.abort();
@@ -1850,7 +2031,7 @@ function DocumentDraft({ title, words, state }) {
1850
2031
 
1851
2032
  // src/features/document-editor/document-editor.tsx
1852
2033
  import { useEditor } from "@tiptap/react";
1853
- import { useCallback as useCallback4, useEffect as useEffect6, useId, useMemo as useMemo5, useRef as useRef8, useState as useState6 } from "react";
2034
+ import { useCallback as useCallback4, useEffect as useEffect7, useId, useMemo as useMemo5, useRef as useRef8, useState as useState6 } from "react";
1854
2035
 
1855
2036
  // src/features/document-editor/document-bubble.tsx
1856
2037
  import { useEditorState as useEditorState2 } from "@tiptap/react";
@@ -2030,6 +2211,10 @@ var PATHS = {
2030
2211
  /* @__PURE__ */ jsx8("path", { d: "M18 6 6 18" }),
2031
2212
  /* @__PURE__ */ jsx8("path", { d: "m6 6 12 12" })
2032
2213
  ] }),
2214
+ pencil: /* @__PURE__ */ jsxs3(Fragment2, { children: [
2215
+ /* @__PURE__ */ jsx8("path", { d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z" }),
2216
+ /* @__PURE__ */ jsx8("path", { d: "m15 5 4 4" })
2217
+ ] }),
2033
2218
  maximize: /* @__PURE__ */ jsxs3(Fragment2, { children: [
2034
2219
  /* @__PURE__ */ jsx8("path", { d: "M8 3H5a2 2 0 0 0-2 2v3" }),
2035
2220
  /* @__PURE__ */ jsx8("path", { d: "M21 8V5a2 2 0 0 0-2-2h-3" }),
@@ -2082,12 +2267,12 @@ function Icon({ name, size = 16 }) {
2082
2267
  }
2083
2268
 
2084
2269
  // src/features/document-editor/document-popover.tsx
2085
- import { useEffect as useEffect3, useRef as useRef4, useState as useState2 } from "react";
2270
+ import { useEffect as useEffect4, useRef as useRef4, useState as useState2 } from "react";
2086
2271
  import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
2087
2272
  function Dropdown({ trigger, children, align = "start", className, onOpen }) {
2088
2273
  const [open, setOpen] = useState2(false);
2089
2274
  const anchor = useRef4(null);
2090
- useEffect3(() => {
2275
+ useEffect4(() => {
2091
2276
  if (!open) return;
2092
2277
  const onDown = (e) => {
2093
2278
  if (anchor.current && !anchor.current.contains(e.target)) setOpen(false);
@@ -2707,13 +2892,13 @@ function DocumentBubbleMenu({ editor }) {
2707
2892
 
2708
2893
  // src/features/document-editor/document-content.tsx
2709
2894
  import { EditorContent } from "@tiptap/react";
2710
- import { useEffect as useEffect4, useLayoutEffect, useRef as useRef6, useState as useState4 } from "react";
2895
+ import { useEffect as useEffect5, useLayoutEffect, useRef as useRef6, useState as useState4 } from "react";
2711
2896
  import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2712
2897
  var PAGE_WIDTH = 794;
2713
2898
  var PAGE_HEIGHT = 1123;
2714
2899
  var PAGE_GAP = 28;
2715
2900
  var CANVAS_PAD_X = 24;
2716
- var useIsoLayoutEffect = typeof window === "undefined" ? useEffect4 : useLayoutEffect;
2901
+ var useIsoLayoutEffect = typeof window === "undefined" ? useEffect5 : useLayoutEffect;
2717
2902
  function geometry(width) {
2718
2903
  const ratio = width / PAGE_WIDTH;
2719
2904
  const height = Math.round(PAGE_HEIGHT * ratio);
@@ -2749,7 +2934,7 @@ function DocumentContent({ editor, layout = "page", zoom = 1, showMarks = false,
2749
2934
  ro.observe(c);
2750
2935
  return () => ro.disconnect();
2751
2936
  }, []);
2752
- useEffect4(() => {
2937
+ useEffect5(() => {
2753
2938
  if (!editor) return;
2754
2939
  setPaginationConfig(editor, {
2755
2940
  enabled: paged,
@@ -2759,7 +2944,7 @@ function DocumentContent({ editor, layout = "page", zoom = 1, showMarks = false,
2759
2944
  padBottom: g.padY
2760
2945
  });
2761
2946
  }, [editor, paged, g.height, g.padY, width]);
2762
- useEffect4(() => {
2947
+ useEffect5(() => {
2763
2948
  const c = canvas.current;
2764
2949
  const s = sheet.current;
2765
2950
  if (!editor || !c || !s) return;
@@ -2815,7 +3000,7 @@ function DocumentContent({ editor, layout = "page", zoom = 1, showMarks = false,
2815
3000
 
2816
3001
  // src/features/document-editor/document-findbar.tsx
2817
3002
  import { useEditorState as useEditorState3 } from "@tiptap/react";
2818
- import { useEffect as useEffect5, useRef as useRef7, useState as useState5 } from "react";
3003
+ import { useEffect as useEffect6, useRef as useRef7, useState as useState5 } from "react";
2819
3004
  import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2820
3005
  function DocumentFindBar({ editor, editable, onClose, onNotice }) {
2821
3006
  const [term, setTerm] = useState5(() => {
@@ -2832,14 +3017,14 @@ function DocumentFindBar({ editor, editable, onClose, onNotice }) {
2832
3017
  return { count: s?.matches.length ?? 0, current: s?.current ?? -1 };
2833
3018
  }
2834
3019
  });
2835
- useEffect5(() => {
3020
+ useEffect6(() => {
2836
3021
  input.current?.focus();
2837
3022
  input.current?.select();
2838
3023
  }, []);
2839
- useEffect5(() => {
3024
+ useEffect6(() => {
2840
3025
  setSearch(editor, { term, caseSensitive, current: 0 });
2841
3026
  }, [editor, term, caseSensitive]);
2842
- useEffect5(() => () => {
3027
+ useEffect6(() => () => {
2843
3028
  if (!editor.isDestroyed) setSearch(editor, { term: "" });
2844
3029
  }, [editor]);
2845
3030
  const count = info?.count ?? 0;
@@ -3534,7 +3719,7 @@ function DocumentEditorImpl(props) {
3534
3719
  return { json: null, error: toJuneError(e, "DOCUMENT_ERROR") };
3535
3720
  }
3536
3721
  }, [contentKey]);
3537
- useEffect6(() => {
3722
+ useEffect7(() => {
3538
3723
  if (parsed.error) optionsRef.current.onError?.(parsed.error);
3539
3724
  }, [parsed.error]);
3540
3725
  const title = useDocumentValue(store, documentId, (r) => r?.title ?? titleProp);
@@ -3550,7 +3735,7 @@ function DocumentEditorImpl(props) {
3550
3735
  const [findOpen, setFindOpen] = useState6(false);
3551
3736
  const [zoom, setZoom] = useState6(1);
3552
3737
  const [metrics, setMetrics] = useState6({ page: 1, pages: 1 });
3553
- useEffect6(() => {
3738
+ useEffect7(() => {
3554
3739
  if (!fullscreen || typeof document === "undefined") return;
3555
3740
  const prev = document.body.style.overflow;
3556
3741
  document.body.style.overflow = "hidden";
@@ -3564,7 +3749,7 @@ function DocumentEditorImpl(props) {
3564
3749
  const flash = useCallback4((message) => {
3565
3750
  setNotice(message);
3566
3751
  }, []);
3567
- useEffect6(() => {
3752
+ useEffect7(() => {
3568
3753
  if (!notice) return;
3569
3754
  const t = setTimeout(() => setNotice(null), 3e3);
3570
3755
  return () => clearTimeout(t);
@@ -3582,7 +3767,7 @@ function DocumentEditorImpl(props) {
3582
3767
  const delay = options.autosaveDelay ?? 1500;
3583
3768
  return delay > 0 ? debounce(() => void saveRef.current(), delay) : null;
3584
3769
  }, [options.autosaveDelay]);
3585
- useEffect6(() => () => autosave?.flush(), [autosave]);
3770
+ useEffect7(() => () => autosave?.flush(), [autosave]);
3586
3771
  saveRef.current = async () => {
3587
3772
  const record = store.get(documentId);
3588
3773
  const onSave = optionsRef.current.onSave;
@@ -3624,7 +3809,7 @@ function DocumentEditorImpl(props) {
3624
3809
  },
3625
3810
  [autosave]
3626
3811
  );
3627
- useEffect6(() => {
3812
+ useEffect7(() => {
3628
3813
  if (saveState !== "saved" || options.onSave) return;
3629
3814
  const t = setTimeout(() => setSaveState((s) => s === "saved" ? "idle" : s), 2500);
3630
3815
  return () => clearTimeout(t);
@@ -3653,11 +3838,11 @@ function DocumentEditorImpl(props) {
3653
3838
  },
3654
3839
  onUpdate: ({ editor: ed }) => onUpdateRef.current(ed.getJSON())
3655
3840
  });
3656
- useEffect6(() => {
3841
+ useEffect7(() => {
3657
3842
  if (!editor || editor.isEditable === editable) return;
3658
3843
  editor.setEditable(editable);
3659
3844
  }, [editor, editable]);
3660
- useEffect6(() => {
3845
+ useEffect7(() => {
3661
3846
  if (!parsed.json) return;
3662
3847
  if (isDocumentEcho(store, documentId, contentKey)) return;
3663
3848
  const outcome = store.receive({
@@ -3679,7 +3864,7 @@ function DocumentEditorImpl(props) {
3679
3864
  });
3680
3865
  }
3681
3866
  }, [store, documentId, titleProp, parsed.json, documentType, status, emitChange, markDirty]);
3682
- useEffect6(() => {
3867
+ useEffect7(() => {
3683
3868
  if (!editor) return;
3684
3869
  const apply = () => {
3685
3870
  const record = store.get(documentId);
@@ -3696,7 +3881,7 @@ function DocumentEditorImpl(props) {
3696
3881
  apply();
3697
3882
  return store.subscribe(apply);
3698
3883
  }, [editor, store, documentId, instanceId]);
3699
- useEffect6(() => {
3884
+ useEffect7(() => {
3700
3885
  if (!editor || readyRef.current) return;
3701
3886
  const record = store.get(documentId);
3702
3887
  if (!record) return;
@@ -3855,8 +4040,98 @@ function DocumentEditorImpl(props) {
3855
4040
  );
3856
4041
  }
3857
4042
 
4043
+ // src/features/document-editor/document-preview.tsx
4044
+ import { useEffect as useEffect8, useMemo as useMemo6, useState as useState7 } from "react";
4045
+ import { createPortal } from "react-dom";
4046
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
4047
+ function DocumentPreview(props) {
4048
+ const { documentId, title = "Untitled document", content, editable = true } = props;
4049
+ const { theme: contextTheme } = useJuneTheme();
4050
+ const resolvedTheme = props.theme && props.theme !== "auto" ? props.theme : contextTheme;
4051
+ const ctxOptions = useDocumentOptions();
4052
+ const store = useMemo6(() => props.options?.store ?? ctxOptions.store ?? getDefaultDocumentStore(), [props.options?.store, ctxOptions.store]);
4053
+ const storedJson = useDocumentValue(store, documentId, (record) => record?.json);
4054
+ const [open, setOpen] = useState7(false);
4055
+ const [preferStore, setPreferStore] = useState7(false);
4056
+ const contentKey = typeof content === "string" ? content : content ? JSON.stringify(content) : "";
4057
+ useEffect8(() => {
4058
+ setPreferStore(false);
4059
+ }, [contentKey]);
4060
+ const html = useMemo6(() => {
4061
+ try {
4062
+ const fromContent = contentKey ? parseDocumentContent(contentKey) : null;
4063
+ const doc = preferStore && storedJson ? storedJson : fromContent;
4064
+ return doc ? docToHtml(doc) : "";
4065
+ } catch {
4066
+ return "";
4067
+ }
4068
+ }, [contentKey, preferStore, storedJson]);
4069
+ useEffect8(() => {
4070
+ if (!open || typeof document === "undefined") return;
4071
+ const prev = document.body.style.overflow;
4072
+ document.body.style.overflow = "hidden";
4073
+ return () => {
4074
+ document.body.style.overflow = prev;
4075
+ };
4076
+ }, [open]);
4077
+ const close = () => {
4078
+ setPreferStore(true);
4079
+ setOpen(false);
4080
+ };
4081
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
4082
+ !open ? /* @__PURE__ */ jsxs11("section", { className: "june-doc june-doc-card", "data-theme": resolvedTheme, "aria-label": title, children: [
4083
+ /* @__PURE__ */ jsxs11("div", { className: "june-doc-card__bar", children: [
4084
+ /* @__PURE__ */ jsx17("span", { className: "june-doc-card__title", children: title }),
4085
+ editable ? /* @__PURE__ */ jsx17("button", { type: "button", className: "june-doc__btn june-doc__btn--icon june-doc__btn--ghost", "aria-label": "Edit document", title: "Edit", onClick: () => setOpen(true), children: /* @__PURE__ */ jsx17(Icon, { name: "pencil", size: 16 }) }) : null
4086
+ ] }),
4087
+ /* @__PURE__ */ jsx17("div", { className: "june-doc-card__canvas", children: /* @__PURE__ */ jsx17("div", { className: "june-doc-card__sheet", children: /* @__PURE__ */ jsx17("div", { className: "june-doc__prose", dangerouslySetInnerHTML: { __html: html } }) }) })
4088
+ ] }) : null,
4089
+ open && typeof document !== "undefined" ? createPortal(
4090
+ /* @__PURE__ */ jsx17("div", { className: "june-doc-modal", "data-theme": resolvedTheme, onMouseDown: close, children: /* @__PURE__ */ jsxs11(
4091
+ "div",
4092
+ {
4093
+ className: "june-doc june-doc-modal__panel",
4094
+ role: "dialog",
4095
+ "aria-modal": "true",
4096
+ "aria-label": title,
4097
+ tabIndex: -1,
4098
+ "data-theme": resolvedTheme,
4099
+ onMouseDown: (event) => event.stopPropagation(),
4100
+ onKeyDown: (event) => {
4101
+ if (event.key !== "Escape" || event.defaultPrevented) return;
4102
+ if (event.currentTarget.querySelector(".june-doc--fullscreen")) return;
4103
+ close();
4104
+ },
4105
+ children: [
4106
+ /* @__PURE__ */ jsx17("div", { className: "june-doc-modal__bar", children: /* @__PURE__ */ jsxs11("button", { type: "button", className: "june-doc__btn june-doc__btn--ghost", "aria-label": "Close document", onClick: close, children: [
4107
+ /* @__PURE__ */ jsx17(Icon, { name: "close", size: 16 }),
4108
+ /* @__PURE__ */ jsx17("span", { children: "Close" })
4109
+ ] }) }),
4110
+ /* @__PURE__ */ jsx17("div", { className: "june-doc-modal__body", children: /* @__PURE__ */ jsx17(
4111
+ DocumentEditorImpl,
4112
+ {
4113
+ documentId: props.documentId,
4114
+ title: props.title,
4115
+ content: props.content,
4116
+ documentType: props.documentType,
4117
+ status: props.status,
4118
+ editable: props.editable,
4119
+ exportFormats: props.exportFormats,
4120
+ theme: props.theme,
4121
+ generating: props.generating,
4122
+ options: props.options
4123
+ }
4124
+ ) })
4125
+ ]
4126
+ }
4127
+ ) }),
4128
+ document.body
4129
+ ) : null
4130
+ ] });
4131
+ }
4132
+
3858
4133
  // src/features/document-editor/document-component.tsx
3859
- import { jsx as jsx17 } from "react/jsx-runtime";
4134
+ import { jsx as jsx18 } from "react/jsx-runtime";
3860
4135
  var DOCUMENT_COMPONENT_DESCRIPTION = 'Custom component. An editable document. DocumentEditor(documentId, title, content, documentType?, status?, editable?, exportFormats?). Place it in the root container (Card or Stack). "content" is the full document body as one Markdown string. Reuse documentId when revising the same document. Use it only when the user asks to create or revise a document.';
3861
4136
  var documentProps = z.object({
3862
4137
  documentId: z.string().describe("Stable id. Reuse it when revising the same document."),
@@ -3870,23 +4145,24 @@ var documentProps = z.object({
3870
4145
  function DocumentEditor(props) {
3871
4146
  const { isStreaming } = useJuneRuntime();
3872
4147
  if (props.generating || isStreaming) {
3873
- return /* @__PURE__ */ jsx17(DocumentDraft, { title: props.title, state: "drafting" });
4148
+ return /* @__PURE__ */ jsx18(DocumentDraft, { title: props.title, state: "drafting" });
3874
4149
  }
3875
- return /* @__PURE__ */ jsx17(DocumentEditorImpl, { ...props });
4150
+ return /* @__PURE__ */ jsx18(DocumentEditorImpl, { ...props });
3876
4151
  }
3877
4152
  function OpenUIDocument({ props }) {
3878
- return /* @__PURE__ */ jsx17(
3879
- DocumentEditor,
3880
- {
3881
- documentId: props.documentId,
3882
- title: props.title,
3883
- content: props.content,
3884
- documentType: props.documentType,
3885
- status: props.status,
3886
- editable: props.editable,
3887
- exportFormats: props.exportFormats
3888
- }
3889
- );
4153
+ const { isStreaming } = useJuneRuntime();
4154
+ const shared = {
4155
+ documentId: props.documentId,
4156
+ title: props.title,
4157
+ content: props.content,
4158
+ documentType: props.documentType,
4159
+ status: props.status,
4160
+ editable: props.editable,
4161
+ exportFormats: props.exportFormats
4162
+ };
4163
+ if (isStreaming) return /* @__PURE__ */ jsx18(DocumentDraft, { title: props.title, state: "drafting" });
4164
+ if (props.editable === false) return /* @__PURE__ */ jsx18(DocumentEditor, { ...shared });
4165
+ return /* @__PURE__ */ jsx18(DocumentPreview, { ...shared });
3890
4166
  }
3891
4167
  var JuneDocumentEditorComponent = defineComponent3({
3892
4168
  name: "DocumentEditor",
@@ -3937,8 +4213,8 @@ function getDocumentEditorInstructions(options = {}) {
3937
4213
  const rules = getDocumentEditorRules(options);
3938
4214
  return ["Document drafting:", ...rules.map((rule, i) => `${i + 1}. ${rule}`)].join("\n");
3939
4215
  }
3940
- var EXAMPLE_NEW = 'root = Stack([doc])\ndoc = DocumentEditor("business-proposal", "Business Proposal", "## Executive Summary\\n\\nThis proposal outlines [SERVICE] for [CLIENT NAME].\\n\\n## Scope of Work\\n\\n- Discovery\\n- Delivery\\n\\n## Pricing\\n\\n| Item | Cost |\\n| --- | --- |\\n| Discovery | [AMOUNT] |", "proposal", "draft")';
3941
- var EXAMPLE_EDIT = 'root = Stack([doc])\ndoc = DocumentEditor("business-proposal", "Business Proposal", "## Executive Summary\\n\\nThis proposal outlines [SERVICE] for [CLIENT NAME].\\n\\n## Scope of Work\\n\\n- Discovery\\n- Delivery\\n\\n## Pricing\\n\\n| Item | Cost |\\n| --- | --- |\\n| Discovery | [AMOUNT] |\\n| Delivery | [AMOUNT] |", "proposal", "draft")';
4216
+ var EXAMPLE_NEW = 'root = Card([doc])\ndoc = DocumentEditor("business-proposal", "Business Proposal", "## Executive Summary\\n\\nThis proposal outlines [SERVICE] for [CLIENT NAME].\\n\\n## Scope of Work\\n\\n- Discovery\\n- Delivery\\n\\n## Pricing\\n\\n| Item | Cost |\\n| --- | --- |\\n| Discovery | [AMOUNT] |", "proposal", "draft")';
4217
+ var EXAMPLE_EDIT = 'root = Card([doc])\ndoc = DocumentEditor("business-proposal", "Business Proposal", "## Executive Summary\\n\\nThis proposal outlines [SERVICE] for [CLIENT NAME].\\n\\n## Scope of Work\\n\\n- Discovery\\n- Delivery\\n\\n## Pricing\\n\\n| Item | Cost |\\n| --- | --- |\\n| Discovery | [AMOUNT] |\\n| Delivery | [AMOUNT] |", "proposal", "draft")';
3942
4218
  function documentPromptOptions(options = {}) {
3943
4219
  return {
3944
4220
  additionalRules: getDocumentEditorRules(options),
@@ -3959,8 +4235,8 @@ ${docToMarkdown2(r.json)}
3959
4235
  }
3960
4236
 
3961
4237
  // src/features/tool-approval/tool-approval.tsx
3962
- import { useId as useId2, useState as useState7 } from "react";
3963
- import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs11 } from "react/jsx-runtime";
4238
+ import { useId as useId2, useState as useState8 } from "react";
4239
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
3964
4240
  var defaultToolApprovalLabels = {
3965
4241
  approve: "Approve",
3966
4242
  reject: "Reject",
@@ -3998,8 +4274,8 @@ function ToolApproval({
3998
4274
  }) {
3999
4275
  const labels = { ...defaultToolApprovalLabels, ...labelOverrides };
4000
4276
  const options = request.options?.length ? request.options : ["approve", "reject"];
4001
- const [draft, setDraft] = useState7(null);
4002
- const [draftError, setDraftError] = useState7(null);
4277
+ const [draft, setDraft] = useState8(null);
4278
+ const [draftError, setDraftError] = useState8(null);
4003
4279
  const titleId = useId2();
4004
4280
  const pending = status === "pending";
4005
4281
  const decide = (decision) => {
@@ -4017,7 +4293,7 @@ function ToolApproval({
4017
4293
  setDraft(null);
4018
4294
  decide({ choice: "edit", editedArguments });
4019
4295
  };
4020
- return /* @__PURE__ */ jsxs11(
4296
+ return /* @__PURE__ */ jsxs12(
4021
4297
  "section",
4022
4298
  {
4023
4299
  className: className ? `june-approval ${className}` : "june-approval",
@@ -4025,14 +4301,14 @@ function ToolApproval({
4025
4301
  "data-status": status,
4026
4302
  "data-risk": request.risk,
4027
4303
  children: [
4028
- /* @__PURE__ */ jsxs11("header", { className: "june-approval__header", children: [
4029
- /* @__PURE__ */ jsx18("strong", { id: titleId, className: "june-approval__tool", children: request.tool }),
4030
- request.risk && /* @__PURE__ */ jsx18("span", { className: "june-approval__badge", "data-risk": request.risk, children: labels.risk(request.risk) }),
4031
- request.sideEffect && /* @__PURE__ */ jsx18("span", { className: "june-approval__badge", children: labels.sideEffect[request.sideEffect] })
4304
+ /* @__PURE__ */ jsxs12("header", { className: "june-approval__header", children: [
4305
+ /* @__PURE__ */ jsx19("strong", { id: titleId, className: "june-approval__tool", children: request.tool }),
4306
+ request.risk && /* @__PURE__ */ jsx19("span", { className: "june-approval__badge", "data-risk": request.risk, children: labels.risk(request.risk) }),
4307
+ request.sideEffect && /* @__PURE__ */ jsx19("span", { className: "june-approval__badge", children: labels.sideEffect[request.sideEffect] })
4032
4308
  ] }),
4033
- request.description && /* @__PURE__ */ jsx18("p", { className: "june-approval__description", children: request.description }),
4034
- draft !== null ? /* @__PURE__ */ jsxs11("div", { className: "june-approval__edit", children: [
4035
- /* @__PURE__ */ jsx18(
4309
+ request.description && /* @__PURE__ */ jsx19("p", { className: "june-approval__description", children: request.description }),
4310
+ draft !== null ? /* @__PURE__ */ jsxs12("div", { className: "june-approval__edit", children: [
4311
+ /* @__PURE__ */ jsx19(
4036
4312
  "textarea",
4037
4313
  {
4038
4314
  "aria-label": labels.edit,
@@ -4043,12 +4319,12 @@ function ToolApproval({
4043
4319
  spellCheck: false
4044
4320
  }
4045
4321
  ),
4046
- draftError && /* @__PURE__ */ jsx18("p", { role: "alert", className: "june-approval__error", children: draftError })
4047
- ] }) : hasArguments(request.arguments) ? renderArguments ? renderArguments(request.arguments) : /* @__PURE__ */ jsx18("pre", { className: "june-approval__args", children: format(request.arguments) }) : null,
4048
- pending ? /* @__PURE__ */ jsx18("div", { className: "june-approval__actions", children: draft !== null ? /* @__PURE__ */ jsxs11(Fragment5, { children: [
4049
- /* @__PURE__ */ jsx18("button", { type: "button", className: "june-approval__button", "data-variant": "primary", onClick: saveEdit, disabled, children: labels.save }),
4050
- /* @__PURE__ */ jsx18("button", { type: "button", className: "june-approval__button", onClick: () => setDraft(null), disabled, children: labels.cancel })
4051
- ] }) : options.map((choice) => /* @__PURE__ */ jsx18(
4322
+ draftError && /* @__PURE__ */ jsx19("p", { role: "alert", className: "june-approval__error", children: draftError })
4323
+ ] }) : hasArguments(request.arguments) ? renderArguments ? renderArguments(request.arguments) : /* @__PURE__ */ jsx19("pre", { className: "june-approval__args", children: format(request.arguments) }) : null,
4324
+ pending ? /* @__PURE__ */ jsx19("div", { className: "june-approval__actions", children: draft !== null ? /* @__PURE__ */ jsxs12(Fragment6, { children: [
4325
+ /* @__PURE__ */ jsx19("button", { type: "button", className: "june-approval__button", "data-variant": "primary", onClick: saveEdit, disabled, children: labels.save }),
4326
+ /* @__PURE__ */ jsx19("button", { type: "button", className: "june-approval__button", onClick: () => setDraft(null), disabled, children: labels.cancel })
4327
+ ] }) : options.map((choice) => /* @__PURE__ */ jsx19(
4052
4328
  "button",
4053
4329
  {
4054
4330
  type: "button",
@@ -4059,7 +4335,7 @@ function ToolApproval({
4059
4335
  children: labels[choice]
4060
4336
  },
4061
4337
  choice
4062
- )) }) : /* @__PURE__ */ jsxs11("p", { className: "june-approval__status", role: "status", children: [
4338
+ )) }) : /* @__PURE__ */ jsxs12("p", { className: "june-approval__status", role: "status", children: [
4063
4339
  labels.status[status],
4064
4340
  status === "failed" && error ? `: ${error}` : null
4065
4341
  ] })
@@ -4069,12 +4345,12 @@ function ToolApproval({
4069
4345
  }
4070
4346
 
4071
4347
  // src/features/tool-approval/tool-approval-list.tsx
4072
- import { jsx as jsx19 } from "react/jsx-runtime";
4348
+ import { jsx as jsx20 } from "react/jsx-runtime";
4073
4349
  function ToolApprovalList({ approvals, pendingOnly = false, renderItem, labels, className }) {
4074
4350
  const entries = pendingOnly ? approvals.entries.filter((e) => e.status === "pending") : approvals.entries;
4075
4351
  if (entries.length === 0) return null;
4076
- return /* @__PURE__ */ jsx19("div", { className: className ? `june-approval-list ${className}` : "june-approval-list", children: entries.map(
4077
- (entry) => renderItem ? /* @__PURE__ */ jsx19("div", { children: renderItem(entry, approvals.decide) }, entry.request.id) : /* @__PURE__ */ jsx19(
4352
+ return /* @__PURE__ */ jsx20("div", { className: className ? `june-approval-list ${className}` : "june-approval-list", children: entries.map(
4353
+ (entry) => renderItem ? /* @__PURE__ */ jsx20("div", { children: renderItem(entry, approvals.decide) }, entry.request.id) : /* @__PURE__ */ jsx20(
4078
4354
  ToolApproval,
4079
4355
  {
4080
4356
  request: entry.request,
@@ -4089,11 +4365,11 @@ function ToolApprovalList({ approvals, pendingOnly = false, renderItem, labels,
4089
4365
  }
4090
4366
 
4091
4367
  // src/features/tool-approval/use-tool-approval.ts
4092
- import { useCallback as useCallback5, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef9, useState as useState8 } from "react";
4368
+ import { useCallback as useCallback5, useEffect as useEffect9, useMemo as useMemo7, useRef as useRef9, useState as useState9 } from "react";
4093
4369
  var counter = 0;
4094
4370
  var nextId = () => `june-approval-${++counter}`;
4095
4371
  function useToolApproval() {
4096
- const [entries, setEntries] = useState8([]);
4372
+ const [entries, setEntries] = useState9([]);
4097
4373
  const waiters = useRef9(/* @__PURE__ */ new Map());
4098
4374
  const settle = useCallback5((id, decision) => {
4099
4375
  const resolve = waiters.current.get(id);
@@ -4138,15 +4414,15 @@ function useToolApproval() {
4138
4414
  },
4139
4415
  [request]
4140
4416
  );
4141
- useEffect7(() => {
4417
+ useEffect9(() => {
4142
4418
  const map = waiters.current;
4143
4419
  return () => {
4144
4420
  map.forEach((resolve) => resolve({ choice: "reject" }));
4145
4421
  map.clear();
4146
4422
  };
4147
4423
  }, []);
4148
- const pending = useMemo6(() => entries.filter((e) => e.status === "pending"), [entries]);
4149
- return useMemo6(
4424
+ const pending = useMemo7(() => entries.filter((e) => e.status === "pending"), [entries]);
4425
+ return useMemo7(
4150
4426
  () => ({ entries, pending, request, decide, setStatus, dismiss, confirmAction }),
4151
4427
  [entries, pending, request, decide, setStatus, dismiss, confirmAction]
4152
4428
  );