@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.
- package/dist/VaultPane-J3S3CFPK.js +7 -0
- package/dist/{chunk-IITUMDKF.js → chunk-JJ36K7AN.js} +117 -27
- package/dist/chunk-JJ36K7AN.js.map +1 -0
- package/dist/teams/invitations-api.js +0 -3
- package/dist/teams/invitations-api.js.map +1 -1
- package/dist/vault/index.js +1 -1
- package/dist/vault/lazy.js +1 -1
- package/package.json +1 -1
- package/dist/VaultPane-YCMQBRIB.js +0 -7
- package/dist/chunk-IITUMDKF.js.map +0 -1
- /package/dist/{VaultPane-YCMQBRIB.js.map → VaultPane-J3S3CFPK.js.map} +0 -0
|
@@ -119,11 +119,37 @@ function collectFilePaths(nodes, into) {
|
|
|
119
119
|
}
|
|
120
120
|
return into;
|
|
121
121
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
|
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 =
|
|
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)
|
|
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
|
|
388
|
-
/* @__PURE__ */
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
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-
|
|
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":[]}
|
package/dist/vault/index.js
CHANGED
package/dist/vault/lazy.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/vault/lazy.tsx
|
|
2
2
|
import { lazy } from "react";
|
|
3
3
|
var VaultPaneLazy = lazy(
|
|
4
|
-
() => import("../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.
|
|
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 +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"]}
|
|
File without changes
|