@tangle-network/agent-app 0.42.8 → 0.42.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.
@@ -0,0 +1,7 @@
1
+ import {
2
+ VaultPane
3
+ } from "./chunk-JJ36K7AN.js";
4
+ export {
5
+ VaultPane
6
+ };
7
+ //# sourceMappingURL=VaultPane-J3S3CFPK.js.map
@@ -119,11 +119,37 @@ function collectFilePaths(nodes, into) {
119
119
  }
120
120
  return into;
121
121
  }
122
- function countFiles(nodes) {
123
- return nodes.reduce(
124
- (sum, node) => node.type === "file" ? sum + 1 : sum + countFiles(node.children ?? []),
125
- 0
126
- );
122
+ function resolveFilePath(rawPath, filePaths) {
123
+ if (filePaths.has(rawPath)) return rawPath;
124
+ const path = rawPath.replace(/^\/+|\/+$/g, "");
125
+ return filePaths.has(path) ? path : null;
126
+ }
127
+ function treeClickPath(event) {
128
+ const path = event.nativeEvent.composedPath?.() ?? [];
129
+ for (const item of path) {
130
+ if (!(item instanceof HTMLElement)) continue;
131
+ if (item.dataset.type !== "item") continue;
132
+ if (item.dataset.itemType !== "file") return null;
133
+ return item.dataset.itemPath ?? null;
134
+ }
135
+ const target = event.target instanceof HTMLElement ? event.target.closest('[data-type="item"]') : null;
136
+ if (!(target instanceof HTMLElement)) return null;
137
+ if (target.dataset.itemType !== "file") return null;
138
+ return target.dataset.itemPath ?? null;
139
+ }
140
+ function filterNodes(nodes, q) {
141
+ const out = [];
142
+ for (const node of nodes) {
143
+ if (node.type === "file") {
144
+ if (node.name.toLowerCase().includes(q)) out.push(node);
145
+ } else if (node.name.toLowerCase().includes(q)) {
146
+ out.push(node);
147
+ } else {
148
+ const children = filterNodes(node.children ?? [], q);
149
+ if (children.length > 0) out.push({ ...node, children });
150
+ }
151
+ }
152
+ return out;
127
153
  }
128
154
  var EditorErrorBoundary = class extends Component {
129
155
  state = { error: null };
@@ -173,6 +199,23 @@ function EmptyState() {
173
199
  /* @__PURE__ */ jsx2("p", { className: "mt-1 max-w-xs text-xs text-muted-foreground", children: "Select a file from the directory, or create a new one." })
174
200
  ] });
175
201
  }
202
+ function ReadErrorState({ message, onRetry }) {
203
+ return /* @__PURE__ */ jsxs2("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [
204
+ /* @__PURE__ */ jsxs2("div", { children: [
205
+ /* @__PURE__ */ jsx2("h3", { className: "text-sm font-medium text-foreground", children: "Couldn't open this file" }),
206
+ /* @__PURE__ */ jsx2("p", { className: "mt-1 max-w-md text-xs text-muted-foreground", children: message })
207
+ ] }),
208
+ /* @__PURE__ */ jsx2(
209
+ "button",
210
+ {
211
+ type: "button",
212
+ onClick: onRetry,
213
+ className: "inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60",
214
+ children: "Retry"
215
+ }
216
+ )
217
+ ] });
218
+ }
176
219
  function VaultPane(props) {
177
220
  const {
178
221
  port,
@@ -199,6 +242,8 @@ function VaultPane(props) {
199
242
  const selectedPath = controlled ? controlledPath ?? null : internalPath;
200
243
  const [selectedFile, setSelectedFile] = useState(null);
201
244
  const [fileLoading, setFileLoading] = useState(false);
245
+ const [readError, setReadError] = useState(null);
246
+ const [reloadNonce, setReloadNonce] = useState(0);
202
247
  const [saving, setSaving] = useState(false);
203
248
  const [editorMode, setEditorMode] = useState("rich");
204
249
  const [richDraft, setRichDraft] = useState("");
@@ -211,14 +256,23 @@ function VaultPane(props) {
211
256
  const [deleting, setDeleting] = useState(false);
212
257
  const [dockOpen, setDockOpen] = useState(false);
213
258
  const [pendingNav, setPendingNav] = useState(null);
259
+ const [query, setQuery] = useState("");
214
260
  const savedContentRef = useRef2("");
215
261
  const loadedPathRef = useRef2(null);
216
262
  const filePaths = useMemo(() => collectFilePaths(tree, /* @__PURE__ */ new Set()), [tree]);
263
+ const resolvedSelectedPath = useMemo(
264
+ () => selectedPath ? resolveFilePath(selectedPath, filePaths) : null,
265
+ [selectedPath, filePaths]
266
+ );
217
267
  const treeRoot = useMemo(
218
268
  () => ({ name: "Vault", path: "", type: "directory", children: tree }),
219
269
  [tree]
220
270
  );
221
- const fileCount = useMemo(() => countFiles(tree), [tree]);
271
+ const visibleRoot = useMemo(() => {
272
+ const q = query.trim().toLowerCase();
273
+ if (!q) return treeRoot;
274
+ return { ...treeRoot, children: filterNodes(tree, q) };
275
+ }, [treeRoot, tree, query]);
222
276
  const commitPath = useCallback(
223
277
  (next) => {
224
278
  if (!controlled) setInternalPath(next);
@@ -241,18 +295,33 @@ function VaultPane(props) {
241
295
  if (!selectedPath) {
242
296
  setSelectedFile(null);
243
297
  setFileLoading(false);
298
+ setReadError(null);
299
+ loadedPathRef.current = null;
300
+ return;
301
+ }
302
+ if (treeLoading) return;
303
+ if (!resolvedSelectedPath) {
304
+ commitPath(null);
305
+ setSelectedFile(null);
306
+ setFileLoading(false);
307
+ setReadError(null);
244
308
  loadedPathRef.current = null;
245
309
  return;
246
310
  }
247
311
  let cancelled = false;
248
- const path = selectedPath;
312
+ const path = resolvedSelectedPath;
313
+ if (path !== selectedPath) commitPath(path);
249
314
  setFileLoading(true);
315
+ setReadError(null);
250
316
  void (async () => {
251
317
  try {
252
318
  const file = await port.readFile(path);
253
319
  if (!cancelled) setSelectedFile(file);
254
- } catch {
255
- if (!cancelled) setSelectedFile(null);
320
+ } catch (err) {
321
+ if (!cancelled) {
322
+ setSelectedFile(null);
323
+ setReadError(err instanceof Error ? err.message : "Failed to read file");
324
+ }
256
325
  } finally {
257
326
  if (!cancelled) setFileLoading(false);
258
327
  }
@@ -260,7 +329,7 @@ function VaultPane(props) {
260
329
  return () => {
261
330
  cancelled = true;
262
331
  };
263
- }, [port, selectedPath, refreshKey]);
332
+ }, [port, selectedPath, resolvedSelectedPath, treeLoading, refreshKey, reloadNonce, commitPath]);
264
333
  useEffect2(() => {
265
334
  if (!selectedFile) {
266
335
  loadedPathRef.current = null;
@@ -291,6 +360,16 @@ function VaultPane(props) {
291
360
  },
292
361
  [isDirty, selectedPath, commitPath]
293
362
  );
363
+ const selectFileRef = useRef2(() => {
364
+ });
365
+ selectFileRef.current = (rawPath) => {
366
+ const path = resolveFilePath(rawPath, filePaths);
367
+ if (path) {
368
+ guardedOpen(path);
369
+ return;
370
+ }
371
+ };
372
+ const handleTreeSelect = useCallback((path) => selectFileRef.current(path), []);
294
373
  const guardedClose = useCallback(() => {
295
374
  if (isDirty) {
296
375
  setPendingNav({ type: "close" });
@@ -384,15 +463,18 @@ function VaultPane(props) {
384
463
  setSelectedFile(null);
385
464
  }, children: /* @__PURE__ */ jsxs2("div", { className: `flex min-h-0 flex-1 overflow-hidden ${className ?? ""}`, children: [
386
465
  /* @__PURE__ */ jsxs2("div", { className: "flex w-[23rem] min-w-[23rem] flex-col border-r border-border bg-background", children: [
387
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between gap-3 border-b border-border px-4 py-3", children: [
388
- /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 items-center gap-2", children: [
389
- /* @__PURE__ */ jsx2("span", { className: "text-sm font-semibold text-foreground", children: "Vault" }),
390
- /* @__PURE__ */ jsxs2("span", { className: "text-xs text-muted-foreground", children: [
391
- fileCount,
392
- " file",
393
- fileCount === 1 ? "" : "s"
394
- ] })
395
- ] }),
466
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 border-b border-border px-4 py-3", children: [
467
+ /* @__PURE__ */ jsx2("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx2(
468
+ "input",
469
+ {
470
+ type: "text",
471
+ value: query,
472
+ onChange: (e) => setQuery(e.target.value),
473
+ placeholder: "Search\u2026",
474
+ "aria-label": "Search vault",
475
+ className: "h-8 w-full rounded-md border border-border bg-background px-3 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-primary/60"
476
+ }
477
+ ) }),
396
478
  /* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center gap-1", children: [
397
479
  headerActions,
398
480
  /* @__PURE__ */ jsx2(
@@ -417,13 +499,21 @@ function VaultPane(props) {
417
499
  )
418
500
  ] })
419
501
  ] }),
420
- /* @__PURE__ */ jsx2("div", { className: "flex-1 overflow-y-auto", children: treeLoading ? /* @__PURE__ */ jsx2(TreeSkeleton, {}) : renderTree({
421
- root: treeRoot,
422
- selectedPath: selectedPath ?? void 0,
423
- onSelect: (path) => {
424
- if (filePaths.has(path)) guardedOpen(path);
502
+ /* @__PURE__ */ jsx2(
503
+ "div",
504
+ {
505
+ className: "flex-1 overflow-y-auto",
506
+ onClickCapture: (event) => {
507
+ const path = treeClickPath(event);
508
+ if (path) handleTreeSelect(path);
509
+ },
510
+ children: treeLoading ? /* @__PURE__ */ jsx2(TreeSkeleton, {}) : renderTree({
511
+ root: visibleRoot,
512
+ selectedPath: resolvedSelectedPath ?? void 0,
513
+ onSelect: handleTreeSelect
514
+ })
425
515
  }
426
- }) })
516
+ )
427
517
  ] }),
428
518
  /* @__PURE__ */ jsxs2("div", { className: "flex min-w-0 flex-1 flex-col overflow-hidden", children: [
429
519
  selectedFile && /* @__PURE__ */ jsxs2("div", { className: "flex shrink-0 items-center justify-between border-b border-border bg-card px-4 py-1.5", children: [
@@ -479,7 +569,7 @@ function VaultPane(props) {
479
569
  )
480
570
  ] })
481
571
  ] }),
482
- /* @__PURE__ */ jsx2("div", { className: "flex-1 overflow-hidden", children: fileLoading ? /* @__PURE__ */ jsx2(EditorSkeleton, {}) : selectedFile && canWrite && isMarkdownCapable && editorMode === "source" ? /* @__PURE__ */ jsx2(
572
+ /* @__PURE__ */ jsx2("div", { className: "flex-1 overflow-hidden", children: fileLoading ? /* @__PURE__ */ jsx2(EditorSkeleton, {}) : readError ? /* @__PURE__ */ jsx2(ReadErrorState, { message: readError, onRetry: () => setReloadNonce((n) => n + 1) }) : selectedFile && canWrite && isMarkdownCapable && editorMode === "source" ? /* @__PURE__ */ jsx2(
483
573
  SourceEditor,
484
574
  {
485
575
  path: selectedFile.path,
@@ -601,4 +691,4 @@ export {
601
691
  ConfirmDialog,
602
692
  VaultPane
603
693
  };
604
- //# sourceMappingURL=chunk-IITUMDKF.js.map
694
+ //# sourceMappingURL=chunk-JJ36K7AN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vault/VaultPane.tsx","../src/vault/ConfirmDialog.tsx"],"sourcesContent":["/**\n * The shared 3-pane vault: tree | artifact viewer | optional agent dock. This is\n * pure shell MECHANISM — selection, the dirty-guard + pending-nav state machine,\n * rich/source editor modes, create/delete/refresh, skeletons, an error boundary,\n * and an empty state. It renders NO file tree and NO artifact viewer of its own:\n * those arrive through the `renderTree` / `renderArtifact` / `renderDock` seams,\n * so a product wires sandbox-ui's RichFileTree + FileArtifactPane in ~10 lines.\n *\n * Data flows exclusively through `port` (a `VaultDataPort`). The pane never\n * imports a fetch client, a router, a toast system, or a markdown library — the\n * optional `codec` seam supplies rich/source parsing (identity passthrough by\n * default). Chrome uses the shared theme tokens (bg-card, border-border, …).\n */\n\nimport {\n Component,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ErrorInfo,\n type MouseEvent,\n type ReactNode,\n} from 'react'\nimport { ConfirmDialog } from './ConfirmDialog'\nimport type {\n VaultEditorMode,\n VaultFile,\n VaultMarkdownCodec,\n VaultPaneProps,\n VaultRichParts,\n VaultTreeNode,\n} from './contracts'\n\nconst IDENTITY_CODEC: VaultMarkdownCodec = {\n parse: (raw) => raw,\n serialize: (parts) => (typeof parts === 'string' ? parts : String(parts ?? '')),\n}\n\ntype PendingNav = { type: 'open'; path: string } | { type: 'close' } | null\n\nfunction collectFilePaths(nodes: VaultTreeNode[], into: Set<string>): Set<string> {\n for (const node of nodes) {\n if (node.type === 'file') into.add(node.path)\n if (node.children) collectFilePaths(node.children, into)\n }\n return into\n}\n\nfunction resolveFilePath(rawPath: string, filePaths: Set<string>): string | null {\n if (filePaths.has(rawPath)) return rawPath\n const path = rawPath.replace(/^\\/+|\\/+$/g, '')\n return filePaths.has(path) ? path : null\n}\n\nfunction treeClickPath(event: MouseEvent<HTMLElement>): string | null {\n const path = event.nativeEvent.composedPath?.() ?? []\n for (const item of path) {\n if (!(item instanceof HTMLElement)) continue\n if (item.dataset.type !== 'item') continue\n if (item.dataset.itemType !== 'file') return null\n return item.dataset.itemPath ?? null\n }\n\n const target = event.target instanceof HTMLElement\n ? event.target.closest('[data-type=\"item\"]')\n : null\n if (!(target instanceof HTMLElement)) return null\n if (target.dataset.itemType !== 'file') return null\n return target.dataset.itemPath ?? null\n}\n\n// Case-insensitive name filter over the tree: files survive when their name\n// matches; a directory survives whole (with all its children) when its own name\n// matches, otherwise only when some descendant survives.\nfunction filterNodes(nodes: VaultTreeNode[], q: string): VaultTreeNode[] {\n const out: VaultTreeNode[] = []\n for (const node of nodes) {\n if (node.type === 'file') {\n if (node.name.toLowerCase().includes(q)) out.push(node)\n } else if (node.name.toLowerCase().includes(q)) {\n out.push(node)\n } else {\n const children = filterNodes(node.children ?? [], q)\n if (children.length > 0) out.push({ ...node, children })\n }\n }\n return out\n}\n\nclass EditorErrorBoundary extends Component<{ children: ReactNode; onReset?: () => void }, { error: unknown }> {\n state: { error: unknown } = { error: null }\n static getDerivedStateFromError(error: unknown) {\n return { error }\n }\n componentDidCatch(error: unknown, info: ErrorInfo) {\n console.error('Vault crashed:', error, info)\n }\n render() {\n if (this.state.error) {\n const msg = this.state.error instanceof Error\n ? this.state.error.message\n : typeof this.state.error === 'string'\n ? this.state.error\n : 'Something went wrong loading the vault'\n return (\n <div className=\"flex h-full flex-1 flex-col items-center justify-center p-8 text-center\">\n <h3 className=\"mb-1 text-sm font-medium text-foreground\">Vault failed to load</h3>\n <p className=\"mb-4 max-w-xs text-xs text-muted-foreground\">{String(msg)}</p>\n <button\n type=\"button\"\n onClick={() => { this.setState({ error: null }); this.props.onReset?.() }}\n className=\"inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n Try again\n </button>\n </div>\n )\n }\n return this.props.children\n }\n}\n\nfunction TreeSkeleton() {\n return (\n <div className=\"space-y-2 p-4\" aria-hidden=\"true\">\n {[32, 48, 40, 52].map((w, i) => (\n <div key={i} className=\"h-4 animate-pulse rounded bg-muted\" style={{ width: `${w * 4}px` }} />\n ))}\n </div>\n )\n}\n\nfunction EditorSkeleton() {\n return (\n <div className=\"space-y-3 p-8\" aria-hidden=\"true\">\n <div className=\"h-5 w-1/2 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-full animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-3/4 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-2/3 animate-pulse rounded bg-muted\" />\n </div>\n )\n}\n\nfunction EmptyState() {\n return (\n <div className=\"flex h-full flex-col items-center justify-center p-8 text-center\">\n <h3 className=\"text-sm font-medium text-foreground\">Open a vault document</h3>\n <p className=\"mt-1 max-w-xs text-xs text-muted-foreground\">\n Select a file from the directory, or create a new one.\n </p>\n </div>\n )\n}\n\nfunction ReadErrorState({ message, onRetry }: { message: string; onRetry: () => void }) {\n return (\n <div className=\"flex h-full flex-col items-center justify-center gap-3 p-8 text-center\">\n <div>\n <h3 className=\"text-sm font-medium text-foreground\">Couldn't open this file</h3>\n <p className=\"mt-1 max-w-md text-xs text-muted-foreground\">{message}</p>\n </div>\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n Retry\n </button>\n </div>\n )\n}\n\nexport function VaultPane(props: VaultPaneProps) {\n const {\n port,\n renderTree,\n renderArtifact,\n renderDock,\n canWrite = true,\n selectedPath: controlledPath,\n onSelectedPathChange,\n codec,\n className,\n dockToggle,\n refreshKey,\n headerActions,\n } = props\n\n const activeCodec = codec ?? IDENTITY_CODEC\n const controlled = controlledPath !== undefined\n const isMarkdownCapable = codec !== undefined\n // `false` → a persistent dock (no toggle, always open with the selected file).\n const persistentDock = dockToggle === false\n const dockToggleCfg = dockToggle ? dockToggle : { label: 'Discuss', disabledWhenDirty: true }\n\n const [tree, setTree] = useState<VaultTreeNode[]>([])\n const [treeLoading, setTreeLoading] = useState(true)\n const [internalPath, setInternalPath] = useState<string | null>(null)\n const selectedPath = controlled ? (controlledPath ?? null) : internalPath\n\n const [selectedFile, setSelectedFile] = useState<VaultFile | null>(null)\n const [fileLoading, setFileLoading] = useState(false)\n const [readError, setReadError] = useState<string | null>(null)\n const [reloadNonce, setReloadNonce] = useState(0)\n const [saving, setSaving] = useState(false)\n\n const [editorMode, setEditorMode] = useState<VaultEditorMode>('rich')\n const [richDraft, setRichDraft] = useState<VaultRichParts>('')\n const [sourceDraft, setSourceDraft] = useState('')\n const [isDirty, setIsDirty] = useState(false)\n\n const [createOpen, setCreateOpen] = useState(false)\n const [newPath, setNewPath] = useState('')\n const [creating, setCreating] = useState(false)\n const [deleteOpen, setDeleteOpen] = useState(false)\n const [deleting, setDeleting] = useState(false)\n const [dockOpen, setDockOpen] = useState(false)\n const [pendingNav, setPendingNav] = useState<PendingNav>(null)\n const [query, setQuery] = useState('')\n\n const savedContentRef = useRef('')\n const loadedPathRef = useRef<string | null>(null)\n\n const filePaths = useMemo(() => collectFilePaths(tree, new Set<string>()), [tree])\n const resolvedSelectedPath = useMemo(\n () => selectedPath ? resolveFilePath(selectedPath, filePaths) : null,\n [selectedPath, filePaths],\n )\n const treeRoot = useMemo<VaultTreeNode>(\n () => ({ name: 'Vault', path: '', type: 'directory', children: tree }),\n [tree],\n )\n const visibleRoot = useMemo<VaultTreeNode>(() => {\n const q = query.trim().toLowerCase()\n if (!q) return treeRoot\n return { ...treeRoot, children: filterNodes(tree, q) }\n }, [treeRoot, tree, query])\n\n const commitPath = useCallback(\n (next: string | null) => {\n if (!controlled) setInternalPath(next)\n onSelectedPathChange?.(next)\n },\n [controlled, onSelectedPathChange],\n )\n\n const refresh = useCallback(async () => {\n setTreeLoading(true)\n try {\n setTree(await port.listTree())\n } finally {\n setTreeLoading(false)\n }\n }, [port])\n\n useEffect(() => {\n void refresh()\n }, [refresh, refreshKey])\n\n useEffect(() => {\n if (!selectedPath) {\n setSelectedFile(null)\n setFileLoading(false)\n setReadError(null)\n loadedPathRef.current = null\n return\n }\n if (treeLoading) return\n if (!resolvedSelectedPath) {\n commitPath(null)\n setSelectedFile(null)\n setFileLoading(false)\n setReadError(null)\n loadedPathRef.current = null\n return\n }\n let cancelled = false\n const path = resolvedSelectedPath\n if (path !== selectedPath) commitPath(path)\n setFileLoading(true)\n setReadError(null)\n void (async () => {\n try {\n const file = await port.readFile(path)\n if (!cancelled) setSelectedFile(file)\n } catch (err) {\n // Surface read failures instead of making them indistinguishable from\n // the intentionally empty \"no file selected\" state.\n if (!cancelled) {\n setSelectedFile(null)\n setReadError(err instanceof Error ? err.message : 'Failed to read file')\n }\n } finally {\n if (!cancelled) setFileLoading(false)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [port, selectedPath, resolvedSelectedPath, treeLoading, refreshKey, reloadNonce, commitPath])\n\n useEffect(() => {\n if (!selectedFile) {\n loadedPathRef.current = null\n savedContentRef.current = ''\n setRichDraft('')\n setSourceDraft('')\n setEditorMode('rich')\n setIsDirty(false)\n return\n }\n const pathChanged = loadedPathRef.current !== selectedFile.path\n loadedPathRef.current = selectedFile.path\n savedContentRef.current = selectedFile.content\n setRichDraft(activeCodec.parse(selectedFile.content))\n setSourceDraft(selectedFile.content)\n if (pathChanged) setEditorMode('rich')\n setIsDirty(false)\n setDockOpen(false)\n }, [selectedFile, activeCodec])\n\n const guardedOpen = useCallback(\n (path: string) => {\n if (path === selectedPath) return\n if (isDirty) {\n setPendingNav({ type: 'open', path })\n return\n }\n commitPath(path)\n },\n [isDirty, selectedPath, commitPath],\n )\n\n // Some tree models keep their original selection callback while resetting\n // paths internally. Keep the callable stable, but have it execute the latest\n // file-path validation and dirty-guard logic.\n const selectFileRef = useRef<(path: string) => void>(() => {})\n selectFileRef.current = (rawPath: string) => {\n const path = resolveFilePath(rawPath, filePaths)\n if (path) {\n guardedOpen(path)\n return\n }\n }\n const handleTreeSelect = useCallback((path: string) => selectFileRef.current(path), [])\n\n const guardedClose = useCallback(() => {\n if (isDirty) {\n setPendingNav({ type: 'close' })\n return\n }\n commitPath(null)\n setSelectedFile(null)\n }, [isDirty, commitPath])\n\n const confirmDiscard = useCallback(() => {\n const nav = pendingNav\n setPendingNav(null)\n setIsDirty(false)\n if (!nav) return\n if (nav.type === 'open') {\n commitPath(nav.path)\n } else {\n commitPath(null)\n setSelectedFile(null)\n }\n }, [pendingNav, commitPath])\n\n const showRichMode = useCallback(() => {\n setEditorMode((mode) => {\n if (mode === 'rich') return mode\n setRichDraft(activeCodec.parse(sourceDraft))\n setIsDirty(sourceDraft !== savedContentRef.current)\n return 'rich'\n })\n }, [activeCodec, sourceDraft])\n\n const showSourceMode = useCallback(() => {\n setEditorMode((mode) => {\n if (mode === 'source') return mode\n const content = isDirty ? activeCodec.serialize(richDraft) : savedContentRef.current\n setSourceDraft(content)\n setIsDirty(content !== savedContentRef.current)\n return 'source'\n })\n }, [activeCodec, isDirty, richDraft])\n\n const onSourceChange = useCallback((next: string) => {\n setSourceDraft(next)\n setIsDirty(next !== savedContentRef.current)\n }, [])\n\n const onRichChange = useCallback((next: VaultRichParts) => {\n setRichDraft(next)\n setIsDirty(activeCodec.serialize(next) !== savedContentRef.current)\n }, [activeCodec])\n\n const saveCurrent = useCallback(async () => {\n if (!selectedFile) return\n const content = editorMode === 'source' ? sourceDraft : activeCodec.serialize(richDraft)\n setSaving(true)\n try {\n await port.writeFile(selectedFile.path, content)\n savedContentRef.current = content\n setSelectedFile({ ...selectedFile, content })\n setSourceDraft(content)\n setRichDraft(activeCodec.parse(content))\n setIsDirty(false)\n } finally {\n setSaving(false)\n }\n }, [selectedFile, editorMode, sourceDraft, richDraft, activeCodec, port])\n\n const handleCreate = useCallback(async () => {\n const trimmed = newPath.trim()\n if (!trimmed) return\n setCreating(true)\n try {\n const created = await port.createFile(trimmed)\n setCreateOpen(false)\n setNewPath('')\n await refresh()\n commitPath(created)\n } finally {\n setCreating(false)\n }\n }, [newPath, port, refresh, commitPath])\n\n const handleDelete = useCallback(async () => {\n if (!selectedFile) return\n setDeleting(true)\n try {\n await port.deleteFile(selectedFile.path)\n setDeleteOpen(false)\n setIsDirty(false)\n commitPath(null)\n setSelectedFile(null)\n await refresh()\n } finally {\n setDeleting(false)\n }\n }, [selectedFile, port, refresh, commitPath])\n\n return (\n <EditorErrorBoundary onReset={() => { commitPath(null); setSelectedFile(null) }}>\n <div className={`flex min-h-0 flex-1 overflow-hidden ${className ?? ''}`}>\n <div className=\"flex w-[23rem] min-w-[23rem] flex-col border-r border-border bg-background\">\n <div className=\"flex items-center gap-2 border-b border-border px-4 py-3\">\n <div className=\"min-w-0 flex-1\">\n <input\n type=\"text\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n placeholder=\"Search…\"\n aria-label=\"Search vault\"\n className=\"h-8 w-full rounded-md border border-border bg-background px-3 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-primary/60\"\n />\n </div>\n <div className=\"flex shrink-0 items-center gap-1\">\n {headerActions}\n <button\n type=\"button\"\n aria-label=\"Refresh vault\"\n onClick={() => void refresh()}\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n ↻\n </button>\n {canWrite && (\n <button\n type=\"button\"\n aria-label=\"New vault file\"\n onClick={() => setCreateOpen(true)}\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n +\n </button>\n )}\n </div>\n </div>\n <div\n className=\"flex-1 overflow-y-auto\"\n onClickCapture={(event) => {\n const path = treeClickPath(event)\n if (path) handleTreeSelect(path)\n }}\n >\n {treeLoading ? (\n <TreeSkeleton />\n ) : (\n renderTree({\n root: visibleRoot,\n selectedPath: resolvedSelectedPath ?? undefined,\n onSelect: handleTreeSelect,\n })\n )}\n </div>\n </div>\n\n <div className=\"flex min-w-0 flex-1 flex-col overflow-hidden\">\n {selectedFile && (\n <div className=\"flex shrink-0 items-center justify-between border-b border-border bg-card px-4 py-1.5\">\n <span data-vault-path className=\"truncate text-xs text-muted-foreground\">{selectedFile.path}</span>\n <div className=\"flex items-center gap-1\">\n {canWrite && isMarkdownCapable && (\n <div className=\"mr-1 flex items-center gap-1\">\n <button\n type=\"button\"\n aria-label=\"Edit as rich text\"\n aria-pressed={editorMode === 'rich'}\n onClick={showRichMode}\n className={`inline-flex h-7 items-center rounded px-2 text-xs transition-colors ${\n editorMode === 'rich'\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n Rich\n </button>\n <button\n type=\"button\"\n aria-label=\"Edit as source\"\n aria-pressed={editorMode === 'source'}\n onClick={showSourceMode}\n className={`inline-flex h-7 items-center rounded px-2 text-xs transition-colors ${\n editorMode === 'source'\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n Source\n </button>\n </div>\n )}\n {renderDock && !persistentDock && (\n <button\n type=\"button\"\n aria-label={dockToggleCfg.label}\n aria-pressed={dockOpen}\n disabled={(dockToggleCfg.disabledWhenDirty ?? true) && isDirty}\n title={(dockToggleCfg.disabledWhenDirty ?? true) && isDirty ? 'Save your changes first' : (dockToggleCfg.title ?? dockToggleCfg.label)}\n onClick={() => setDockOpen((v) => !v)}\n className={`inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs transition-colors disabled:pointer-events-none disabled:opacity-40 ${\n dockOpen\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n {dockToggleCfg.label}\n </button>\n )}\n {canWrite && (\n <button\n type=\"button\"\n aria-label=\"Delete this file\"\n title=\"Delete file\"\n onClick={() => setDeleteOpen(true)}\n className=\"p-1 text-muted-foreground transition-colors hover:text-destructive\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n )}\n <div className=\"flex-1 overflow-hidden\">\n {fileLoading ? (\n <EditorSkeleton />\n ) : readError ? (\n <ReadErrorState message={readError} onRetry={() => setReloadNonce((n) => n + 1)} />\n ) : selectedFile && canWrite && isMarkdownCapable && editorMode === 'source' ? (\n <SourceEditor\n path={selectedFile.path}\n content={sourceDraft}\n saving={saving}\n dirty={isDirty}\n onChange={onSourceChange}\n onSave={() => void saveCurrent()}\n />\n ) : selectedFile ? (\n renderArtifact({\n file: selectedFile,\n loading: false,\n mode: editorMode,\n canWrite,\n richDraft,\n dirty: isDirty,\n onRichChange,\n onSave: () => void saveCurrent(),\n })\n ) : (\n <EmptyState />\n )}\n </div>\n </div>\n\n {renderDock && selectedFile && renderDock({\n file: selectedFile,\n open: persistentDock ? true : dockOpen,\n onClose: persistentDock ? () => {} : () => setDockOpen(false),\n })}\n\n <ConfirmDialog\n open={createOpen}\n title=\"Create vault file\"\n description=\"Add a new document to this vault.\"\n confirmLabel={creating ? 'Creating…' : 'Create'}\n confirmDisabled={creating || !newPath.trim()}\n onConfirm={() => void handleCreate()}\n onCancel={() => { setCreateOpen(false); setNewPath('') }}\n >\n <input\n value={newPath}\n autoFocus\n onChange={(e) => setNewPath(e.target.value)}\n placeholder=\"e.g. playbooks/new-strategy.md\"\n aria-label=\"New file path\"\n className=\"h-9 w-full rounded-md border border-border bg-background px-3 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-primary/60\"\n />\n </ConfirmDialog>\n\n <ConfirmDialog\n open={deleteOpen}\n title=\"Delete file?\"\n description={`This permanently removes ${selectedFile?.path ?? 'this file'} from the vault.`}\n confirmLabel={deleting ? 'Deleting…' : 'Delete file'}\n confirmDisabled={deleting}\n destructive\n onConfirm={() => void handleDelete()}\n onCancel={() => setDeleteOpen(false)}\n />\n\n <ConfirmDialog\n open={pendingNav !== null}\n title=\"Discard unsaved changes?\"\n description=\"Your edits to this document haven't been saved. Continue and lose them?\"\n confirmLabel=\"Discard changes\"\n destructive\n onConfirm={confirmDiscard}\n onCancel={() => setPendingNav(null)}\n />\n </div>\n </EditorErrorBoundary>\n )\n}\n\nfunction SourceEditor({\n path,\n content,\n saving,\n dirty,\n onChange,\n onSave,\n}: {\n path: string\n content: string\n saving: boolean\n dirty: boolean\n onChange: (content: string) => void\n onSave: () => void\n}) {\n return (\n <div className=\"flex h-full min-h-0 flex-col bg-background\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border px-4 py-2\">\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">{path}</p>\n <div className=\"flex shrink-0 items-center gap-2\">\n <span className=\"rounded-full border border-border bg-background px-2.5 py-1 text-xs text-muted-foreground\">\n {dirty ? 'Unsaved changes' : 'Saved'}\n </span>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving || !dirty}\n className=\"inline-flex h-7 items-center rounded-md bg-primary px-2 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50\"\n >\n {saving ? 'Saving…' : 'Save'}\n </button>\n </div>\n </div>\n <textarea\n value={content}\n onChange={(event) => onChange(event.target.value)}\n spellCheck={false}\n aria-label=\"Source editor\"\n className=\"min-h-0 flex-1 resize-none border-0 bg-background p-4 font-mono text-sm leading-6 text-foreground outline-none\"\n />\n </div>\n )\n}\n","/**\n * Self-contained confirm dialog — no dialog library. A focus-trapped modal with\n * Esc-to-cancel and Enter-to-confirm, used for create / delete / discard-unsaved\n * flows so the pane carries zero UI-kit dependency.\n */\n\nimport { useEffect, useRef, type ReactNode } from 'react'\n\nexport interface ConfirmDialogProps {\n open: boolean\n title: string\n description?: ReactNode\n confirmLabel?: string\n cancelLabel?: string\n /** Styles the confirm button as a destructive action. */\n destructive?: boolean\n /** Disables the confirm button (e.g. while the action is in flight). */\n confirmDisabled?: boolean\n onConfirm: () => void\n onCancel: () => void\n /** Optional body (e.g. an input field for the create flow). */\n children?: ReactNode\n}\n\nexport function ConfirmDialog({\n open,\n title,\n description,\n confirmLabel = 'Confirm',\n cancelLabel = 'Cancel',\n destructive = false,\n confirmDisabled = false,\n onConfirm,\n onCancel,\n children,\n}: ConfirmDialogProps) {\n const panelRef = useRef<HTMLDivElement>(null)\n const confirmRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (!open) return\n confirmRef.current?.focus()\n }, [open])\n\n if (!open) return null\n\n function onKeyDown(event: React.KeyboardEvent) {\n if (event.key === 'Escape') {\n event.preventDefault()\n onCancel()\n return\n }\n if (event.key === 'Enter' && !confirmDisabled) {\n event.preventDefault()\n onConfirm()\n return\n }\n if (event.key !== 'Tab') return\n const focusable = panelRef.current?.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n )\n if (!focusable || focusable.length === 0) return\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n const active = document.activeElement as HTMLElement | null\n if (event.shiftKey && active === first) {\n event.preventDefault()\n last?.focus()\n } else if (!event.shiftKey && active === last) {\n event.preventDefault()\n first?.focus()\n }\n }\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\"\n onMouseDown={(e) => { if (e.target === e.currentTarget) onCancel() }}\n >\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n onKeyDown={onKeyDown}\n className=\"w-full max-w-md rounded-lg border border-border bg-card p-5 shadow-lg\"\n >\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n {description && <p className=\"mt-1.5 text-xs text-muted-foreground\">{description}</p>}\n {children && <div className=\"mt-3\">{children}</div>}\n <div className=\"mt-5 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"inline-flex h-8 items-center rounded-md px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n {cancelLabel}\n </button>\n <button\n ref={confirmRef}\n type=\"button\"\n onClick={onConfirm}\n disabled={confirmDisabled}\n className={`inline-flex h-8 items-center rounded-md px-3 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60 disabled:pointer-events-none disabled:opacity-50 ${\n destructive\n ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90'\n : 'bg-primary text-primary-foreground hover:bg-primary/90'\n }`}\n >\n {confirmLabel}\n </button>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useRef","jsx","jsxs","useRef","useEffect"]}
@@ -192,9 +192,6 @@ function createInvitationsApi(opts) {
192
192
  error: "Multiple accounts use this email. Contact support to reconcile before accepting this invitation."
193
193
  };
194
194
  }
195
- if (!currentUser.emailVerified) {
196
- return { succeeded: false, status: 403, error: "Verify your email before accepting this invitation" };
197
- }
198
195
  let orgMember = await getOrganizationMember(invitation.organizationId, currentUser.id);
199
196
  if (orgMember?.role === "owner" || orgMember?.role === "admin") {
200
197
  return { succeeded: false, status: 409, error: "This user already has account-level access." };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/teams/invitations-api.ts"],"sourcesContent":["/**\n * Framework-neutral email-invitation lifecycle for the teams module: create /\n * list / resend / revoke / preview / accept over the dedicated\n * `workspace_invitation` table (from `createWorkspaceInvitationTable`). Lifted out\n * of any one app's route file — each app binds this once to its own db/tables/\n * access and mounts the handlers in its own routes with its own auth.\n *\n * Unlike `members-api` (whose \"invite\" is a pending workspaceMember row, no\n * email), this models the rich lifecycle: a dedicated invitation row with status,\n * 7-day expiry, emailStatus, resend, and revoke. On accept it materializes the\n * `organizationMembers` + `workspaceMembers` rows, so `members-api.listMembers`\n * still surfaces accepted members; pending invites live only here.\n *\n * Handlers return a discriminated `InvitationOutcome` (not a `Response`) so the\n * route adapter maps `{ status }` to its own framework's response. Three seams:\n * - `sendInvitationEmail` (REQUIRED) — the app's mail transport. Returns a\n * typed outcome; a failed send never blocks invitation creation (emailStatus\n * is recorded as 'failed' and the invite link is still returned).\n * - `enforceSeat` (OPTIONAL) — billing/seat-limit gate, called at create time\n * only when the invite would consume a NEW seat. Reused from members-api.\n * - `memberSyncSeam.add` (OPTIONAL) — fire-and-forget propagation of the new\n * member to a sandbox/external system on accept. Fail-soft by contract.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { and, eq, lte, sql } from 'drizzle-orm'\nimport {\n type InvitationEmailStatus,\n type InvitationPermission,\n type InvitationStatus,\n generateInvitationToken,\n getInvitationExpiresAt,\n inviteUrlForToken,\n normalizeInvitationEmail,\n parseInvitationPermission,\n} from './invitations'\nimport { type WorkspaceRole, hasWorkspaceRole } from './roles'\nimport { type EnforceSeatSeam, type MemberSyncSeam, SeatLimitError } from './members-api'\nimport type { TeamDatabase, WorkspaceAccessApi } from './drizzle/access'\nimport type { TeamParentTable, TeamTables } from './drizzle/schema'\nimport type { WorkspaceInvitationRow, WorkspaceInvitationTables } from './drizzle/invitations-schema'\n\n// Re-export the seams an adopter wires, so a consumer can import everything the\n// invitations API needs from this one subpath.\nexport { SeatLimitError } from './members-api'\nexport type { EnforceSeatSeam, MemberSyncSeam } from './members-api'\n\n/** The app's mail transport. Returns a typed outcome; never throws to the API. */\nexport interface SendInvitationEmailInput {\n to: string\n workspaceName: string\n inviterEmail: string\n permission: InvitationPermission\n inviteUrl: string\n expiresAt: Date\n}\nexport type SendInvitationEmailResult = { succeeded: true } | { succeeded: false; error: string }\nexport interface SendInvitationEmailSeam {\n (input: SendInvitationEmailInput): Promise<SendInvitationEmailResult>\n}\n\n/** The product's user table, narrowed to the columns the queries read. */\nexport interface InvitationUserTable {\n id: any\n email: any\n emailVerified: any\n}\n\n/** The product's workspace table, narrowed to the columns the queries read. */\nexport interface InvitationWorkspaceTable {\n id: any\n name: any\n}\n\nexport interface InvitationsApiOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The invitation table from `createWorkspaceInvitationTable`. */\n invitationsTable: WorkspaceInvitationTables\n userTable: TeamParentTable & InvitationUserTable\n workspaceTable: TeamParentTable & InvitationWorkspaceTable\n access: Pick<WorkspaceAccessApi, 'getWorkspaceAccess'>\n /** REQUIRED — the app's mail transport (e.g. Resend + renderInvitationEmail). */\n sendInvitationEmail: SendInvitationEmailSeam\n /** OPTIONAL — seat-limit gate at create time; mirrors members-api. */\n enforceSeat?: EnforceSeatSeam\n /** OPTIONAL — fire-and-forget member propagation on accept. */\n memberSyncSeam?: Pick<MemberSyncSeam, 'add'>\n /** Inviter display fallback when the inviter's email is unknown. */\n productDisplayName?: string\n}\n\nexport interface WorkspaceInvitationView {\n id: string\n workspaceId: string\n organizationId: string\n email: string\n invitedByUserId: string\n inviterEmail: string | null\n permissions: InvitationPermission\n token: string\n status: InvitationStatus\n emailStatus: InvitationEmailStatus\n expiresAt: Date\n createdAt: Date\n acceptedAt: Date | null\n revokedAt: Date | null\n lastSentAt: Date | null\n}\n\nexport interface InvitationPreview {\n workspaceId: string\n workspaceName: string\n email: string\n inviterEmail: string | null\n permissions: InvitationPermission\n status: InvitationStatus\n expiresAt: Date\n}\n\nexport type InvitationOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; status: number; error: string }\n\n/**\n * Build the invitations API bound to one product's db/tables/access/seams.\n * Returns the six lifecycle handlers an app mounts in its routes.\n */\nexport function createInvitationsApi(opts: InvitationsApiOptions) {\n const { db, tables, access, sendInvitationEmail, enforceSeat, memberSyncSeam } = opts\n const { organizationMembers, workspaceMembers } = tables\n const { workspaceInvitations } = opts.invitationsTable\n const users = opts.userTable\n const workspaces = opts.workspaceTable\n const productDisplayName = opts.productDisplayName ?? 'A workspace admin'\n\n function fireSync(op: () => Promise<void> | void) {\n try {\n Promise.resolve(op()).catch(() => {})\n } catch {\n // seam threw synchronously — swallow; sync is best-effort by contract.\n }\n }\n\n async function createInvitation(input: {\n workspaceId: string\n email: string\n permissions: string | undefined\n invitedByUserId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const now = input.now ?? new Date()\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.invitedByUserId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const permissions = parseInvitationPermission(input.permissions ?? 'editor')\n if (!permissions) return { succeeded: false, status: 400, error: 'Invalid permissions' }\n if (!hasWorkspaceRole(accessRow.role, permissions)) {\n return { succeeded: false, status: 403, error: 'Cannot assign higher permissions than your own' }\n }\n\n const email = normalizeInvitationEmail(input.email)\n if (!email) return { succeeded: false, status: 400, error: 'Missing email' }\n\n const organizationId = accessRow.organization.id\n const workspaceName = (accessRow.workspace as { name: string }).name\n\n await expirePendingInvitations(input.workspaceId, email, now)\n\n const existingPending = await getPendingInvitation(input.workspaceId, email)\n if (existingPending) return { succeeded: false, status: 409, error: 'A pending invitation already exists for this email' }\n\n const accountCheck = await checkExistingAccountAccess(organizationId, input.workspaceId, email, input.invitedByUserId)\n if (!accountCheck.succeeded) return accountCheck\n\n // Seat gate: only a genuinely NEW seat. The checks above reject re-invites,\n // existing workspace members, and org admins; an existing plain org member is\n // already billable (no new seat), so skip the gate for them — mirrors\n // members-api. The pending row is not inserted yet, so the seam's count\n // reflects current usage and denies the invite that would push OVER the limit\n // (at invite time, not accept time).\n if (enforceSeat && !accountCheck.isExistingOrgMember) {\n try {\n await enforceSeat({ actorId: input.invitedByUserId, organizationId })\n } catch (err) {\n if (err instanceof SeatLimitError) return { succeeded: false, status: err.status, error: err.message }\n throw err\n }\n }\n\n const token = generateInvitationToken()\n const expiresAt = getInvitationExpiresAt(now)\n\n const [created] = await db.insert(workspaceInvitations).values({\n workspaceId: input.workspaceId,\n organizationId,\n email,\n invitedByUserId: input.invitedByUserId,\n permissions,\n token,\n status: 'pending',\n emailStatus: 'not_sent',\n expiresAt,\n createdAt: now,\n }).returning()\n\n const inviteUrl = inviteUrlForToken(input.origin, token)\n const emailResult = await sendInvitationEmail({\n to: email,\n workspaceName,\n inviterEmail: accountCheck.inviterEmail ?? productDisplayName,\n permission: permissions,\n inviteUrl,\n expiresAt,\n })\n\n const emailStatus: InvitationEmailStatus = emailResult.succeeded ? 'sent' : 'failed'\n const [updated] = await db.update(workspaceInvitations)\n .set({ emailStatus, lastSentAt: emailResult.succeeded ? now : null })\n .where(eq(workspaceInvitations.id, created!.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, accountCheck.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function listInvitations(input: {\n workspaceId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitations: Array<WorkspaceInvitationView & { inviteUrl: string }> }>> {\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n await expirePendingInvitations(input.workspaceId, undefined, input.now ?? new Date())\n\n const rows = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.workspaceId, input.workspaceId))\n .orderBy(sql`${workspaceInvitations.createdAt} desc`)\n\n return {\n succeeded: true,\n value: {\n invitations: rows.map((row: { invitation: WorkspaceInvitationRow; inviterEmail: string | null }) => ({\n ...toInvitationView(row.invitation, row.inviterEmail),\n inviteUrl: inviteUrlForToken(input.origin, row.invitation.token),\n })),\n },\n }\n }\n\n async function resendInvitation(input: {\n invitationId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const now = input.now ?? new Date()\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be resent' }\n }\n if (row.invitation.expiresAt <= now) {\n await markInvitationExpired(row.invitation.id)\n return { succeeded: false, status: 409, error: 'Invitation has expired' }\n }\n\n const workspaceName = (accessRow.workspace as { name: string }).name\n const inviteUrl = inviteUrlForToken(input.origin, row.invitation.token)\n const emailResult = await sendInvitationEmail({\n to: row.invitation.email,\n workspaceName,\n inviterEmail: row.inviterEmail ?? productDisplayName,\n permission: row.invitation.permissions,\n inviteUrl,\n expiresAt: row.invitation.expiresAt,\n })\n\n const [updated] = await db.update(workspaceInvitations)\n .set({\n emailStatus: emailResult.succeeded ? 'sent' : 'failed',\n lastSentAt: emailResult.succeeded ? now : row.invitation.lastSentAt,\n })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, row.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function revokeInvitation(input: {\n invitationId: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be revoked' }\n }\n\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'revoked', revokedAt: input.now ?? new Date() })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return { succeeded: true, value: { invitation: toInvitationView(updated!, row.inviterEmail) } }\n }\n\n async function getPreview(token: string, now: Date = new Date()): Promise<InvitationOutcome<InvitationPreview>> {\n const row = await getInvitationByToken(token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n return {\n succeeded: true,\n value: {\n workspaceId: invitation.workspaceId,\n workspaceName: row.workspaceName,\n email: invitation.email,\n inviterEmail: row.inviterEmail,\n permissions: invitation.permissions,\n status: invitation.status,\n expiresAt: invitation.expiresAt,\n },\n }\n }\n\n async function acceptInvitation(input: {\n token: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ workspaceId: string }>> {\n const now = input.now ?? new Date()\n const row = await getInvitationByToken(input.token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n const [currentUser] = await db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(eq(users.id, input.userId))\n .limit(1)\n if (!currentUser) return { succeeded: false, status: 401, error: 'Authentication required' }\n\n if (normalizeInvitationEmail(currentUser.email) !== invitation.email) {\n return { succeeded: false, status: 403, error: 'Sign in with the invited email address to accept this invitation' }\n }\n\n const collisionCheck = await findUsersByNormalizedEmail(invitation.email)\n if (collisionCheck.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before accepting this invitation.',\n }\n }\n\n if (!currentUser.emailVerified) {\n return { succeeded: false, status: 403, error: 'Verify your email before accepting this invitation' }\n }\n\n let orgMember = await getOrganizationMember(invitation.organizationId, currentUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n\n if (invitation.status === 'accepted') {\n const existing = orgMember ? await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id) : null\n if (existing) return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n return { succeeded: false, status: 409, error: 'Invitation has already been accepted' }\n }\n if (invitation.status === 'expired') return { succeeded: false, status: 409, error: 'Invitation has expired' }\n if (invitation.status === 'revoked') return { succeeded: false, status: 409, error: 'Invitation has been revoked' }\n\n if (!orgMember) {\n const [inserted] = await db.insert(organizationMembers).values({\n organizationId: invitation.organizationId,\n userId: currentUser.id,\n role: 'member',\n createdAt: now,\n updatedAt: now,\n }).returning()\n if (!inserted) return { succeeded: false, status: 500, error: 'Failed to create organization membership' }\n orgMember = inserted\n }\n\n const existingMember = await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id)\n if (!existingMember) {\n await db.insert(workspaceMembers).values({\n workspaceId: invitation.workspaceId,\n organizationMemberId: orgMember.id,\n userId: currentUser.id,\n role: invitation.permissions,\n inviteEmail: invitation.email,\n // members-api nulls the token on accept to release the unique slot; the\n // invitations flow keeps it (its `inv_` tokens never collide), so the\n // materialized row carries the originating token for audit.\n inviteToken: invitation.token,\n invitedAt: now,\n acceptedAt: now,\n })\n if (memberSyncSeam?.add) {\n const userId = currentUser.id\n const role = invitation.permissions as WorkspaceRole\n fireSync(() => memberSyncSeam.add!({ workspaceId: invitation.workspaceId, userId, role }))\n }\n }\n\n await db.update(workspaceInvitations)\n .set({ status: 'accepted', acceptedAt: now })\n .where(eq(workspaceInvitations.id, invitation.id))\n\n return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n }\n\n // ── internal query helpers ──\n\n async function expirePendingInvitations(workspaceId: string, email: string | undefined, now: Date) {\n const conditions = [\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.status, 'pending' as const),\n lte(workspaceInvitations.expiresAt, now),\n ]\n if (email) conditions.push(eq(workspaceInvitations.email, email))\n await db.update(workspaceInvitations).set({ status: 'expired' }).where(and(...conditions))\n }\n\n async function getPendingInvitation(workspaceId: string, email: string) {\n const [invitation] = await db\n .select({ id: workspaceInvitations.id })\n .from(workspaceInvitations)\n .where(and(\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.email, email),\n eq(workspaceInvitations.status, 'pending' as const),\n ))\n .limit(1)\n return invitation ?? null\n }\n\n async function checkExistingAccountAccess(\n organizationId: string,\n workspaceId: string,\n email: string,\n invitedByUserId: string,\n ): Promise<\n | { succeeded: false; status: number; error: string }\n | { succeeded: true; inviterEmail: string | null; isExistingOrgMember: boolean }\n > {\n const matchingUsers = await findUsersByNormalizedEmail(email)\n const [inviter] = await db\n .select({ email: users.email })\n .from(users)\n .where(eq(users.id, invitedByUserId))\n .limit(1)\n\n if (matchingUsers.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before inviting this user.',\n }\n }\n const existingUser = matchingUsers[0]\n if (!existingUser) return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: false }\n\n const orgMember = await getOrganizationMember(organizationId, existingUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n if (orgMember) {\n const workspaceMember = await getWorkspaceMemberByOrganizationMember(workspaceId, orgMember.id)\n if (workspaceMember) return { succeeded: false, status: 409, error: 'User is already a workspace member' }\n }\n\n return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: Boolean(orgMember) }\n }\n\n async function findUsersByNormalizedEmail(email: string) {\n return db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(sql`lower(${users.email}) = ${email}`)\n }\n\n async function getOrganizationMember(organizationId: string, userId: string) {\n const [member] = await db\n .select()\n .from(organizationMembers)\n .where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId)))\n .limit(1)\n return member ?? null\n }\n\n async function getWorkspaceMemberByOrganizationMember(workspaceId: string, organizationMemberId: string) {\n const [member] = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(\n eq(workspaceMembers.workspaceId, workspaceId),\n eq(workspaceMembers.organizationMemberId, organizationMemberId),\n ))\n .limit(1)\n return member ?? null\n }\n\n async function getInvitationById(invitationId: string) {\n const [row] = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.id, invitationId))\n .limit(1)\n return row ?? null\n }\n\n async function getInvitationByToken(token: string) {\n const [row] = await db\n .select({\n invitation: workspaceInvitations,\n workspaceName: workspaces.name,\n inviterEmail: users.email,\n })\n .from(workspaceInvitations)\n .innerJoin(workspaces, eq(workspaces.id, workspaceInvitations.workspaceId))\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.token, token))\n .limit(1)\n return row ?? null\n }\n\n async function markInvitationExpired(invitationId: string) {\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'expired' })\n .where(eq(workspaceInvitations.id, invitationId))\n .returning()\n return updated!\n }\n\n function toInvitationView(invitation: WorkspaceInvitationRow, inviterEmail: string | null): WorkspaceInvitationView {\n return {\n id: invitation.id,\n workspaceId: invitation.workspaceId,\n organizationId: invitation.organizationId,\n email: invitation.email,\n invitedByUserId: invitation.invitedByUserId,\n inviterEmail,\n permissions: invitation.permissions,\n token: invitation.token,\n status: invitation.status,\n emailStatus: invitation.emailStatus,\n expiresAt: invitation.expiresAt,\n createdAt: invitation.createdAt,\n acceptedAt: invitation.acceptedAt,\n revokedAt: invitation.revokedAt,\n lastSentAt: invitation.lastSentAt,\n }\n }\n\n return { createInvitation, listInvitations, resendInvitation, revokeInvitation, getPreview, acceptInvitation }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/teams/invitations-api.ts"],"sourcesContent":["/**\n * Framework-neutral email-invitation lifecycle for the teams module: create /\n * list / resend / revoke / preview / accept over the dedicated\n * `workspace_invitation` table (from `createWorkspaceInvitationTable`). Lifted out\n * of any one app's route file — each app binds this once to its own db/tables/\n * access and mounts the handlers in its own routes with its own auth.\n *\n * Unlike `members-api` (whose \"invite\" is a pending workspaceMember row, no\n * email), this models the rich lifecycle: a dedicated invitation row with status,\n * 7-day expiry, emailStatus, resend, and revoke. On accept it materializes the\n * `organizationMembers` + `workspaceMembers` rows, so `members-api.listMembers`\n * still surfaces accepted members; pending invites live only here.\n *\n * Handlers return a discriminated `InvitationOutcome` (not a `Response`) so the\n * route adapter maps `{ status }` to its own framework's response. Three seams:\n * - `sendInvitationEmail` (REQUIRED) — the app's mail transport. Returns a\n * typed outcome; a failed send never blocks invitation creation (emailStatus\n * is recorded as 'failed' and the invite link is still returned).\n * - `enforceSeat` (OPTIONAL) — billing/seat-limit gate, called at create time\n * only when the invite would consume a NEW seat. Reused from members-api.\n * - `memberSyncSeam.add` (OPTIONAL) — fire-and-forget propagation of the new\n * member to a sandbox/external system on accept. Fail-soft by contract.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { and, eq, lte, sql } from 'drizzle-orm'\nimport {\n type InvitationEmailStatus,\n type InvitationPermission,\n type InvitationStatus,\n generateInvitationToken,\n getInvitationExpiresAt,\n inviteUrlForToken,\n normalizeInvitationEmail,\n parseInvitationPermission,\n} from './invitations'\nimport { type WorkspaceRole, hasWorkspaceRole } from './roles'\nimport { type EnforceSeatSeam, type MemberSyncSeam, SeatLimitError } from './members-api'\nimport type { TeamDatabase, WorkspaceAccessApi } from './drizzle/access'\nimport type { TeamParentTable, TeamTables } from './drizzle/schema'\nimport type { WorkspaceInvitationRow, WorkspaceInvitationTables } from './drizzle/invitations-schema'\n\n// Re-export the seams an adopter wires, so a consumer can import everything the\n// invitations API needs from this one subpath.\nexport { SeatLimitError } from './members-api'\nexport type { EnforceSeatSeam, MemberSyncSeam } from './members-api'\n\n/** The app's mail transport. Returns a typed outcome; never throws to the API. */\nexport interface SendInvitationEmailInput {\n to: string\n workspaceName: string\n inviterEmail: string\n permission: InvitationPermission\n inviteUrl: string\n expiresAt: Date\n}\nexport type SendInvitationEmailResult = { succeeded: true } | { succeeded: false; error: string }\nexport interface SendInvitationEmailSeam {\n (input: SendInvitationEmailInput): Promise<SendInvitationEmailResult>\n}\n\n/** The product's user table, narrowed to the columns the queries read. */\nexport interface InvitationUserTable {\n id: any\n email: any\n emailVerified: any\n}\n\n/** The product's workspace table, narrowed to the columns the queries read. */\nexport interface InvitationWorkspaceTable {\n id: any\n name: any\n}\n\nexport interface InvitationsApiOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The invitation table from `createWorkspaceInvitationTable`. */\n invitationsTable: WorkspaceInvitationTables\n userTable: TeamParentTable & InvitationUserTable\n workspaceTable: TeamParentTable & InvitationWorkspaceTable\n access: Pick<WorkspaceAccessApi, 'getWorkspaceAccess'>\n /** REQUIRED — the app's mail transport (e.g. Resend + renderInvitationEmail). */\n sendInvitationEmail: SendInvitationEmailSeam\n /** OPTIONAL — seat-limit gate at create time; mirrors members-api. */\n enforceSeat?: EnforceSeatSeam\n /** OPTIONAL — fire-and-forget member propagation on accept. */\n memberSyncSeam?: Pick<MemberSyncSeam, 'add'>\n /** Inviter display fallback when the inviter's email is unknown. */\n productDisplayName?: string\n}\n\nexport interface WorkspaceInvitationView {\n id: string\n workspaceId: string\n organizationId: string\n email: string\n invitedByUserId: string\n inviterEmail: string | null\n permissions: InvitationPermission\n token: string\n status: InvitationStatus\n emailStatus: InvitationEmailStatus\n expiresAt: Date\n createdAt: Date\n acceptedAt: Date | null\n revokedAt: Date | null\n lastSentAt: Date | null\n}\n\nexport interface InvitationPreview {\n workspaceId: string\n workspaceName: string\n email: string\n inviterEmail: string | null\n permissions: InvitationPermission\n status: InvitationStatus\n expiresAt: Date\n}\n\nexport type InvitationOutcome<T> =\n | { succeeded: true; value: T }\n | { succeeded: false; status: number; error: string }\n\n/**\n * Build the invitations API bound to one product's db/tables/access/seams.\n * Returns the six lifecycle handlers an app mounts in its routes.\n */\nexport function createInvitationsApi(opts: InvitationsApiOptions) {\n const { db, tables, access, sendInvitationEmail, enforceSeat, memberSyncSeam } = opts\n const { organizationMembers, workspaceMembers } = tables\n const { workspaceInvitations } = opts.invitationsTable\n const users = opts.userTable\n const workspaces = opts.workspaceTable\n const productDisplayName = opts.productDisplayName ?? 'A workspace admin'\n\n function fireSync(op: () => Promise<void> | void) {\n try {\n Promise.resolve(op()).catch(() => {})\n } catch {\n // seam threw synchronously — swallow; sync is best-effort by contract.\n }\n }\n\n async function createInvitation(input: {\n workspaceId: string\n email: string\n permissions: string | undefined\n invitedByUserId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const now = input.now ?? new Date()\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.invitedByUserId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const permissions = parseInvitationPermission(input.permissions ?? 'editor')\n if (!permissions) return { succeeded: false, status: 400, error: 'Invalid permissions' }\n if (!hasWorkspaceRole(accessRow.role, permissions)) {\n return { succeeded: false, status: 403, error: 'Cannot assign higher permissions than your own' }\n }\n\n const email = normalizeInvitationEmail(input.email)\n if (!email) return { succeeded: false, status: 400, error: 'Missing email' }\n\n const organizationId = accessRow.organization.id\n const workspaceName = (accessRow.workspace as { name: string }).name\n\n await expirePendingInvitations(input.workspaceId, email, now)\n\n const existingPending = await getPendingInvitation(input.workspaceId, email)\n if (existingPending) return { succeeded: false, status: 409, error: 'A pending invitation already exists for this email' }\n\n const accountCheck = await checkExistingAccountAccess(organizationId, input.workspaceId, email, input.invitedByUserId)\n if (!accountCheck.succeeded) return accountCheck\n\n // Seat gate: only a genuinely NEW seat. The checks above reject re-invites,\n // existing workspace members, and org admins; an existing plain org member is\n // already billable (no new seat), so skip the gate for them — mirrors\n // members-api. The pending row is not inserted yet, so the seam's count\n // reflects current usage and denies the invite that would push OVER the limit\n // (at invite time, not accept time).\n if (enforceSeat && !accountCheck.isExistingOrgMember) {\n try {\n await enforceSeat({ actorId: input.invitedByUserId, organizationId })\n } catch (err) {\n if (err instanceof SeatLimitError) return { succeeded: false, status: err.status, error: err.message }\n throw err\n }\n }\n\n const token = generateInvitationToken()\n const expiresAt = getInvitationExpiresAt(now)\n\n const [created] = await db.insert(workspaceInvitations).values({\n workspaceId: input.workspaceId,\n organizationId,\n email,\n invitedByUserId: input.invitedByUserId,\n permissions,\n token,\n status: 'pending',\n emailStatus: 'not_sent',\n expiresAt,\n createdAt: now,\n }).returning()\n\n const inviteUrl = inviteUrlForToken(input.origin, token)\n const emailResult = await sendInvitationEmail({\n to: email,\n workspaceName,\n inviterEmail: accountCheck.inviterEmail ?? productDisplayName,\n permission: permissions,\n inviteUrl,\n expiresAt,\n })\n\n const emailStatus: InvitationEmailStatus = emailResult.succeeded ? 'sent' : 'failed'\n const [updated] = await db.update(workspaceInvitations)\n .set({ emailStatus, lastSentAt: emailResult.succeeded ? now : null })\n .where(eq(workspaceInvitations.id, created!.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, accountCheck.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function listInvitations(input: {\n workspaceId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitations: Array<WorkspaceInvitationView & { inviteUrl: string }> }>> {\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n await expirePendingInvitations(input.workspaceId, undefined, input.now ?? new Date())\n\n const rows = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.workspaceId, input.workspaceId))\n .orderBy(sql`${workspaceInvitations.createdAt} desc`)\n\n return {\n succeeded: true,\n value: {\n invitations: rows.map((row: { invitation: WorkspaceInvitationRow; inviterEmail: string | null }) => ({\n ...toInvitationView(row.invitation, row.inviterEmail),\n inviteUrl: inviteUrlForToken(input.origin, row.invitation.token),\n })),\n },\n }\n }\n\n async function resendInvitation(input: {\n invitationId: string\n userId: string\n origin: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView; inviteUrl: string; emailError?: string }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n\n const now = input.now ?? new Date()\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be resent' }\n }\n if (row.invitation.expiresAt <= now) {\n await markInvitationExpired(row.invitation.id)\n return { succeeded: false, status: 409, error: 'Invitation has expired' }\n }\n\n const workspaceName = (accessRow.workspace as { name: string }).name\n const inviteUrl = inviteUrlForToken(input.origin, row.invitation.token)\n const emailResult = await sendInvitationEmail({\n to: row.invitation.email,\n workspaceName,\n inviterEmail: row.inviterEmail ?? productDisplayName,\n permission: row.invitation.permissions,\n inviteUrl,\n expiresAt: row.invitation.expiresAt,\n })\n\n const [updated] = await db.update(workspaceInvitations)\n .set({\n emailStatus: emailResult.succeeded ? 'sent' : 'failed',\n lastSentAt: emailResult.succeeded ? now : row.invitation.lastSentAt,\n })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return {\n succeeded: true,\n value: {\n invitation: toInvitationView(updated!, row.inviterEmail),\n inviteUrl,\n emailError: emailResult.succeeded ? undefined : emailResult.error,\n },\n }\n }\n\n async function revokeInvitation(input: {\n invitationId: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ invitation: WorkspaceInvitationView }>> {\n const row = await getInvitationById(input.invitationId)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n const accessRow = await access.getWorkspaceAccess(row.invitation.workspaceId, input.userId, 'admin')\n if (!accessRow) return { succeeded: false, status: 404, error: 'Workspace not found' }\n if (row.invitation.status !== 'pending') {\n return { succeeded: false, status: 409, error: 'Only pending invitations can be revoked' }\n }\n\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'revoked', revokedAt: input.now ?? new Date() })\n .where(eq(workspaceInvitations.id, row.invitation.id))\n .returning()\n\n return { succeeded: true, value: { invitation: toInvitationView(updated!, row.inviterEmail) } }\n }\n\n async function getPreview(token: string, now: Date = new Date()): Promise<InvitationOutcome<InvitationPreview>> {\n const row = await getInvitationByToken(token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n return {\n succeeded: true,\n value: {\n workspaceId: invitation.workspaceId,\n workspaceName: row.workspaceName,\n email: invitation.email,\n inviterEmail: row.inviterEmail,\n permissions: invitation.permissions,\n status: invitation.status,\n expiresAt: invitation.expiresAt,\n },\n }\n }\n\n async function acceptInvitation(input: {\n token: string\n userId: string\n now?: Date\n }): Promise<InvitationOutcome<{ workspaceId: string }>> {\n const now = input.now ?? new Date()\n const row = await getInvitationByToken(input.token)\n if (!row) return { succeeded: false, status: 404, error: 'Invitation not found' }\n\n let invitation = row.invitation\n if (invitation.status === 'pending' && invitation.expiresAt <= now) {\n invitation = await markInvitationExpired(invitation.id)\n }\n\n const [currentUser] = await db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(eq(users.id, input.userId))\n .limit(1)\n if (!currentUser) return { succeeded: false, status: 401, error: 'Authentication required' }\n\n if (normalizeInvitationEmail(currentUser.email) !== invitation.email) {\n return { succeeded: false, status: 403, error: 'Sign in with the invited email address to accept this invitation' }\n }\n\n const collisionCheck = await findUsersByNormalizedEmail(invitation.email)\n if (collisionCheck.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before accepting this invitation.',\n }\n }\n\n let orgMember = await getOrganizationMember(invitation.organizationId, currentUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n\n if (invitation.status === 'accepted') {\n const existing = orgMember ? await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id) : null\n if (existing) return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n return { succeeded: false, status: 409, error: 'Invitation has already been accepted' }\n }\n if (invitation.status === 'expired') return { succeeded: false, status: 409, error: 'Invitation has expired' }\n if (invitation.status === 'revoked') return { succeeded: false, status: 409, error: 'Invitation has been revoked' }\n\n if (!orgMember) {\n const [inserted] = await db.insert(organizationMembers).values({\n organizationId: invitation.organizationId,\n userId: currentUser.id,\n role: 'member',\n createdAt: now,\n updatedAt: now,\n }).returning()\n if (!inserted) return { succeeded: false, status: 500, error: 'Failed to create organization membership' }\n orgMember = inserted\n }\n\n const existingMember = await getWorkspaceMemberByOrganizationMember(invitation.workspaceId, orgMember.id)\n if (!existingMember) {\n await db.insert(workspaceMembers).values({\n workspaceId: invitation.workspaceId,\n organizationMemberId: orgMember.id,\n userId: currentUser.id,\n role: invitation.permissions,\n inviteEmail: invitation.email,\n // members-api nulls the token on accept to release the unique slot; the\n // invitations flow keeps it (its `inv_` tokens never collide), so the\n // materialized row carries the originating token for audit.\n inviteToken: invitation.token,\n invitedAt: now,\n acceptedAt: now,\n })\n if (memberSyncSeam?.add) {\n const userId = currentUser.id\n const role = invitation.permissions as WorkspaceRole\n fireSync(() => memberSyncSeam.add!({ workspaceId: invitation.workspaceId, userId, role }))\n }\n }\n\n await db.update(workspaceInvitations)\n .set({ status: 'accepted', acceptedAt: now })\n .where(eq(workspaceInvitations.id, invitation.id))\n\n return { succeeded: true, value: { workspaceId: invitation.workspaceId } }\n }\n\n // ── internal query helpers ──\n\n async function expirePendingInvitations(workspaceId: string, email: string | undefined, now: Date) {\n const conditions = [\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.status, 'pending' as const),\n lte(workspaceInvitations.expiresAt, now),\n ]\n if (email) conditions.push(eq(workspaceInvitations.email, email))\n await db.update(workspaceInvitations).set({ status: 'expired' }).where(and(...conditions))\n }\n\n async function getPendingInvitation(workspaceId: string, email: string) {\n const [invitation] = await db\n .select({ id: workspaceInvitations.id })\n .from(workspaceInvitations)\n .where(and(\n eq(workspaceInvitations.workspaceId, workspaceId),\n eq(workspaceInvitations.email, email),\n eq(workspaceInvitations.status, 'pending' as const),\n ))\n .limit(1)\n return invitation ?? null\n }\n\n async function checkExistingAccountAccess(\n organizationId: string,\n workspaceId: string,\n email: string,\n invitedByUserId: string,\n ): Promise<\n | { succeeded: false; status: number; error: string }\n | { succeeded: true; inviterEmail: string | null; isExistingOrgMember: boolean }\n > {\n const matchingUsers = await findUsersByNormalizedEmail(email)\n const [inviter] = await db\n .select({ email: users.email })\n .from(users)\n .where(eq(users.id, invitedByUserId))\n .limit(1)\n\n if (matchingUsers.length > 1) {\n return {\n succeeded: false,\n status: 409,\n error: 'Multiple accounts use this email. Contact support to reconcile before inviting this user.',\n }\n }\n const existingUser = matchingUsers[0]\n if (!existingUser) return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: false }\n\n const orgMember = await getOrganizationMember(organizationId, existingUser.id)\n if (orgMember?.role === 'owner' || orgMember?.role === 'admin') {\n return { succeeded: false, status: 409, error: 'This user already has account-level access.' }\n }\n if (orgMember) {\n const workspaceMember = await getWorkspaceMemberByOrganizationMember(workspaceId, orgMember.id)\n if (workspaceMember) return { succeeded: false, status: 409, error: 'User is already a workspace member' }\n }\n\n return { succeeded: true, inviterEmail: inviter?.email ?? null, isExistingOrgMember: Boolean(orgMember) }\n }\n\n async function findUsersByNormalizedEmail(email: string) {\n return db\n .select({ id: users.id, email: users.email, emailVerified: users.emailVerified })\n .from(users)\n .where(sql`lower(${users.email}) = ${email}`)\n }\n\n async function getOrganizationMember(organizationId: string, userId: string) {\n const [member] = await db\n .select()\n .from(organizationMembers)\n .where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId)))\n .limit(1)\n return member ?? null\n }\n\n async function getWorkspaceMemberByOrganizationMember(workspaceId: string, organizationMemberId: string) {\n const [member] = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(\n eq(workspaceMembers.workspaceId, workspaceId),\n eq(workspaceMembers.organizationMemberId, organizationMemberId),\n ))\n .limit(1)\n return member ?? null\n }\n\n async function getInvitationById(invitationId: string) {\n const [row] = await db\n .select({ invitation: workspaceInvitations, inviterEmail: users.email })\n .from(workspaceInvitations)\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.id, invitationId))\n .limit(1)\n return row ?? null\n }\n\n async function getInvitationByToken(token: string) {\n const [row] = await db\n .select({\n invitation: workspaceInvitations,\n workspaceName: workspaces.name,\n inviterEmail: users.email,\n })\n .from(workspaceInvitations)\n .innerJoin(workspaces, eq(workspaces.id, workspaceInvitations.workspaceId))\n .leftJoin(users, eq(users.id, workspaceInvitations.invitedByUserId))\n .where(eq(workspaceInvitations.token, token))\n .limit(1)\n return row ?? null\n }\n\n async function markInvitationExpired(invitationId: string) {\n const [updated] = await db.update(workspaceInvitations)\n .set({ status: 'expired' })\n .where(eq(workspaceInvitations.id, invitationId))\n .returning()\n return updated!\n }\n\n function toInvitationView(invitation: WorkspaceInvitationRow, inviterEmail: string | null): WorkspaceInvitationView {\n return {\n id: invitation.id,\n workspaceId: invitation.workspaceId,\n organizationId: invitation.organizationId,\n email: invitation.email,\n invitedByUserId: invitation.invitedByUserId,\n inviterEmail,\n permissions: invitation.permissions,\n token: invitation.token,\n status: invitation.status,\n emailStatus: invitation.emailStatus,\n expiresAt: invitation.expiresAt,\n createdAt: invitation.createdAt,\n acceptedAt: invitation.acceptedAt,\n revokedAt: invitation.revokedAt,\n lastSentAt: invitation.lastSentAt,\n }\n }\n\n return { createInvitation, listInvitations, resendInvitation, revokeInvitation, getPreview, acceptInvitation }\n}\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  ConfirmDialog,
3
3
  VaultPane
4
- } from "../chunk-IITUMDKF.js";
4
+ } from "../chunk-JJ36K7AN.js";
5
5
  export {
6
6
  ConfirmDialog,
7
7
  VaultPane
@@ -1,7 +1,7 @@
1
1
  // src/vault/lazy.tsx
2
2
  import { lazy } from "react";
3
3
  var VaultPaneLazy = lazy(
4
- () => import("../VaultPane-YCMQBRIB.js").then((m) => ({ default: m.VaultPane }))
4
+ () => import("../VaultPane-J3S3CFPK.js").then((m) => ({ default: m.VaultPane }))
5
5
  );
6
6
  export {
7
7
  VaultPaneLazy
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-app",
3
- "version": "0.42.8",
3
+ "version": "0.42.10",
4
4
  "packageManager": "pnpm@10.33.4",
5
5
  "description": "Application-shell framework for Tangle agent products: a bounded tool loop, the structured agent→app tool side channel, integration-hub client, per-workspace billing, and crypto — composed over the Tangle agent substrate through typed seams.",
6
6
  "keywords": [
@@ -1,7 +0,0 @@
1
- import {
2
- VaultPane
3
- } from "./chunk-IITUMDKF.js";
4
- export {
5
- VaultPane
6
- };
7
- //# sourceMappingURL=VaultPane-YCMQBRIB.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/vault/VaultPane.tsx","../src/vault/ConfirmDialog.tsx"],"sourcesContent":["/**\n * The shared 3-pane vault: tree | artifact viewer | optional agent dock. This is\n * pure shell MECHANISM — selection, the dirty-guard + pending-nav state machine,\n * rich/source editor modes, create/delete/refresh, skeletons, an error boundary,\n * and an empty state. It renders NO file tree and NO artifact viewer of its own:\n * those arrive through the `renderTree` / `renderArtifact` / `renderDock` seams,\n * so a product wires sandbox-ui's RichFileTree + FileArtifactPane in ~10 lines.\n *\n * Data flows exclusively through `port` (a `VaultDataPort`). The pane never\n * imports a fetch client, a router, a toast system, or a markdown library — the\n * optional `codec` seam supplies rich/source parsing (identity passthrough by\n * default). Chrome uses the shared theme tokens (bg-card, border-border, …).\n */\n\nimport {\n Component,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ErrorInfo,\n type ReactNode,\n} from 'react'\nimport { ConfirmDialog } from './ConfirmDialog'\nimport type {\n VaultEditorMode,\n VaultFile,\n VaultMarkdownCodec,\n VaultPaneProps,\n VaultRichParts,\n VaultTreeNode,\n} from './contracts'\n\nconst IDENTITY_CODEC: VaultMarkdownCodec = {\n parse: (raw) => raw,\n serialize: (parts) => (typeof parts === 'string' ? parts : String(parts ?? '')),\n}\n\ntype PendingNav = { type: 'open'; path: string } | { type: 'close' } | null\n\nfunction collectFilePaths(nodes: VaultTreeNode[], into: Set<string>): Set<string> {\n for (const node of nodes) {\n if (node.type === 'file') into.add(node.path)\n if (node.children) collectFilePaths(node.children, into)\n }\n return into\n}\n\nfunction countFiles(nodes: VaultTreeNode[]): number {\n return nodes.reduce(\n (sum, node) => (node.type === 'file' ? sum + 1 : sum + countFiles(node.children ?? [])),\n 0,\n )\n}\n\nclass EditorErrorBoundary extends Component<{ children: ReactNode; onReset?: () => void }, { error: unknown }> {\n state: { error: unknown } = { error: null }\n static getDerivedStateFromError(error: unknown) {\n return { error }\n }\n componentDidCatch(error: unknown, info: ErrorInfo) {\n console.error('Vault crashed:', error, info)\n }\n render() {\n if (this.state.error) {\n const msg = this.state.error instanceof Error\n ? this.state.error.message\n : typeof this.state.error === 'string'\n ? this.state.error\n : 'Something went wrong loading the vault'\n return (\n <div className=\"flex h-full flex-1 flex-col items-center justify-center p-8 text-center\">\n <h3 className=\"mb-1 text-sm font-medium text-foreground\">Vault failed to load</h3>\n <p className=\"mb-4 max-w-xs text-xs text-muted-foreground\">{String(msg)}</p>\n <button\n type=\"button\"\n onClick={() => { this.setState({ error: null }); this.props.onReset?.() }}\n className=\"inline-flex h-8 items-center rounded-md border border-border px-3 text-xs font-medium text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n Try again\n </button>\n </div>\n )\n }\n return this.props.children\n }\n}\n\nfunction TreeSkeleton() {\n return (\n <div className=\"space-y-2 p-4\" aria-hidden=\"true\">\n {[32, 48, 40, 52].map((w, i) => (\n <div key={i} className=\"h-4 animate-pulse rounded bg-muted\" style={{ width: `${w * 4}px` }} />\n ))}\n </div>\n )\n}\n\nfunction EditorSkeleton() {\n return (\n <div className=\"space-y-3 p-8\" aria-hidden=\"true\">\n <div className=\"h-5 w-1/2 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-full animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-3/4 animate-pulse rounded bg-muted\" />\n <div className=\"h-4 w-2/3 animate-pulse rounded bg-muted\" />\n </div>\n )\n}\n\nfunction EmptyState() {\n return (\n <div className=\"flex h-full flex-col items-center justify-center p-8 text-center\">\n <h3 className=\"text-sm font-medium text-foreground\">Open a vault document</h3>\n <p className=\"mt-1 max-w-xs text-xs text-muted-foreground\">\n Select a file from the directory, or create a new one.\n </p>\n </div>\n )\n}\n\nexport function VaultPane(props: VaultPaneProps) {\n const {\n port,\n renderTree,\n renderArtifact,\n renderDock,\n canWrite = true,\n selectedPath: controlledPath,\n onSelectedPathChange,\n codec,\n className,\n dockToggle,\n refreshKey,\n headerActions,\n } = props\n\n const activeCodec = codec ?? IDENTITY_CODEC\n const controlled = controlledPath !== undefined\n const isMarkdownCapable = codec !== undefined\n // `false` → a persistent dock (no toggle, always open with the selected file).\n const persistentDock = dockToggle === false\n const dockToggleCfg = dockToggle ? dockToggle : { label: 'Discuss', disabledWhenDirty: true }\n\n const [tree, setTree] = useState<VaultTreeNode[]>([])\n const [treeLoading, setTreeLoading] = useState(true)\n const [internalPath, setInternalPath] = useState<string | null>(null)\n const selectedPath = controlled ? (controlledPath ?? null) : internalPath\n\n const [selectedFile, setSelectedFile] = useState<VaultFile | null>(null)\n const [fileLoading, setFileLoading] = useState(false)\n const [saving, setSaving] = useState(false)\n\n const [editorMode, setEditorMode] = useState<VaultEditorMode>('rich')\n const [richDraft, setRichDraft] = useState<VaultRichParts>('')\n const [sourceDraft, setSourceDraft] = useState('')\n const [isDirty, setIsDirty] = useState(false)\n\n const [createOpen, setCreateOpen] = useState(false)\n const [newPath, setNewPath] = useState('')\n const [creating, setCreating] = useState(false)\n const [deleteOpen, setDeleteOpen] = useState(false)\n const [deleting, setDeleting] = useState(false)\n const [dockOpen, setDockOpen] = useState(false)\n const [pendingNav, setPendingNav] = useState<PendingNav>(null)\n\n const savedContentRef = useRef('')\n const loadedPathRef = useRef<string | null>(null)\n\n const filePaths = useMemo(() => collectFilePaths(tree, new Set<string>()), [tree])\n const treeRoot = useMemo<VaultTreeNode>(\n () => ({ name: 'Vault', path: '', type: 'directory', children: tree }),\n [tree],\n )\n const fileCount = useMemo(() => countFiles(tree), [tree])\n\n const commitPath = useCallback(\n (next: string | null) => {\n if (!controlled) setInternalPath(next)\n onSelectedPathChange?.(next)\n },\n [controlled, onSelectedPathChange],\n )\n\n const refresh = useCallback(async () => {\n setTreeLoading(true)\n try {\n setTree(await port.listTree())\n } finally {\n setTreeLoading(false)\n }\n }, [port])\n\n useEffect(() => {\n void refresh()\n }, [refresh, refreshKey])\n\n useEffect(() => {\n if (!selectedPath) {\n setSelectedFile(null)\n setFileLoading(false)\n loadedPathRef.current = null\n return\n }\n let cancelled = false\n const path = selectedPath\n setFileLoading(true)\n void (async () => {\n try {\n const file = await port.readFile(path)\n if (!cancelled) setSelectedFile(file)\n } catch {\n if (!cancelled) setSelectedFile(null)\n } finally {\n if (!cancelled) setFileLoading(false)\n }\n })()\n return () => {\n cancelled = true\n }\n }, [port, selectedPath, refreshKey])\n\n useEffect(() => {\n if (!selectedFile) {\n loadedPathRef.current = null\n savedContentRef.current = ''\n setRichDraft('')\n setSourceDraft('')\n setEditorMode('rich')\n setIsDirty(false)\n return\n }\n const pathChanged = loadedPathRef.current !== selectedFile.path\n loadedPathRef.current = selectedFile.path\n savedContentRef.current = selectedFile.content\n setRichDraft(activeCodec.parse(selectedFile.content))\n setSourceDraft(selectedFile.content)\n if (pathChanged) setEditorMode('rich')\n setIsDirty(false)\n setDockOpen(false)\n }, [selectedFile, activeCodec])\n\n const guardedOpen = useCallback(\n (path: string) => {\n if (path === selectedPath) return\n if (isDirty) {\n setPendingNav({ type: 'open', path })\n return\n }\n commitPath(path)\n },\n [isDirty, selectedPath, commitPath],\n )\n\n const guardedClose = useCallback(() => {\n if (isDirty) {\n setPendingNav({ type: 'close' })\n return\n }\n commitPath(null)\n setSelectedFile(null)\n }, [isDirty, commitPath])\n\n const confirmDiscard = useCallback(() => {\n const nav = pendingNav\n setPendingNav(null)\n setIsDirty(false)\n if (!nav) return\n if (nav.type === 'open') {\n commitPath(nav.path)\n } else {\n commitPath(null)\n setSelectedFile(null)\n }\n }, [pendingNav, commitPath])\n\n const showRichMode = useCallback(() => {\n setEditorMode((mode) => {\n if (mode === 'rich') return mode\n setRichDraft(activeCodec.parse(sourceDraft))\n setIsDirty(sourceDraft !== savedContentRef.current)\n return 'rich'\n })\n }, [activeCodec, sourceDraft])\n\n const showSourceMode = useCallback(() => {\n setEditorMode((mode) => {\n if (mode === 'source') return mode\n const content = isDirty ? activeCodec.serialize(richDraft) : savedContentRef.current\n setSourceDraft(content)\n setIsDirty(content !== savedContentRef.current)\n return 'source'\n })\n }, [activeCodec, isDirty, richDraft])\n\n const onSourceChange = useCallback((next: string) => {\n setSourceDraft(next)\n setIsDirty(next !== savedContentRef.current)\n }, [])\n\n const onRichChange = useCallback((next: VaultRichParts) => {\n setRichDraft(next)\n setIsDirty(activeCodec.serialize(next) !== savedContentRef.current)\n }, [activeCodec])\n\n const saveCurrent = useCallback(async () => {\n if (!selectedFile) return\n const content = editorMode === 'source' ? sourceDraft : activeCodec.serialize(richDraft)\n setSaving(true)\n try {\n await port.writeFile(selectedFile.path, content)\n savedContentRef.current = content\n setSelectedFile({ ...selectedFile, content })\n setSourceDraft(content)\n setRichDraft(activeCodec.parse(content))\n setIsDirty(false)\n } finally {\n setSaving(false)\n }\n }, [selectedFile, editorMode, sourceDraft, richDraft, activeCodec, port])\n\n const handleCreate = useCallback(async () => {\n const trimmed = newPath.trim()\n if (!trimmed) return\n setCreating(true)\n try {\n const created = await port.createFile(trimmed)\n setCreateOpen(false)\n setNewPath('')\n await refresh()\n commitPath(created)\n } finally {\n setCreating(false)\n }\n }, [newPath, port, refresh, commitPath])\n\n const handleDelete = useCallback(async () => {\n if (!selectedFile) return\n setDeleting(true)\n try {\n await port.deleteFile(selectedFile.path)\n setDeleteOpen(false)\n setIsDirty(false)\n commitPath(null)\n setSelectedFile(null)\n await refresh()\n } finally {\n setDeleting(false)\n }\n }, [selectedFile, port, refresh, commitPath])\n\n return (\n <EditorErrorBoundary onReset={() => { commitPath(null); setSelectedFile(null) }}>\n <div className={`flex min-h-0 flex-1 overflow-hidden ${className ?? ''}`}>\n <div className=\"flex w-[23rem] min-w-[23rem] flex-col border-r border-border bg-background\">\n <div className=\"flex items-center justify-between gap-3 border-b border-border px-4 py-3\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"text-sm font-semibold text-foreground\">Vault</span>\n <span className=\"text-xs text-muted-foreground\">\n {fileCount} file{fileCount === 1 ? '' : 's'}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center gap-1\">\n {headerActions}\n <button\n type=\"button\"\n aria-label=\"Refresh vault\"\n onClick={() => void refresh()}\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n ↻\n </button>\n {canWrite && (\n <button\n type=\"button\"\n aria-label=\"New vault file\"\n onClick={() => setCreateOpen(true)}\n className=\"inline-flex h-8 w-8 items-center justify-center rounded-md bg-primary text-primary-foreground transition-colors hover:bg-primary/90 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n +\n </button>\n )}\n </div>\n </div>\n <div className=\"flex-1 overflow-y-auto\">\n {treeLoading ? (\n <TreeSkeleton />\n ) : (\n renderTree({\n root: treeRoot,\n selectedPath: selectedPath ?? undefined,\n onSelect: (path) => { if (filePaths.has(path)) guardedOpen(path) },\n })\n )}\n </div>\n </div>\n\n <div className=\"flex min-w-0 flex-1 flex-col overflow-hidden\">\n {selectedFile && (\n <div className=\"flex shrink-0 items-center justify-between border-b border-border bg-card px-4 py-1.5\">\n <span data-vault-path className=\"truncate text-xs text-muted-foreground\">{selectedFile.path}</span>\n <div className=\"flex items-center gap-1\">\n {canWrite && isMarkdownCapable && (\n <div className=\"mr-1 flex items-center gap-1\">\n <button\n type=\"button\"\n aria-label=\"Edit as rich text\"\n aria-pressed={editorMode === 'rich'}\n onClick={showRichMode}\n className={`inline-flex h-7 items-center rounded px-2 text-xs transition-colors ${\n editorMode === 'rich'\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n Rich\n </button>\n <button\n type=\"button\"\n aria-label=\"Edit as source\"\n aria-pressed={editorMode === 'source'}\n onClick={showSourceMode}\n className={`inline-flex h-7 items-center rounded px-2 text-xs transition-colors ${\n editorMode === 'source'\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n Source\n </button>\n </div>\n )}\n {renderDock && !persistentDock && (\n <button\n type=\"button\"\n aria-label={dockToggleCfg.label}\n aria-pressed={dockOpen}\n disabled={(dockToggleCfg.disabledWhenDirty ?? true) && isDirty}\n title={(dockToggleCfg.disabledWhenDirty ?? true) && isDirty ? 'Save your changes first' : (dockToggleCfg.title ?? dockToggleCfg.label)}\n onClick={() => setDockOpen((v) => !v)}\n className={`inline-flex items-center gap-1 rounded px-2 py-0.5 text-xs transition-colors disabled:pointer-events-none disabled:opacity-40 ${\n dockOpen\n ? 'bg-primary text-primary-foreground'\n : 'text-muted-foreground hover:bg-muted hover:text-foreground'\n }`}\n >\n {dockToggleCfg.label}\n </button>\n )}\n {canWrite && (\n <button\n type=\"button\"\n aria-label=\"Delete this file\"\n title=\"Delete file\"\n onClick={() => setDeleteOpen(true)}\n className=\"p-1 text-muted-foreground transition-colors hover:text-destructive\"\n >\n ✕\n </button>\n )}\n </div>\n </div>\n )}\n <div className=\"flex-1 overflow-hidden\">\n {fileLoading ? (\n <EditorSkeleton />\n ) : selectedFile && canWrite && isMarkdownCapable && editorMode === 'source' ? (\n <SourceEditor\n path={selectedFile.path}\n content={sourceDraft}\n saving={saving}\n dirty={isDirty}\n onChange={onSourceChange}\n onSave={() => void saveCurrent()}\n />\n ) : selectedFile ? (\n renderArtifact({\n file: selectedFile,\n loading: false,\n mode: editorMode,\n canWrite,\n richDraft,\n dirty: isDirty,\n onRichChange,\n onSave: () => void saveCurrent(),\n })\n ) : (\n <EmptyState />\n )}\n </div>\n </div>\n\n {renderDock && selectedFile && renderDock({\n file: selectedFile,\n open: persistentDock ? true : dockOpen,\n onClose: persistentDock ? () => {} : () => setDockOpen(false),\n })}\n\n <ConfirmDialog\n open={createOpen}\n title=\"Create vault file\"\n description=\"Add a new document to this vault.\"\n confirmLabel={creating ? 'Creating…' : 'Create'}\n confirmDisabled={creating || !newPath.trim()}\n onConfirm={() => void handleCreate()}\n onCancel={() => { setCreateOpen(false); setNewPath('') }}\n >\n <input\n value={newPath}\n autoFocus\n onChange={(e) => setNewPath(e.target.value)}\n placeholder=\"e.g. playbooks/new-strategy.md\"\n aria-label=\"New file path\"\n className=\"h-9 w-full rounded-md border border-border bg-background px-3 text-sm text-foreground outline-none placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-primary/60\"\n />\n </ConfirmDialog>\n\n <ConfirmDialog\n open={deleteOpen}\n title=\"Delete file?\"\n description={`This permanently removes ${selectedFile?.path ?? 'this file'} from the vault.`}\n confirmLabel={deleting ? 'Deleting…' : 'Delete file'}\n confirmDisabled={deleting}\n destructive\n onConfirm={() => void handleDelete()}\n onCancel={() => setDeleteOpen(false)}\n />\n\n <ConfirmDialog\n open={pendingNav !== null}\n title=\"Discard unsaved changes?\"\n description=\"Your edits to this document haven't been saved. Continue and lose them?\"\n confirmLabel=\"Discard changes\"\n destructive\n onConfirm={confirmDiscard}\n onCancel={() => setPendingNav(null)}\n />\n </div>\n </EditorErrorBoundary>\n )\n}\n\nfunction SourceEditor({\n path,\n content,\n saving,\n dirty,\n onChange,\n onSave,\n}: {\n path: string\n content: string\n saving: boolean\n dirty: boolean\n onChange: (content: string) => void\n onSave: () => void\n}) {\n return (\n <div className=\"flex h-full min-h-0 flex-col bg-background\">\n <div className=\"flex items-center justify-between gap-2 border-b border-border px-4 py-2\">\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">{path}</p>\n <div className=\"flex shrink-0 items-center gap-2\">\n <span className=\"rounded-full border border-border bg-background px-2.5 py-1 text-xs text-muted-foreground\">\n {dirty ? 'Unsaved changes' : 'Saved'}\n </span>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving || !dirty}\n className=\"inline-flex h-7 items-center rounded-md bg-primary px-2 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:pointer-events-none disabled:opacity-50\"\n >\n {saving ? 'Saving…' : 'Save'}\n </button>\n </div>\n </div>\n <textarea\n value={content}\n onChange={(event) => onChange(event.target.value)}\n spellCheck={false}\n aria-label=\"Source editor\"\n className=\"min-h-0 flex-1 resize-none border-0 bg-background p-4 font-mono text-sm leading-6 text-foreground outline-none\"\n />\n </div>\n )\n}\n","/**\n * Self-contained confirm dialog — no dialog library. A focus-trapped modal with\n * Esc-to-cancel and Enter-to-confirm, used for create / delete / discard-unsaved\n * flows so the pane carries zero UI-kit dependency.\n */\n\nimport { useEffect, useRef, type ReactNode } from 'react'\n\nexport interface ConfirmDialogProps {\n open: boolean\n title: string\n description?: ReactNode\n confirmLabel?: string\n cancelLabel?: string\n /** Styles the confirm button as a destructive action. */\n destructive?: boolean\n /** Disables the confirm button (e.g. while the action is in flight). */\n confirmDisabled?: boolean\n onConfirm: () => void\n onCancel: () => void\n /** Optional body (e.g. an input field for the create flow). */\n children?: ReactNode\n}\n\nexport function ConfirmDialog({\n open,\n title,\n description,\n confirmLabel = 'Confirm',\n cancelLabel = 'Cancel',\n destructive = false,\n confirmDisabled = false,\n onConfirm,\n onCancel,\n children,\n}: ConfirmDialogProps) {\n const panelRef = useRef<HTMLDivElement>(null)\n const confirmRef = useRef<HTMLButtonElement>(null)\n\n useEffect(() => {\n if (!open) return\n confirmRef.current?.focus()\n }, [open])\n\n if (!open) return null\n\n function onKeyDown(event: React.KeyboardEvent) {\n if (event.key === 'Escape') {\n event.preventDefault()\n onCancel()\n return\n }\n if (event.key === 'Enter' && !confirmDisabled) {\n event.preventDefault()\n onConfirm()\n return\n }\n if (event.key !== 'Tab') return\n const focusable = panelRef.current?.querySelectorAll<HTMLElement>(\n 'button:not([disabled]), input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])',\n )\n if (!focusable || focusable.length === 0) return\n const first = focusable[0]\n const last = focusable[focusable.length - 1]\n const active = document.activeElement as HTMLElement | null\n if (event.shiftKey && active === first) {\n event.preventDefault()\n last?.focus()\n } else if (!event.shiftKey && active === last) {\n event.preventDefault()\n first?.focus()\n }\n }\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4\"\n onMouseDown={(e) => { if (e.target === e.currentTarget) onCancel() }}\n >\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={title}\n onKeyDown={onKeyDown}\n className=\"w-full max-w-md rounded-lg border border-border bg-card p-5 shadow-lg\"\n >\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n {description && <p className=\"mt-1.5 text-xs text-muted-foreground\">{description}</p>}\n {children && <div className=\"mt-3\">{children}</div>}\n <div className=\"mt-5 flex justify-end gap-2\">\n <button\n type=\"button\"\n onClick={onCancel}\n className=\"inline-flex h-8 items-center rounded-md px-3 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60\"\n >\n {cancelLabel}\n </button>\n <button\n ref={confirmRef}\n type=\"button\"\n onClick={onConfirm}\n disabled={confirmDisabled}\n className={`inline-flex h-8 items-center rounded-md px-3 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary/60 disabled:pointer-events-none disabled:opacity-50 ${\n destructive\n ? 'bg-destructive text-destructive-foreground hover:bg-destructive/90'\n : 'bg-primary text-primary-foreground hover:bg-primary/90'\n }`}\n >\n {confirmLabel}\n </button>\n </div>\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":["useEffect","useRef","jsx","jsxs","useRef","useEffect"]}