granola-toolkit 0.48.0 → 0.50.0
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/cli.js +867 -45
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ const granolaTransportPaths = {
|
|
|
19
19
|
authStatus: "/auth/status",
|
|
20
20
|
authUnlock: "/auth/unlock",
|
|
21
21
|
automationMatches: "/automation/matches",
|
|
22
|
+
automationArtefacts: "/automation/artefacts",
|
|
22
23
|
automationRules: "/automation/rules",
|
|
23
24
|
automationRuns: "/automation/runs",
|
|
24
25
|
events: "/events",
|
|
@@ -86,9 +87,29 @@ function granolaAutomationRunsPath(options = {}) {
|
|
|
86
87
|
status: options.status
|
|
87
88
|
});
|
|
88
89
|
}
|
|
90
|
+
function granolaAutomationArtefactsPath(options = {}) {
|
|
91
|
+
return appendSearchParams(granolaTransportPaths.automationArtefacts, {
|
|
92
|
+
kind: options.kind,
|
|
93
|
+
limit: options.limit,
|
|
94
|
+
meetingId: options.meetingId,
|
|
95
|
+
status: options.status
|
|
96
|
+
});
|
|
97
|
+
}
|
|
89
98
|
function granolaAutomationRunDecisionPath(id, decision) {
|
|
90
99
|
return `${granolaTransportPaths.automationRuns}/${encodeURIComponent(id)}/${decision}`;
|
|
91
100
|
}
|
|
101
|
+
function granolaAutomationArtefactRerunPath(id) {
|
|
102
|
+
return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}/rerun`;
|
|
103
|
+
}
|
|
104
|
+
function granolaAutomationArtefactPath(id) {
|
|
105
|
+
return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}`;
|
|
106
|
+
}
|
|
107
|
+
function granolaAutomationArtefactDecisionPath(id, decision) {
|
|
108
|
+
return `${granolaAutomationArtefactPath(id)}/${decision}`;
|
|
109
|
+
}
|
|
110
|
+
function granolaAutomationArtefactUpdatePath(id) {
|
|
111
|
+
return `${granolaAutomationArtefactPath(id)}/update`;
|
|
112
|
+
}
|
|
92
113
|
function granolaExportJobRerunPath(id) {
|
|
93
114
|
return `${granolaTransportPaths.exportJobs}/${encodeURIComponent(id)}/rerun`;
|
|
94
115
|
}
|
|
@@ -191,6 +212,12 @@ var GranolaServerClient = class GranolaServerClient {
|
|
|
191
212
|
async inspectAuth() {
|
|
192
213
|
return await this.requestJson(granolaTransportPaths.authStatus);
|
|
193
214
|
}
|
|
215
|
+
async listAutomationArtefacts(options = {}) {
|
|
216
|
+
return await this.requestJson(granolaAutomationArtefactsPath(options));
|
|
217
|
+
}
|
|
218
|
+
async getAutomationArtefact(id) {
|
|
219
|
+
return await this.requestJson(granolaAutomationArtefactPath(id));
|
|
220
|
+
}
|
|
194
221
|
async listAutomationRules() {
|
|
195
222
|
return await this.requestJson(granolaTransportPaths.automationRules);
|
|
196
223
|
}
|
|
@@ -208,6 +235,23 @@ var GranolaServerClient = class GranolaServerClient {
|
|
|
208
235
|
method: "POST"
|
|
209
236
|
});
|
|
210
237
|
}
|
|
238
|
+
async resolveAutomationArtefact(id, decision, options = {}) {
|
|
239
|
+
return await this.requestJson(granolaAutomationArtefactDecisionPath(id, decision), {
|
|
240
|
+
body: JSON.stringify(options),
|
|
241
|
+
headers: { "content-type": "application/json" },
|
|
242
|
+
method: "POST"
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async updateAutomationArtefact(id, patch) {
|
|
246
|
+
return await this.requestJson(granolaAutomationArtefactUpdatePath(id), {
|
|
247
|
+
body: JSON.stringify(patch),
|
|
248
|
+
headers: { "content-type": "application/json" },
|
|
249
|
+
method: "POST"
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
async rerunAutomationArtefact(id) {
|
|
253
|
+
return await this.requestJson(granolaAutomationArtefactRerunPath(id), { method: "POST" });
|
|
254
|
+
}
|
|
211
255
|
async inspectSync() {
|
|
212
256
|
return cloneValue(this.#state.sync);
|
|
213
257
|
}
|
|
@@ -1476,7 +1520,7 @@ function renderGranolaTuiMeetingTab(bundle, tab) {
|
|
|
1476
1520
|
}
|
|
1477
1521
|
}
|
|
1478
1522
|
function buildGranolaTuiSummary(state, meetingSource) {
|
|
1479
|
-
return `auth ${state.auth.mode === "api-key" ? "API key" : state.auth.mode === "stored-session" ? "stored session" : "supabase.json"} | ${state.documents.loaded ? `${state.documents.count} documents` : "documents pending"} | ${state.folders.loaded ? `${state.folders.count} folders` : "folders pending"} | ${state.cache.loaded ? `${state.cache.transcriptCount} transcript sets` : state.cache.configured ? "cache configured" : "cache missing"} | ${state.index.loaded ? `${state.index.meetingCount} indexed` : "index pending"} | ${state.sync.running ? "sync running" : state.sync.lastError ? "sync needs attention" : state.sync.lastCompletedAt ? `synced ${state.sync.lastCompletedAt.slice(11, 16)}` : "sync idle"} | ${state.automation.pendingRunCount ? `${state.automation.pendingRunCount} pending` : state.automation.runCount ? `${state.automation.runCount} runs` : "automation idle"} | source ${meetingSource}`;
|
|
1523
|
+
return `auth ${state.auth.mode === "api-key" ? "API key" : state.auth.mode === "stored-session" ? "stored session" : "supabase.json"} | ${state.documents.loaded ? `${state.documents.count} documents` : "documents pending"} | ${state.folders.loaded ? `${state.folders.count} folders` : "folders pending"} | ${state.cache.loaded ? `${state.cache.transcriptCount} transcript sets` : state.cache.configured ? "cache configured" : "cache missing"} | ${state.index.loaded ? `${state.index.meetingCount} indexed` : "index pending"} | ${state.sync.running ? "sync running" : state.sync.lastError ? "sync needs attention" : state.sync.lastCompletedAt ? `synced ${state.sync.lastCompletedAt.slice(11, 16)}` : "sync idle"} | ${state.automation.pendingArtefactCount ? `${state.automation.pendingArtefactCount} artefacts pending` : state.automation.pendingRunCount ? `${state.automation.pendingRunCount} runs pending` : state.automation.runCount ? `${state.automation.runCount} runs` : "automation idle"} | source ${meetingSource}`;
|
|
1480
1524
|
}
|
|
1481
1525
|
//#endregion
|
|
1482
1526
|
//#region src/tui/theme.ts
|
|
@@ -1527,6 +1571,14 @@ function statusLabel(run) {
|
|
|
1527
1571
|
default: return granolaTuiTheme.dim(run.status);
|
|
1528
1572
|
}
|
|
1529
1573
|
}
|
|
1574
|
+
function artefactStatusLabel(artefact) {
|
|
1575
|
+
switch (artefact.status) {
|
|
1576
|
+
case "approved": return granolaTuiTheme.info(artefact.status);
|
|
1577
|
+
case "generated": return granolaTuiTheme.warning(artefact.status);
|
|
1578
|
+
case "rejected": return granolaTuiTheme.error(artefact.status);
|
|
1579
|
+
default: return granolaTuiTheme.dim(artefact.status);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1530
1582
|
var GranolaTuiAutomationOverlay = class {
|
|
1531
1583
|
focused = false;
|
|
1532
1584
|
#selectedIndex = 0;
|
|
@@ -1534,8 +1586,17 @@ var GranolaTuiAutomationOverlay = class {
|
|
|
1534
1586
|
this.options = options;
|
|
1535
1587
|
}
|
|
1536
1588
|
invalidate() {}
|
|
1589
|
+
get items() {
|
|
1590
|
+
return [...this.options.artefacts.map((artefact) => ({
|
|
1591
|
+
artefact,
|
|
1592
|
+
kind: "artefact"
|
|
1593
|
+
})), ...this.options.runs.map((run) => ({
|
|
1594
|
+
kind: "run",
|
|
1595
|
+
run
|
|
1596
|
+
}))];
|
|
1597
|
+
}
|
|
1537
1598
|
get selected() {
|
|
1538
|
-
return this.
|
|
1599
|
+
return this.items[this.#selectedIndex];
|
|
1539
1600
|
}
|
|
1540
1601
|
handleInput(data) {
|
|
1541
1602
|
if (matchesKey(data, "esc")) {
|
|
@@ -1547,15 +1608,24 @@ var GranolaTuiAutomationOverlay = class {
|
|
|
1547
1608
|
return;
|
|
1548
1609
|
}
|
|
1549
1610
|
if (matchesKey(data, "down")) {
|
|
1550
|
-
this.#selectedIndex = Math.min(this.
|
|
1611
|
+
this.#selectedIndex = Math.min(this.items.length - 1, this.#selectedIndex + 1);
|
|
1551
1612
|
return;
|
|
1552
1613
|
}
|
|
1553
1614
|
if (matchesKey(data, "enter") || matchesKey(data, "a")) {
|
|
1554
|
-
if (this.selected?.status === "pending") this.options.
|
|
1615
|
+
if (this.selected?.kind === "run" && this.selected.run.status === "pending") this.options.onApproveRun(this.selected.run.id);
|
|
1616
|
+
if (this.selected?.kind === "artefact" && this.selected.artefact.status !== "superseded") this.options.onApproveArtefact(this.selected.artefact.id);
|
|
1555
1617
|
return;
|
|
1556
1618
|
}
|
|
1557
1619
|
if (matchesKey(data, "r")) {
|
|
1558
|
-
if (this.selected?.
|
|
1620
|
+
if (this.selected?.kind === "run" && this.selected.run.status === "pending") {
|
|
1621
|
+
this.options.onRejectRun(this.selected.run.id);
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
if (this.selected?.kind === "artefact" && this.selected.artefact.status !== "superseded") this.options.onRejectArtefact(this.selected.artefact.id);
|
|
1625
|
+
return;
|
|
1626
|
+
}
|
|
1627
|
+
if (matchesKey(data, "e")) {
|
|
1628
|
+
if (this.selected?.kind === "artefact") this.options.onRerunArtefact(this.selected.artefact.id);
|
|
1559
1629
|
}
|
|
1560
1630
|
}
|
|
1561
1631
|
render(width) {
|
|
@@ -1564,20 +1634,20 @@ var GranolaTuiAutomationOverlay = class {
|
|
|
1564
1634
|
const maxRuns = 6;
|
|
1565
1635
|
const lines = [];
|
|
1566
1636
|
lines.push(`+${"-".repeat(bodyWidth - 2)}+`);
|
|
1567
|
-
lines.push(frameLine$2(granolaTuiTheme.strong("Automation
|
|
1568
|
-
lines.push(frameLine$2("
|
|
1637
|
+
lines.push(frameLine$2(granolaTuiTheme.strong("Automation Review"), bodyWidth));
|
|
1638
|
+
lines.push(frameLine$2("Enter/a approve r reject e rerun artefact Esc close", bodyWidth));
|
|
1569
1639
|
lines.push(frameLine$2("", bodyWidth));
|
|
1570
|
-
if (this.
|
|
1571
|
-
else for (const [index,
|
|
1640
|
+
if (this.items.length === 0) lines.push(frameLine$2(granolaTuiTheme.dim("No automation review items yet."), bodyWidth));
|
|
1641
|
+
else for (const [index, item] of this.items.slice(0, maxRuns).entries()) {
|
|
1572
1642
|
const selected = index === this.#selectedIndex;
|
|
1573
|
-
const title = `${selected ? ">" : " "} ${run.actionName} · ${statusLabel(run)}`;
|
|
1643
|
+
const title = item.kind === "artefact" ? `${selected ? ">" : " "} ${item.artefact.structured.title} · ${artefactStatusLabel(item.artefact)}` : `${selected ? ">" : " "} ${item.run.actionName} · ${statusLabel(item.run)}`;
|
|
1574
1644
|
lines.push(frameLine$2(selected ? granolaTuiTheme.selected(title) : title, bodyWidth));
|
|
1575
|
-
lines.push(frameLine$2(` ${run.ruleName} · ${run.title}`, bodyWidth));
|
|
1576
|
-
const details = run.prompt || run.result || run.error || run.eventKind;
|
|
1645
|
+
lines.push(frameLine$2(item.kind === "artefact" ? ` ${item.artefact.ruleName} · ${item.artefact.meetingId}` : ` ${item.run.ruleName} · ${item.run.title}`, bodyWidth));
|
|
1646
|
+
const details = item.kind === "artefact" ? item.artefact.structured.summary || item.artefact.structured.markdown || item.artefact.id : item.run.prompt || item.run.result || item.run.error || item.run.eventKind;
|
|
1577
1647
|
for (const line of wrapDetails(` ${details}`, innerWidth).slice(0, 2)) lines.push(frameLine$2(line, bodyWidth));
|
|
1578
1648
|
lines.push(frameLine$2("", bodyWidth));
|
|
1579
1649
|
}
|
|
1580
|
-
lines.push(frameLine$2(granolaTuiTheme.dim("
|
|
1650
|
+
lines.push(frameLine$2(granolaTuiTheme.dim("Artefacts are listed before pending ask-user runs."), bodyWidth));
|
|
1581
1651
|
lines.push(`+${"-".repeat(bodyWidth - 2)}+`);
|
|
1582
1652
|
return lines;
|
|
1583
1653
|
}
|
|
@@ -1863,6 +1933,7 @@ var GranolaTuiWorkspace = class {
|
|
|
1863
1933
|
#maxMeetings;
|
|
1864
1934
|
#appState;
|
|
1865
1935
|
#activePane = "meetings";
|
|
1936
|
+
#automationArtefacts = [];
|
|
1866
1937
|
#automationRuns = [];
|
|
1867
1938
|
#detailError = "";
|
|
1868
1939
|
#detailScroll = 0;
|
|
@@ -1898,6 +1969,7 @@ var GranolaTuiWorkspace = class {
|
|
|
1898
1969
|
this.handleAppUpdate(event);
|
|
1899
1970
|
});
|
|
1900
1971
|
await this.loadAutomationRuns();
|
|
1972
|
+
await this.loadAutomationArtefacts();
|
|
1901
1973
|
await this.loadFolders({ setStatus: false });
|
|
1902
1974
|
await this.loadMeetings({
|
|
1903
1975
|
preferredMeetingId: this.options.initialMeetingId,
|
|
@@ -1917,6 +1989,7 @@ var GranolaTuiWorkspace = class {
|
|
|
1917
1989
|
this.#selectedFolderId = event.state.ui.selectedFolderId;
|
|
1918
1990
|
this.#selectedMeetingId = event.state.ui.selectedMeetingId ?? this.#selectedMeetingId;
|
|
1919
1991
|
this.loadAutomationRuns();
|
|
1992
|
+
this.loadAutomationArtefacts();
|
|
1920
1993
|
if (this.#meetingSource === "index" && event.state.documents.loadedAt && event.state.documents.loadedAt !== previousDocumentsLoadedAt && !this.#loadingMeetings) (async () => {
|
|
1921
1994
|
await this.loadFolders({ setStatus: false });
|
|
1922
1995
|
await this.loadMeetings({ preferredMeetingId: this.#selectedMeetingId });
|
|
@@ -1977,6 +2050,12 @@ var GranolaTuiWorkspace = class {
|
|
|
1977
2050
|
this.tui.requestRender();
|
|
1978
2051
|
} catch {}
|
|
1979
2052
|
}
|
|
2053
|
+
async loadAutomationArtefacts() {
|
|
2054
|
+
try {
|
|
2055
|
+
this.#automationArtefacts = [...(await this.app.listAutomationArtefacts({ limit: 20 })).artefacts];
|
|
2056
|
+
this.tui.requestRender();
|
|
2057
|
+
} catch {}
|
|
2058
|
+
}
|
|
1980
2059
|
async loadMeetings(options = {}) {
|
|
1981
2060
|
const token = ++this.#listToken;
|
|
1982
2061
|
this.#loadingMeetings = true;
|
|
@@ -2291,19 +2370,39 @@ var GranolaTuiWorkspace = class {
|
|
|
2291
2370
|
this.tui.requestRender();
|
|
2292
2371
|
};
|
|
2293
2372
|
const overlay = new GranolaTuiAutomationOverlay({
|
|
2294
|
-
|
|
2373
|
+
artefacts: this.#automationArtefacts,
|
|
2374
|
+
onApproveArtefact: async (id) => {
|
|
2375
|
+
closeOverlay();
|
|
2376
|
+
await this.app.resolveAutomationArtefact(id, "approve");
|
|
2377
|
+
await this.loadAutomationArtefacts();
|
|
2378
|
+
this.setStatus("Artefact approved");
|
|
2379
|
+
},
|
|
2380
|
+
onApproveRun: async (id) => {
|
|
2295
2381
|
closeOverlay();
|
|
2296
2382
|
await this.app.resolveAutomationRun(id, "approve");
|
|
2297
2383
|
await this.loadAutomationRuns();
|
|
2298
2384
|
this.setStatus("Automation approved");
|
|
2299
2385
|
},
|
|
2300
2386
|
onCancel: closeOverlay,
|
|
2301
|
-
|
|
2387
|
+
onRejectArtefact: async (id) => {
|
|
2388
|
+
closeOverlay();
|
|
2389
|
+
await this.app.resolveAutomationArtefact(id, "reject");
|
|
2390
|
+
await this.loadAutomationArtefacts();
|
|
2391
|
+
this.setStatus("Artefact rejected");
|
|
2392
|
+
},
|
|
2393
|
+
onRejectRun: async (id) => {
|
|
2302
2394
|
closeOverlay();
|
|
2303
2395
|
await this.app.resolveAutomationRun(id, "reject");
|
|
2304
2396
|
await this.loadAutomationRuns();
|
|
2305
2397
|
this.setStatus("Automation rejected");
|
|
2306
2398
|
},
|
|
2399
|
+
onRerunArtefact: async (id) => {
|
|
2400
|
+
closeOverlay();
|
|
2401
|
+
await this.app.rerunAutomationArtefact(id);
|
|
2402
|
+
await this.loadAutomationArtefacts();
|
|
2403
|
+
await this.loadAutomationRuns();
|
|
2404
|
+
this.setStatus("Artefact rerun complete");
|
|
2405
|
+
},
|
|
2307
2406
|
runs: this.#automationRuns
|
|
2308
2407
|
});
|
|
2309
2408
|
this.#overlay = this.tui.showOverlay(overlay, {
|
|
@@ -2312,7 +2411,7 @@ var GranolaTuiWorkspace = class {
|
|
|
2312
2411
|
minWidth: 56,
|
|
2313
2412
|
width: "76%"
|
|
2314
2413
|
});
|
|
2315
|
-
this.setStatus("Automation
|
|
2414
|
+
this.setStatus("Automation review");
|
|
2316
2415
|
}
|
|
2317
2416
|
handleInput(data) {
|
|
2318
2417
|
if (matchesKey(data, "ctrl+c") || matchesKey(data, "q")) {
|
|
@@ -2613,6 +2712,7 @@ function defaultGranolaToolkitPersistenceLayout(options = {}) {
|
|
|
2613
2712
|
const dataDirectory = defaultGranolaToolkitDataDirectory(targetPlatform, options.homeDirectory ?? homedir());
|
|
2614
2713
|
return {
|
|
2615
2714
|
agentHarnessesFile: join(dataDirectory, "agent-harnesses.json"),
|
|
2715
|
+
automationArtefactsFile: join(dataDirectory, "automation-artefacts.json"),
|
|
2616
2716
|
automationMatchesFile: join(dataDirectory, "automation-matches.jsonl"),
|
|
2617
2717
|
automationRulesFile: join(dataDirectory, "automation-rules.json"),
|
|
2618
2718
|
automationRunsFile: join(dataDirectory, "automation-runs.jsonl"),
|
|
@@ -2662,6 +2762,7 @@ function parseHarness(value) {
|
|
|
2662
2762
|
if (!prompt && !promptFile) return;
|
|
2663
2763
|
return {
|
|
2664
2764
|
cwd: typeof record.cwd === "string" && record.cwd.trim() ? record.cwd.trim() : void 0,
|
|
2765
|
+
fallbackHarnessIds: stringArray$1(record.fallbackHarnessIds),
|
|
2665
2766
|
id,
|
|
2666
2767
|
match: parseMatch(record.match),
|
|
2667
2768
|
model: typeof record.model === "string" && record.model.trim() ? record.model.trim() : void 0,
|
|
@@ -2677,6 +2778,7 @@ function parseHarness(value) {
|
|
|
2677
2778
|
function cloneHarness(harness) {
|
|
2678
2779
|
return {
|
|
2679
2780
|
...harness,
|
|
2781
|
+
fallbackHarnessIds: harness.fallbackHarnessIds ? [...harness.fallbackHarnessIds] : void 0,
|
|
2680
2782
|
match: harness.match ? {
|
|
2681
2783
|
...harness.match,
|
|
2682
2784
|
calendarEventIds: harness.match.calendarEventIds ? [...harness.match.calendarEventIds] : void 0,
|
|
@@ -2767,6 +2869,222 @@ function createDefaultAgentHarnessStore(filePath) {
|
|
|
2767
2869
|
return new FileAgentHarnessStore(filePath);
|
|
2768
2870
|
}
|
|
2769
2871
|
//#endregion
|
|
2872
|
+
//#region src/automation-artefacts.ts
|
|
2873
|
+
const AUTOMATION_ARTEFACTS_VERSION = 1;
|
|
2874
|
+
const MAX_AUTOMATION_ARTEFACTS = 200;
|
|
2875
|
+
function cloneAttempt(attempt) {
|
|
2876
|
+
return { ...attempt };
|
|
2877
|
+
}
|
|
2878
|
+
function cloneHistoryEntry(entry) {
|
|
2879
|
+
return { ...entry };
|
|
2880
|
+
}
|
|
2881
|
+
function cloneSection(section) {
|
|
2882
|
+
return { ...section };
|
|
2883
|
+
}
|
|
2884
|
+
function cloneActionItem(actionItem) {
|
|
2885
|
+
return { ...actionItem };
|
|
2886
|
+
}
|
|
2887
|
+
function cloneStructured(structured) {
|
|
2888
|
+
return {
|
|
2889
|
+
...structured,
|
|
2890
|
+
actionItems: structured.actionItems.map((item) => cloneActionItem(item)),
|
|
2891
|
+
decisions: [...structured.decisions],
|
|
2892
|
+
followUps: [...structured.followUps],
|
|
2893
|
+
highlights: [...structured.highlights],
|
|
2894
|
+
metadata: structured.metadata ? structuredClone(structured.metadata) : void 0,
|
|
2895
|
+
sections: structured.sections.map((section) => cloneSection(section))
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
function cloneArtefact(artefact) {
|
|
2899
|
+
return {
|
|
2900
|
+
...artefact,
|
|
2901
|
+
attempts: artefact.attempts.map((attempt) => cloneAttempt(attempt)),
|
|
2902
|
+
history: artefact.history.map((entry) => cloneHistoryEntry(entry)),
|
|
2903
|
+
structured: cloneStructured(artefact.structured)
|
|
2904
|
+
};
|
|
2905
|
+
}
|
|
2906
|
+
function normaliseHistoryAction(value) {
|
|
2907
|
+
switch (value) {
|
|
2908
|
+
case "approved":
|
|
2909
|
+
case "edited":
|
|
2910
|
+
case "generated":
|
|
2911
|
+
case "rejected":
|
|
2912
|
+
case "rerun": return value;
|
|
2913
|
+
default: return;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
function normaliseHistoryEntry(value) {
|
|
2917
|
+
const record = asRecord(value);
|
|
2918
|
+
if (!record) return;
|
|
2919
|
+
const action = normaliseHistoryAction(record.action);
|
|
2920
|
+
const at = stringValue(record.at).trim();
|
|
2921
|
+
if (!action || !at) return;
|
|
2922
|
+
return {
|
|
2923
|
+
action,
|
|
2924
|
+
at,
|
|
2925
|
+
note: stringValue(record.note).trim() || void 0
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
function normaliseSection(value) {
|
|
2929
|
+
const record = asRecord(value);
|
|
2930
|
+
if (!record) return;
|
|
2931
|
+
const title = stringValue(record.title).trim();
|
|
2932
|
+
const body = stringValue(record.body).trim();
|
|
2933
|
+
if (!title || !body) return;
|
|
2934
|
+
return {
|
|
2935
|
+
body,
|
|
2936
|
+
title
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
function normaliseActionItem(value) {
|
|
2940
|
+
const record = asRecord(value);
|
|
2941
|
+
if (!record) return;
|
|
2942
|
+
const title = stringValue(record.title).trim();
|
|
2943
|
+
if (!title) return;
|
|
2944
|
+
return {
|
|
2945
|
+
dueDate: stringValue(record.dueDate).trim() || void 0,
|
|
2946
|
+
owner: stringValue(record.owner).trim() || void 0,
|
|
2947
|
+
title
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
function normaliseAttempt(value) {
|
|
2951
|
+
const record = asRecord(value);
|
|
2952
|
+
if (!record) return;
|
|
2953
|
+
const provider = stringValue(record.provider).trim();
|
|
2954
|
+
const model = stringValue(record.model).trim();
|
|
2955
|
+
const harnessId = stringValue(record.harnessId).trim();
|
|
2956
|
+
const error = stringValue(record.error).trim();
|
|
2957
|
+
if (!provider && !model && !harnessId && !error) return;
|
|
2958
|
+
return {
|
|
2959
|
+
error: error || void 0,
|
|
2960
|
+
harnessId: harnessId || void 0,
|
|
2961
|
+
model: model || void 0,
|
|
2962
|
+
provider: provider === "codex" || provider === "openai" || provider === "openrouter" ? provider : void 0
|
|
2963
|
+
};
|
|
2964
|
+
}
|
|
2965
|
+
function normaliseStructured(value) {
|
|
2966
|
+
const record = asRecord(value);
|
|
2967
|
+
if (!record) return;
|
|
2968
|
+
const title = stringValue(record.title).trim();
|
|
2969
|
+
const markdown = stringValue(record.markdown).trim();
|
|
2970
|
+
if (!title || !markdown) return;
|
|
2971
|
+
return {
|
|
2972
|
+
actionItems: Array.isArray(record.actionItems) ? record.actionItems.map((item) => normaliseActionItem(item)).filter((item) => Boolean(item)) : [],
|
|
2973
|
+
decisions: stringArray$2(record.decisions).map((item) => item.trim()).filter(Boolean),
|
|
2974
|
+
followUps: stringArray$2(record.followUps).map((item) => item.trim()).filter(Boolean),
|
|
2975
|
+
highlights: stringArray$2(record.highlights).map((item) => item.trim()).filter(Boolean),
|
|
2976
|
+
markdown,
|
|
2977
|
+
metadata: asRecord(record.metadata),
|
|
2978
|
+
sections: Array.isArray(record.sections) ? record.sections.map((section) => normaliseSection(section)).filter((section) => Boolean(section)) : [],
|
|
2979
|
+
summary: stringValue(record.summary).trim() || void 0,
|
|
2980
|
+
title
|
|
2981
|
+
};
|
|
2982
|
+
}
|
|
2983
|
+
function normaliseArtefact(value) {
|
|
2984
|
+
const record = asRecord(value);
|
|
2985
|
+
if (!record) return;
|
|
2986
|
+
const id = stringValue(record.id).trim();
|
|
2987
|
+
const runId = stringValue(record.runId).trim();
|
|
2988
|
+
const meetingId = stringValue(record.meetingId).trim();
|
|
2989
|
+
const eventId = stringValue(record.eventId).trim();
|
|
2990
|
+
const matchId = stringValue(record.matchId).trim();
|
|
2991
|
+
const ruleId = stringValue(record.ruleId).trim();
|
|
2992
|
+
const ruleName = stringValue(record.ruleName).trim();
|
|
2993
|
+
const actionId = stringValue(record.actionId).trim();
|
|
2994
|
+
const actionName = stringValue(record.actionName).trim();
|
|
2995
|
+
const createdAt = stringValue(record.createdAt).trim();
|
|
2996
|
+
const updatedAt = stringValue(record.updatedAt).trim();
|
|
2997
|
+
const rawOutput = stringValue(record.rawOutput).trim();
|
|
2998
|
+
const prompt = stringValue(record.prompt).trim();
|
|
2999
|
+
const model = stringValue(record.model).trim();
|
|
3000
|
+
const provider = stringValue(record.provider).trim();
|
|
3001
|
+
const kind = stringValue(record.kind).trim();
|
|
3002
|
+
const status = stringValue(record.status).trim();
|
|
3003
|
+
const parseMode = stringValue(record.parseMode).trim();
|
|
3004
|
+
const structured = normaliseStructured(record.structured);
|
|
3005
|
+
if (!id || !runId || !meetingId || !eventId || !matchId || !ruleId || !ruleName || !actionId || !actionName || !createdAt || !updatedAt || !rawOutput || !prompt || !model || !structured || kind !== "enrichment" && kind !== "notes" || provider !== "codex" && provider !== "openai" && provider !== "openrouter" || status !== "approved" && status !== "generated" && status !== "rejected" && status !== "superseded" || parseMode !== "json" && parseMode !== "markdown-fallback") return;
|
|
3006
|
+
return {
|
|
3007
|
+
actionId,
|
|
3008
|
+
actionName,
|
|
3009
|
+
attempts: Array.isArray(record.attempts) ? record.attempts.map((attempt) => normaliseAttempt(attempt)).filter((attempt) => Boolean(attempt)) : [],
|
|
3010
|
+
createdAt,
|
|
3011
|
+
eventId,
|
|
3012
|
+
history: Array.isArray(record.history) ? record.history.map((entry) => normaliseHistoryEntry(entry)).filter((entry) => Boolean(entry)) : [{
|
|
3013
|
+
action: "generated",
|
|
3014
|
+
at: createdAt
|
|
3015
|
+
}],
|
|
3016
|
+
id,
|
|
3017
|
+
kind,
|
|
3018
|
+
matchId,
|
|
3019
|
+
meetingId,
|
|
3020
|
+
model,
|
|
3021
|
+
parseMode,
|
|
3022
|
+
prompt,
|
|
3023
|
+
provider,
|
|
3024
|
+
rawOutput,
|
|
3025
|
+
rerunOfId: stringValue(record.rerunOfId).trim() || void 0,
|
|
3026
|
+
ruleId,
|
|
3027
|
+
ruleName,
|
|
3028
|
+
runId,
|
|
3029
|
+
status,
|
|
3030
|
+
structured,
|
|
3031
|
+
supersededById: stringValue(record.supersededById).trim() || void 0,
|
|
3032
|
+
updatedAt
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
function normaliseFile(parsed) {
|
|
3036
|
+
const record = asRecord(parsed);
|
|
3037
|
+
if (!record || record.version !== AUTOMATION_ARTEFACTS_VERSION || !Array.isArray(record.artefacts)) return {
|
|
3038
|
+
artefacts: [],
|
|
3039
|
+
version: AUTOMATION_ARTEFACTS_VERSION
|
|
3040
|
+
};
|
|
3041
|
+
return {
|
|
3042
|
+
artefacts: record.artefacts.map((artefact) => normaliseArtefact(artefact)).filter((artefact) => Boolean(artefact)).slice(0, MAX_AUTOMATION_ARTEFACTS),
|
|
3043
|
+
version: AUTOMATION_ARTEFACTS_VERSION
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
function sortArtefacts(artefacts) {
|
|
3047
|
+
return artefacts.slice().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
3048
|
+
}
|
|
3049
|
+
var FileAutomationArtefactStore = class {
|
|
3050
|
+
constructor(filePath = defaultAutomationArtefactsFilePath()) {
|
|
3051
|
+
this.filePath = filePath;
|
|
3052
|
+
}
|
|
3053
|
+
async readArtefact(id) {
|
|
3054
|
+
return (await this.readArtefacts({ limit: 0 })).find((artefact) => artefact.id === id);
|
|
3055
|
+
}
|
|
3056
|
+
async readArtefacts(options = {}) {
|
|
3057
|
+
try {
|
|
3058
|
+
const filtered = sortArtefacts(normaliseFile(parseJsonString(await readFile(this.filePath, "utf8"))).artefacts).filter((artefact) => {
|
|
3059
|
+
if (options.kind && artefact.kind !== options.kind) return false;
|
|
3060
|
+
if (options.meetingId && artefact.meetingId !== options.meetingId) return false;
|
|
3061
|
+
if (options.status && artefact.status !== options.status) return false;
|
|
3062
|
+
return true;
|
|
3063
|
+
});
|
|
3064
|
+
return (options.limit && options.limit > 0 ? filtered.slice(0, options.limit) : filtered).map((artefact) => cloneArtefact(artefact));
|
|
3065
|
+
} catch {
|
|
3066
|
+
return [];
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
async writeArtefacts(artefacts) {
|
|
3070
|
+
const payload = {
|
|
3071
|
+
artefacts: sortArtefacts(artefacts).slice(0, MAX_AUTOMATION_ARTEFACTS),
|
|
3072
|
+
version: AUTOMATION_ARTEFACTS_VERSION
|
|
3073
|
+
};
|
|
3074
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
3075
|
+
await writeFile(this.filePath, `${JSON.stringify(payload, null, 2)}\n`, {
|
|
3076
|
+
encoding: "utf8",
|
|
3077
|
+
mode: 384
|
|
3078
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
};
|
|
3081
|
+
function defaultAutomationArtefactsFilePath() {
|
|
3082
|
+
return defaultGranolaToolkitPersistenceLayout().automationArtefactsFile;
|
|
3083
|
+
}
|
|
3084
|
+
function createDefaultAutomationArtefactStore(filePath) {
|
|
3085
|
+
return new FileAutomationArtefactStore(filePath);
|
|
3086
|
+
}
|
|
3087
|
+
//#endregion
|
|
2770
3088
|
//#region src/client/auth.ts
|
|
2771
3089
|
const execFileAsync$1 = promisify(execFile);
|
|
2772
3090
|
const DEFAULT_CLIENT_ID = "client_GranolaMac";
|
|
@@ -3378,7 +3696,11 @@ function createDefaultAutomationAgentRunner(config, options = {}) {
|
|
|
3378
3696
|
//#region src/automation-actions.ts
|
|
3379
3697
|
function cloneAction$1(action) {
|
|
3380
3698
|
switch (action.kind) {
|
|
3381
|
-
case "agent": return {
|
|
3699
|
+
case "agent": return {
|
|
3700
|
+
...action,
|
|
3701
|
+
fallbackHarnessIds: action.fallbackHarnessIds ? [...action.fallbackHarnessIds] : void 0,
|
|
3702
|
+
pipeline: action.pipeline ? { ...action.pipeline } : void 0
|
|
3703
|
+
};
|
|
3382
3704
|
case "ask-user": return { ...action };
|
|
3383
3705
|
case "command": return {
|
|
3384
3706
|
...action,
|
|
@@ -3398,19 +3720,22 @@ function buildAutomationActionRunId(match, actionId) {
|
|
|
3398
3720
|
function enabledAutomationActions(rule) {
|
|
3399
3721
|
return (rule.actions ?? []).filter((action) => action.enabled !== false).map((action) => cloneAction$1(action));
|
|
3400
3722
|
}
|
|
3401
|
-
function baseRun(match, rule, action, startedAt) {
|
|
3723
|
+
function baseRun(match, rule, action, startedAt, options = {}) {
|
|
3402
3724
|
return {
|
|
3403
3725
|
actionId: action.id,
|
|
3404
3726
|
actionKind: action.kind,
|
|
3405
3727
|
actionName: automationActionName(action),
|
|
3728
|
+
artefactIds: void 0,
|
|
3406
3729
|
eventId: match.eventId,
|
|
3407
3730
|
eventKind: match.eventKind,
|
|
3408
3731
|
folders: match.folders.map((folder) => ({ ...folder })),
|
|
3409
|
-
id: buildAutomationActionRunId(match, action.id),
|
|
3732
|
+
id: options.runId ?? buildAutomationActionRunId(match, action.id),
|
|
3733
|
+
matchId: match.id,
|
|
3410
3734
|
matchedAt: match.matchedAt,
|
|
3411
3735
|
meetingId: match.meetingId,
|
|
3412
3736
|
ruleId: rule.id,
|
|
3413
3737
|
ruleName: rule.name,
|
|
3738
|
+
rerunOfId: options.rerunOfId,
|
|
3414
3739
|
startedAt,
|
|
3415
3740
|
status: "completed",
|
|
3416
3741
|
tags: [...match.tags],
|
|
@@ -3442,16 +3767,20 @@ function skippedRun(run, finishedAt, reason) {
|
|
|
3442
3767
|
status: "skipped"
|
|
3443
3768
|
};
|
|
3444
3769
|
}
|
|
3445
|
-
async function executeAutomationAction(match, rule, action, handlers) {
|
|
3446
|
-
const run = baseRun(match, rule, action, handlers.nowIso());
|
|
3770
|
+
async function executeAutomationAction(match, rule, action, handlers, options = {}) {
|
|
3771
|
+
const run = baseRun(match, rule, action, handlers.nowIso(), options);
|
|
3447
3772
|
switch (action.kind) {
|
|
3448
3773
|
case "agent": try {
|
|
3449
|
-
const result = await handlers.runAgent(match, rule, action);
|
|
3774
|
+
const result = await handlers.runAgent(match, rule, action, run);
|
|
3450
3775
|
return completedRun(run, handlers.nowIso(), {
|
|
3776
|
+
artefactIds: result.artefactIds ? [...result.artefactIds] : void 0,
|
|
3451
3777
|
meta: {
|
|
3778
|
+
attempts: result.attempts,
|
|
3779
|
+
artefactIds: result.artefactIds,
|
|
3452
3780
|
command: result.command,
|
|
3453
3781
|
dryRun: result.dryRun,
|
|
3454
3782
|
model: result.model,
|
|
3783
|
+
pipelineKind: result.pipelineKind,
|
|
3455
3784
|
provider: result.provider,
|
|
3456
3785
|
systemPrompt: result.systemPrompt
|
|
3457
3786
|
},
|
|
@@ -3554,6 +3883,7 @@ function createDefaultAutomationMatchStore(filePath) {
|
|
|
3554
3883
|
function cloneRun(run) {
|
|
3555
3884
|
return {
|
|
3556
3885
|
...run,
|
|
3886
|
+
artefactIds: run.artefactIds ? [...run.artefactIds] : void 0,
|
|
3557
3887
|
folders: run.folders.map((folder) => ({ ...folder })),
|
|
3558
3888
|
meta: run.meta ? structuredClone(run.meta) : void 0,
|
|
3559
3889
|
tags: [...run.tags]
|
|
@@ -3617,7 +3947,11 @@ function cloneRule(rule) {
|
|
|
3617
3947
|
}
|
|
3618
3948
|
function cloneAction(action) {
|
|
3619
3949
|
switch (action.kind) {
|
|
3620
|
-
case "agent": return {
|
|
3950
|
+
case "agent": return {
|
|
3951
|
+
...action,
|
|
3952
|
+
fallbackHarnessIds: action.fallbackHarnessIds ? [...action.fallbackHarnessIds] : void 0,
|
|
3953
|
+
pipeline: action.pipeline ? { ...action.pipeline } : void 0
|
|
3954
|
+
};
|
|
3621
3955
|
case "ask-user": return { ...action };
|
|
3622
3956
|
case "command": return {
|
|
3623
3957
|
...action,
|
|
@@ -3641,6 +3975,11 @@ function stringRecord(value) {
|
|
|
3641
3975
|
if (entries.length === 0) return;
|
|
3642
3976
|
return Object.fromEntries(entries.map(([key, item]) => [key.trim(), item.trim()]));
|
|
3643
3977
|
}
|
|
3978
|
+
function parsePipeline(value) {
|
|
3979
|
+
const record = asRecord(value);
|
|
3980
|
+
const kind = record ? stringValue(record.kind).trim() : typeof value === "string" ? value.trim() : "";
|
|
3981
|
+
return kind === "enrichment" || kind === "notes" ? { kind } : void 0;
|
|
3982
|
+
}
|
|
3644
3983
|
function parseAction(value, index) {
|
|
3645
3984
|
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
3646
3985
|
const record = value;
|
|
@@ -3655,6 +3994,7 @@ function parseAction(value, index) {
|
|
|
3655
3994
|
const prompt = typeof record.prompt === "string" && record.prompt.trim() ? record.prompt.trim() : void 0;
|
|
3656
3995
|
const promptFile = typeof record.promptFile === "string" && record.promptFile.trim() ? record.promptFile.trim() : void 0;
|
|
3657
3996
|
const harnessId = typeof record.harnessId === "string" && record.harnessId.trim() ? record.harnessId.trim() : void 0;
|
|
3997
|
+
const fallbackHarnessIds = stringArray(record.fallbackHarnessIds);
|
|
3658
3998
|
const systemPrompt = typeof record.systemPrompt === "string" && record.systemPrompt.trim() ? record.systemPrompt.trim() : void 0;
|
|
3659
3999
|
const systemPromptFile = typeof record.systemPromptFile === "string" && record.systemPromptFile.trim() ? record.systemPromptFile.trim() : void 0;
|
|
3660
4000
|
if (!prompt && !promptFile && !harnessId) return;
|
|
@@ -3662,11 +4002,13 @@ function parseAction(value, index) {
|
|
|
3662
4002
|
cwd: typeof record.cwd === "string" && record.cwd.trim() ? record.cwd.trim() : void 0,
|
|
3663
4003
|
dryRun: typeof record.dryRun === "boolean" ? record.dryRun : void 0,
|
|
3664
4004
|
enabled,
|
|
4005
|
+
fallbackHarnessIds,
|
|
3665
4006
|
harnessId,
|
|
3666
4007
|
id,
|
|
3667
4008
|
kind,
|
|
3668
4009
|
model: typeof record.model === "string" && record.model.trim() ? record.model.trim() : void 0,
|
|
3669
4010
|
name,
|
|
4011
|
+
pipeline: parsePipeline(record.pipeline),
|
|
3670
4012
|
prompt,
|
|
3671
4013
|
promptFile,
|
|
3672
4014
|
provider,
|
|
@@ -5013,6 +5355,122 @@ function meetingIdsFromSearchResults(results) {
|
|
|
5013
5355
|
return results.map((result) => result.id);
|
|
5014
5356
|
}
|
|
5015
5357
|
//#endregion
|
|
5358
|
+
//#region src/processing.ts
|
|
5359
|
+
function firstParagraph(markdown) {
|
|
5360
|
+
const paragraph = markdown.split(/\n\s*\n/).map((block) => block.replace(/^#+\s+/gm, "").trim()).find((block) => block.length > 0);
|
|
5361
|
+
return paragraph ? paragraph.slice(0, 280) : void 0;
|
|
5362
|
+
}
|
|
5363
|
+
function markdownSections(markdown) {
|
|
5364
|
+
const lines = markdown.split("\n");
|
|
5365
|
+
const sections = [];
|
|
5366
|
+
let currentTitle = "Overview";
|
|
5367
|
+
let currentBody = [];
|
|
5368
|
+
const pushCurrent = () => {
|
|
5369
|
+
const body = currentBody.join("\n").trim();
|
|
5370
|
+
if (!body) return;
|
|
5371
|
+
sections.push({
|
|
5372
|
+
body,
|
|
5373
|
+
title: currentTitle
|
|
5374
|
+
});
|
|
5375
|
+
};
|
|
5376
|
+
for (const line of lines) {
|
|
5377
|
+
const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
|
|
5378
|
+
if (headingMatch) {
|
|
5379
|
+
pushCurrent();
|
|
5380
|
+
currentTitle = headingMatch[2]?.trim() || "Section";
|
|
5381
|
+
currentBody = [];
|
|
5382
|
+
continue;
|
|
5383
|
+
}
|
|
5384
|
+
currentBody.push(line);
|
|
5385
|
+
}
|
|
5386
|
+
pushCurrent();
|
|
5387
|
+
return sections;
|
|
5388
|
+
}
|
|
5389
|
+
function normaliseStrings(value) {
|
|
5390
|
+
return stringArray$2(value).map((item) => item.trim()).filter(Boolean);
|
|
5391
|
+
}
|
|
5392
|
+
function normaliseActionItems(value) {
|
|
5393
|
+
if (!Array.isArray(value)) return [];
|
|
5394
|
+
return value.map((item) => {
|
|
5395
|
+
const record = asRecord(item);
|
|
5396
|
+
if (!record) return;
|
|
5397
|
+
const title = stringValue(record.title).trim();
|
|
5398
|
+
if (!title) return;
|
|
5399
|
+
return {
|
|
5400
|
+
dueDate: stringValue(record.dueDate).trim() || void 0,
|
|
5401
|
+
owner: stringValue(record.owner).trim() || void 0,
|
|
5402
|
+
title
|
|
5403
|
+
};
|
|
5404
|
+
}).filter((item) => Boolean(item));
|
|
5405
|
+
}
|
|
5406
|
+
function normaliseSections(value) {
|
|
5407
|
+
if (!Array.isArray(value)) return [];
|
|
5408
|
+
return value.map((item) => {
|
|
5409
|
+
const record = asRecord(item);
|
|
5410
|
+
if (!record) return;
|
|
5411
|
+
const title = stringValue(record.title).trim();
|
|
5412
|
+
const body = stringValue(record.body).trim();
|
|
5413
|
+
if (!title || !body) return;
|
|
5414
|
+
return {
|
|
5415
|
+
body,
|
|
5416
|
+
title
|
|
5417
|
+
};
|
|
5418
|
+
}).filter((item) => Boolean(item));
|
|
5419
|
+
}
|
|
5420
|
+
function extractJsonPayload(rawOutput) {
|
|
5421
|
+
const direct = parseJsonString(rawOutput);
|
|
5422
|
+
if (direct) return direct;
|
|
5423
|
+
const fencedMatch = rawOutput.match(/```(?:json)?\s*([\s\S]+?)```/i);
|
|
5424
|
+
if (!fencedMatch?.[1]) return;
|
|
5425
|
+
return parseJsonString(fencedMatch[1].trim());
|
|
5426
|
+
}
|
|
5427
|
+
function buildPipelineInstructions(kind, instructions) {
|
|
5428
|
+
const task = kind === "notes" ? "Turn the transcript and meeting context into improved meeting notes." : "Turn the transcript and meeting context into a structured enrichment for downstream workflows.";
|
|
5429
|
+
return [
|
|
5430
|
+
instructions.trim(),
|
|
5431
|
+
task,
|
|
5432
|
+
"Return JSON only. Use this exact shape: {\"title\":\"string\",\"summary\":\"string\",\"markdown\":\"string\",\"sections\":[{\"title\":\"string\",\"body\":\"string\"}],\"actionItems\":[{\"title\":\"string\",\"owner\":\"string\",\"dueDate\":\"string\"}],\"decisions\":[\"string\"],\"followUps\":[\"string\"],\"highlights\":[\"string\"],\"metadata\":{}}",
|
|
5433
|
+
"Keep arrays empty instead of omitting them. markdown must contain the full Markdown result."
|
|
5434
|
+
].filter(Boolean).join("\n\n");
|
|
5435
|
+
}
|
|
5436
|
+
function parsePipelineOutput(options) {
|
|
5437
|
+
const rawOutput = options.rawOutput.trim();
|
|
5438
|
+
const payload = extractJsonPayload(rawOutput);
|
|
5439
|
+
if (payload) {
|
|
5440
|
+
const title = stringValue(payload.title).trim() || options.meetingTitle;
|
|
5441
|
+
const markdown = stringValue(payload.markdown).trim();
|
|
5442
|
+
if (markdown) return {
|
|
5443
|
+
parseMode: "json",
|
|
5444
|
+
structured: {
|
|
5445
|
+
actionItems: normaliseActionItems(payload.actionItems),
|
|
5446
|
+
decisions: normaliseStrings(payload.decisions),
|
|
5447
|
+
followUps: normaliseStrings(payload.followUps),
|
|
5448
|
+
highlights: normaliseStrings(payload.highlights),
|
|
5449
|
+
markdown,
|
|
5450
|
+
metadata: asRecord(payload.metadata),
|
|
5451
|
+
sections: normaliseSections(payload.sections),
|
|
5452
|
+
summary: stringValue(payload.summary).trim() || firstParagraph(markdown),
|
|
5453
|
+
title
|
|
5454
|
+
}
|
|
5455
|
+
};
|
|
5456
|
+
}
|
|
5457
|
+
const markdown = rawOutput || `# ${options.meetingTitle}\n`;
|
|
5458
|
+
return {
|
|
5459
|
+
parseMode: "markdown-fallback",
|
|
5460
|
+
structured: {
|
|
5461
|
+
actionItems: [],
|
|
5462
|
+
decisions: [],
|
|
5463
|
+
followUps: [],
|
|
5464
|
+
highlights: [],
|
|
5465
|
+
markdown,
|
|
5466
|
+
metadata: { fallbackKind: options.kind },
|
|
5467
|
+
sections: markdownSections(markdown),
|
|
5468
|
+
summary: firstParagraph(markdown),
|
|
5469
|
+
title: options.kind === "notes" ? `${options.meetingTitle} Generated Notes` : `${options.meetingTitle} Enrichment`
|
|
5470
|
+
}
|
|
5471
|
+
};
|
|
5472
|
+
}
|
|
5473
|
+
//#endregion
|
|
5016
5474
|
//#region src/app/core.ts
|
|
5017
5475
|
function transcriptCount(cacheData) {
|
|
5018
5476
|
return Object.values(cacheData.transcripts).filter((segments) => segments.length > 0).length;
|
|
@@ -5068,6 +5526,9 @@ function meetingTranscriptText(bundle) {
|
|
|
5068
5526
|
if (!segments?.length) return;
|
|
5069
5527
|
return segments.slice().sort((left, right) => left.startTimestamp.localeCompare(right.startTimestamp)).map((segment) => segment.text.trim()).filter(Boolean).join("\n");
|
|
5070
5528
|
}
|
|
5529
|
+
function buildAutomationArtefactId(runId, kind) {
|
|
5530
|
+
return `${kind}:${runId}`;
|
|
5531
|
+
}
|
|
5071
5532
|
function buildAutomationAgentPrompt(match, rule, instructions, bundle) {
|
|
5072
5533
|
const transcriptText = bundle ? meetingTranscriptText(bundle)?.trim() : void 0;
|
|
5073
5534
|
const context = {
|
|
@@ -5136,9 +5597,12 @@ function defaultState(config, auth, surface) {
|
|
|
5136
5597
|
return {
|
|
5137
5598
|
auth: { ...auth },
|
|
5138
5599
|
automation: {
|
|
5600
|
+
artefactCount: 0,
|
|
5601
|
+
artefactsFile: config.automation?.artefactsFile ?? defaultAutomationArtefactsFilePath(),
|
|
5139
5602
|
loaded: false,
|
|
5140
5603
|
matchCount: 0,
|
|
5141
5604
|
matchesFile: defaultAutomationMatchesFilePath(),
|
|
5605
|
+
pendingArtefactCount: 0,
|
|
5142
5606
|
pendingRunCount: 0,
|
|
5143
5607
|
ruleCount: 0,
|
|
5144
5608
|
rulesFile: config.automation?.rulesFile ?? defaultAutomationRulesFilePath(),
|
|
@@ -5188,6 +5652,7 @@ function defaultState(config, auth, surface) {
|
|
|
5188
5652
|
}
|
|
5189
5653
|
var GranolaApp = class {
|
|
5190
5654
|
#automationActionRuns;
|
|
5655
|
+
#automationArtefacts;
|
|
5191
5656
|
#automationMatches;
|
|
5192
5657
|
#automationRules;
|
|
5193
5658
|
#cacheData;
|
|
@@ -5204,6 +5669,7 @@ var GranolaApp = class {
|
|
|
5204
5669
|
this.config = config;
|
|
5205
5670
|
this.deps = deps;
|
|
5206
5671
|
this.#state = defaultState(config, deps.auth, options.surface ?? "cli");
|
|
5672
|
+
this.#automationArtefacts = (deps.automationArtefacts ?? []).map((artefact) => this.cloneAutomationArtefact(artefact));
|
|
5207
5673
|
this.#automationMatches = (deps.automationMatches ?? []).map((match) => ({
|
|
5208
5674
|
...match,
|
|
5209
5675
|
folders: match.folders.map((folder) => ({ ...folder })),
|
|
@@ -5213,11 +5679,14 @@ var GranolaApp = class {
|
|
|
5213
5679
|
this.#automationRules = (deps.automationRules ?? []).map((rule) => this.cloneAutomationRule(rule));
|
|
5214
5680
|
this.#state.exports.jobs = (deps.exportJobs ?? []).map((job) => cloneExportJob(job));
|
|
5215
5681
|
this.#state.automation = {
|
|
5682
|
+
artefactCount: this.#automationArtefacts.length,
|
|
5683
|
+
artefactsFile: config.automation?.artefactsFile ?? defaultAutomationArtefactsFilePath(),
|
|
5216
5684
|
lastRunAt: this.#automationActionRuns[0]?.finishedAt ?? this.#automationActionRuns[0]?.startedAt,
|
|
5217
5685
|
lastMatchedAt: this.#automationMatches[0]?.matchedAt,
|
|
5218
5686
|
loaded: true,
|
|
5219
5687
|
matchCount: this.#automationMatches.length,
|
|
5220
5688
|
matchesFile: defaultAutomationMatchesFilePath(),
|
|
5689
|
+
pendingArtefactCount: this.#automationArtefacts.filter((artefact) => artefact.status === "generated").length,
|
|
5221
5690
|
pendingRunCount: this.#automationActionRuns.filter((run) => run.status === "pending").length,
|
|
5222
5691
|
ruleCount: this.#automationRules.length,
|
|
5223
5692
|
rulesFile: config.automation?.rulesFile ?? defaultAutomationRulesFilePath(),
|
|
@@ -5339,11 +5808,28 @@ var GranolaApp = class {
|
|
|
5339
5808
|
cloneAutomationRun(run) {
|
|
5340
5809
|
return {
|
|
5341
5810
|
...run,
|
|
5811
|
+
artefactIds: run.artefactIds ? [...run.artefactIds] : void 0,
|
|
5342
5812
|
folders: run.folders.map((folder) => ({ ...folder })),
|
|
5343
5813
|
meta: run.meta ? structuredClone(run.meta) : void 0,
|
|
5344
5814
|
tags: [...run.tags]
|
|
5345
5815
|
};
|
|
5346
5816
|
}
|
|
5817
|
+
cloneAutomationArtefact(artefact) {
|
|
5818
|
+
return {
|
|
5819
|
+
...artefact,
|
|
5820
|
+
attempts: artefact.attempts.map((attempt) => ({ ...attempt })),
|
|
5821
|
+
history: artefact.history.map((entry) => ({ ...entry })),
|
|
5822
|
+
structured: {
|
|
5823
|
+
...artefact.structured,
|
|
5824
|
+
actionItems: artefact.structured.actionItems.map((item) => ({ ...item })),
|
|
5825
|
+
decisions: [...artefact.structured.decisions],
|
|
5826
|
+
followUps: [...artefact.structured.followUps],
|
|
5827
|
+
highlights: [...artefact.structured.highlights],
|
|
5828
|
+
metadata: artefact.structured.metadata ? structuredClone(artefact.structured.metadata) : void 0,
|
|
5829
|
+
sections: artefact.structured.sections.map((section) => ({ ...section }))
|
|
5830
|
+
}
|
|
5831
|
+
};
|
|
5832
|
+
}
|
|
5347
5833
|
refreshAutomationState() {
|
|
5348
5834
|
const latestMatch = this.#automationMatches.reduce((current, candidate) => !current || candidate.matchedAt.localeCompare(current.matchedAt) > 0 ? candidate : current, void 0);
|
|
5349
5835
|
const latestRun = this.#automationActionRuns.reduce((current, candidate) => {
|
|
@@ -5353,11 +5839,14 @@ var GranolaApp = class {
|
|
|
5353
5839
|
}, void 0);
|
|
5354
5840
|
this.#state.automation = {
|
|
5355
5841
|
...this.#state.automation,
|
|
5842
|
+
artefactCount: this.#automationArtefacts.length,
|
|
5843
|
+
artefactsFile: this.config.automation?.artefactsFile ?? defaultAutomationArtefactsFilePath(),
|
|
5356
5844
|
lastMatchedAt: latestMatch?.matchedAt ?? this.#state.automation.lastMatchedAt,
|
|
5357
5845
|
lastRunAt: latestRun?.finishedAt ?? latestRun?.startedAt ?? this.#state.automation.lastRunAt,
|
|
5358
5846
|
loaded: true,
|
|
5359
5847
|
matchCount: this.#automationMatches.length,
|
|
5360
5848
|
matchesFile: defaultAutomationMatchesFilePath(),
|
|
5849
|
+
pendingArtefactCount: this.#automationArtefacts.filter((artefact) => artefact.status === "generated").length,
|
|
5361
5850
|
pendingRunCount: this.#automationActionRuns.filter((run) => run.status === "pending").length,
|
|
5362
5851
|
ruleCount: this.#automationRules.length,
|
|
5363
5852
|
rulesFile: this.config.automation?.rulesFile ?? defaultAutomationRulesFilePath(),
|
|
@@ -5390,6 +5879,30 @@ var GranolaApp = class {
|
|
|
5390
5879
|
this.#automationActionRuns.sort((left, right) => (right.finishedAt ?? right.startedAt).localeCompare(left.finishedAt ?? left.startedAt));
|
|
5391
5880
|
this.refreshAutomationState();
|
|
5392
5881
|
}
|
|
5882
|
+
async writeAutomationArtefacts(artefacts) {
|
|
5883
|
+
this.#automationArtefacts = artefacts.map((artefact) => this.cloneAutomationArtefact(artefact)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
5884
|
+
if (this.deps.automationArtefactStore) await this.deps.automationArtefactStore.writeArtefacts(this.#automationArtefacts);
|
|
5885
|
+
this.refreshAutomationState();
|
|
5886
|
+
}
|
|
5887
|
+
buildAutomationArtefactHistoryEntry(action, note) {
|
|
5888
|
+
return {
|
|
5889
|
+
action,
|
|
5890
|
+
at: this.nowIso(),
|
|
5891
|
+
note: note?.trim() || void 0
|
|
5892
|
+
};
|
|
5893
|
+
}
|
|
5894
|
+
async readAutomationArtefactById(id) {
|
|
5895
|
+
return (this.deps.automationArtefactStore ? await this.deps.automationArtefactStore.readArtefact(id) : void 0) ?? this.#automationArtefacts.find((artefact) => artefact.id === id);
|
|
5896
|
+
}
|
|
5897
|
+
assertMutableAutomationArtefact(artefact) {
|
|
5898
|
+
if (artefact.status === "superseded") throw new Error(`automation artefact is superseded: ${artefact.id}`);
|
|
5899
|
+
}
|
|
5900
|
+
async replaceAutomationArtefact(nextArtefact) {
|
|
5901
|
+
const nextArtefacts = [this.cloneAutomationArtefact(nextArtefact), ...this.#automationArtefacts.filter((artefact) => artefact.id !== nextArtefact.id).map((artefact) => this.cloneAutomationArtefact(artefact))];
|
|
5902
|
+
await this.writeAutomationArtefacts(nextArtefacts);
|
|
5903
|
+
this.emitStateUpdate();
|
|
5904
|
+
return this.cloneAutomationArtefact(this.#automationArtefacts.find((artefact) => artefact.id === nextArtefact.id) ?? nextArtefact);
|
|
5905
|
+
}
|
|
5393
5906
|
createSyncRunId() {
|
|
5394
5907
|
return `sync-${this.nowIso().replaceAll(/[-:.]/g, "").replace("T", "").replace("Z", "")}`;
|
|
5395
5908
|
}
|
|
@@ -5630,6 +6143,28 @@ var GranolaApp = class {
|
|
|
5630
6143
|
if (!this.deps.syncEventStore) return { events: [] };
|
|
5631
6144
|
return { events: (await this.deps.syncEventStore.readEvents(options.limit)).map(cloneSyncEvent) };
|
|
5632
6145
|
}
|
|
6146
|
+
async listAutomationArtefacts(options = {}) {
|
|
6147
|
+
const limit = options.limit ?? 20;
|
|
6148
|
+
const artefacts = this.deps.automationArtefactStore ? await this.deps.automationArtefactStore.readArtefacts({
|
|
6149
|
+
kind: options.kind,
|
|
6150
|
+
limit,
|
|
6151
|
+
meetingId: options.meetingId,
|
|
6152
|
+
status: options.status
|
|
6153
|
+
}) : this.#automationArtefacts.filter((artefact) => {
|
|
6154
|
+
if (options.kind && artefact.kind !== options.kind) return false;
|
|
6155
|
+
if (options.meetingId && artefact.meetingId !== options.meetingId) return false;
|
|
6156
|
+
if (options.status && artefact.status !== options.status) return false;
|
|
6157
|
+
return true;
|
|
6158
|
+
}).slice(0, limit);
|
|
6159
|
+
this.setUiState({ view: "idle" });
|
|
6160
|
+
return { artefacts: artefacts.map((artefact) => this.cloneAutomationArtefact(artefact)) };
|
|
6161
|
+
}
|
|
6162
|
+
async getAutomationArtefact(id) {
|
|
6163
|
+
const artefact = await this.readAutomationArtefactById(id);
|
|
6164
|
+
if (!artefact) throw new Error(`automation artefact not found: ${id}`);
|
|
6165
|
+
this.setUiState({ view: "idle" });
|
|
6166
|
+
return this.cloneAutomationArtefact(artefact);
|
|
6167
|
+
}
|
|
5633
6168
|
async listAutomationRules() {
|
|
5634
6169
|
const rules = await this.loadAutomationRules({ forceRefresh: true });
|
|
5635
6170
|
this.setUiState({ view: "idle" });
|
|
@@ -5670,6 +6205,73 @@ var GranolaApp = class {
|
|
|
5670
6205
|
this.emitStateUpdate();
|
|
5671
6206
|
return this.cloneAutomationRun(resolved);
|
|
5672
6207
|
}
|
|
6208
|
+
async resolveAutomationArtefact(id, decision, options = {}) {
|
|
6209
|
+
const current = await this.readAutomationArtefactById(id);
|
|
6210
|
+
if (!current) throw new Error(`automation artefact not found: ${id}`);
|
|
6211
|
+
this.assertMutableAutomationArtefact(current);
|
|
6212
|
+
const nextArtefact = {
|
|
6213
|
+
...this.cloneAutomationArtefact(current),
|
|
6214
|
+
history: [...current.history.map((entry) => ({ ...entry })), this.buildAutomationArtefactHistoryEntry(decision === "approve" ? "approved" : "rejected", options.note)],
|
|
6215
|
+
status: decision === "approve" ? "approved" : "rejected",
|
|
6216
|
+
updatedAt: this.nowIso()
|
|
6217
|
+
};
|
|
6218
|
+
return await this.replaceAutomationArtefact(nextArtefact);
|
|
6219
|
+
}
|
|
6220
|
+
async updateAutomationArtefact(id, patch) {
|
|
6221
|
+
const current = await this.readAutomationArtefactById(id);
|
|
6222
|
+
if (!current) throw new Error(`automation artefact not found: ${id}`);
|
|
6223
|
+
this.assertMutableAutomationArtefact(current);
|
|
6224
|
+
const nextTitle = patch.title?.trim();
|
|
6225
|
+
const nextSummary = patch.summary?.trim();
|
|
6226
|
+
const nextMarkdown = patch.markdown?.trim();
|
|
6227
|
+
if (nextTitle === "" || nextMarkdown === "") throw new Error("automation artefact title and markdown must not be empty");
|
|
6228
|
+
const nextArtefact = {
|
|
6229
|
+
...this.cloneAutomationArtefact(current),
|
|
6230
|
+
history: [...current.history.map((entry) => ({ ...entry })), this.buildAutomationArtefactHistoryEntry("edited", patch.note)],
|
|
6231
|
+
structured: {
|
|
6232
|
+
...current.structured,
|
|
6233
|
+
markdown: nextMarkdown ?? current.structured.markdown,
|
|
6234
|
+
summary: nextSummary === void 0 ? current.structured.summary : nextSummary || void 0,
|
|
6235
|
+
title: nextTitle ?? current.structured.title
|
|
6236
|
+
},
|
|
6237
|
+
updatedAt: this.nowIso()
|
|
6238
|
+
};
|
|
6239
|
+
return await this.replaceAutomationArtefact(nextArtefact);
|
|
6240
|
+
}
|
|
6241
|
+
async rerunAutomationArtefact(id) {
|
|
6242
|
+
const current = await this.readAutomationArtefactById(id);
|
|
6243
|
+
if (!current) throw new Error(`automation artefact not found: ${id}`);
|
|
6244
|
+
const rule = (await this.loadAutomationRules({ forceRefresh: true })).find((candidate) => candidate.id === current.ruleId);
|
|
6245
|
+
if (!rule) throw new Error(`automation rule not found: ${current.ruleId}`);
|
|
6246
|
+
const action = enabledAutomationActions(rule).find((candidate) => candidate.id === current.actionId);
|
|
6247
|
+
if (!action || action.kind !== "agent" || !action.pipeline) throw new Error(`automation artefact is not rerunnable: ${id}`);
|
|
6248
|
+
const match = (this.deps.automationMatchStore ? (await this.deps.automationMatchStore.readMatches(0)).find((candidate) => candidate.id === current.matchId) : void 0) ?? this.#automationMatches.find((candidate) => candidate.id === current.matchId);
|
|
6249
|
+
if (!match) throw new Error(`automation match not found: ${current.matchId}`);
|
|
6250
|
+
const nextRun = await executeAutomationAction(this.cloneAutomationMatch(match), rule, action, {
|
|
6251
|
+
exportNotes: async (nextMatch, nextAction) => await this.runAutomationNotesAction(nextMatch, nextAction),
|
|
6252
|
+
exportTranscripts: async (nextMatch, nextAction) => await this.runAutomationTranscriptAction(nextMatch, nextAction),
|
|
6253
|
+
nowIso: () => this.nowIso(),
|
|
6254
|
+
runAgent: async (nextMatch, nextRule, nextAction, run) => await this.runAutomationAgent(nextMatch, nextRule, nextAction, run),
|
|
6255
|
+
runCommand: async (nextMatch, nextRule, nextAction) => await this.runAutomationCommand(nextMatch, nextRule, nextAction)
|
|
6256
|
+
}, {
|
|
6257
|
+
rerunOfId: current.runId,
|
|
6258
|
+
runId: `${current.runId}:rerun:${this.nowIso().replaceAll(/[-:.]/g, "").replace("T", "").replace("Z", "")}`
|
|
6259
|
+
});
|
|
6260
|
+
await this.appendAutomationRuns([nextRun]);
|
|
6261
|
+
const nextArtefactId = nextRun.artefactIds?.[0];
|
|
6262
|
+
const nextArtefact = nextArtefactId ? this.#automationArtefacts.find((artefact) => artefact.id === nextArtefactId) : void 0;
|
|
6263
|
+
if (!nextArtefact) throw new Error(`rerun did not produce an automation artefact: ${id}`);
|
|
6264
|
+
const updatedArtefacts = this.#automationArtefacts.map((artefact) => artefact.id === current.id ? {
|
|
6265
|
+
...artefact,
|
|
6266
|
+
history: [...artefact.history.map((entry) => ({ ...entry })), this.buildAutomationArtefactHistoryEntry("rerun", `Superseded by ${nextArtefact.id}`)],
|
|
6267
|
+
status: "superseded",
|
|
6268
|
+
supersededById: nextArtefact.id,
|
|
6269
|
+
updatedAt: nextArtefact.createdAt
|
|
6270
|
+
} : artefact);
|
|
6271
|
+
await this.writeAutomationArtefacts(updatedArtefacts);
|
|
6272
|
+
this.emitStateUpdate();
|
|
6273
|
+
return this.cloneAutomationArtefact(this.#automationArtefacts.find((artefact) => artefact.id === nextArtefact.id) ?? nextArtefact);
|
|
6274
|
+
}
|
|
5673
6275
|
async loginAuth(options = {}) {
|
|
5674
6276
|
const controller = this.requireAuthController();
|
|
5675
6277
|
try {
|
|
@@ -5856,30 +6458,125 @@ var GranolaApp = class {
|
|
|
5856
6458
|
child.stdin.end();
|
|
5857
6459
|
});
|
|
5858
6460
|
}
|
|
5859
|
-
async
|
|
5860
|
-
const bundle = match.eventKind === "meeting.removed" ? void 0 : await this.maybeReadMeetingBundleById(match.meetingId, { requireCache: false });
|
|
5861
|
-
const harness = resolveAgentHarness(this.deps.agentHarnessStore ? await this.deps.agentHarnessStore.readHarnesses() : [], {
|
|
5862
|
-
bundle,
|
|
5863
|
-
match
|
|
5864
|
-
}, action.harnessId);
|
|
6461
|
+
async buildAutomationAgentAttempt(match, rule, action, bundle, harness) {
|
|
5865
6462
|
const harnessCwd = harness?.cwd;
|
|
5866
6463
|
const promptFile = await readOptionalActionFile(action.promptFile, action.cwd ?? harnessCwd);
|
|
5867
6464
|
const harnessPromptFile = await readOptionalActionFile(harness?.promptFile, harnessCwd);
|
|
5868
6465
|
const systemPromptFile = await readOptionalActionFile(action.systemPromptFile, action.cwd ?? harnessCwd);
|
|
5869
6466
|
const harnessSystemPromptFile = await readOptionalActionFile(harness?.systemPromptFile, harnessCwd);
|
|
5870
|
-
|
|
6467
|
+
let instructions = combinePromptSections(harnessPromptFile, harness?.prompt, promptFile, action.prompt);
|
|
5871
6468
|
if (!instructions) throw new Error(`automation agent action ${action.id} is missing prompt instructions`);
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
model: action.model ?? harness?.model,
|
|
6469
|
+
if (action.pipeline) instructions = buildPipelineInstructions(action.pipeline.kind, instructions);
|
|
6470
|
+
return {
|
|
6471
|
+
harness,
|
|
5876
6472
|
prompt: buildAutomationAgentPrompt(match, rule, instructions, bundle),
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
6473
|
+
request: {
|
|
6474
|
+
cwd: action.cwd ?? harnessCwd,
|
|
6475
|
+
dryRun: action.dryRun,
|
|
6476
|
+
model: action.model ?? harness?.model,
|
|
6477
|
+
prompt: buildAutomationAgentPrompt(match, rule, instructions, bundle),
|
|
6478
|
+
provider: action.provider ?? harness?.provider,
|
|
6479
|
+
retries: action.retries,
|
|
6480
|
+
systemPrompt: combinePromptSections(harnessSystemPromptFile, harness?.systemPrompt, systemPromptFile, action.systemPrompt),
|
|
6481
|
+
timeoutMs: action.timeoutMs
|
|
6482
|
+
},
|
|
6483
|
+
systemPrompt: combinePromptSections(harnessSystemPromptFile, harness?.systemPrompt, systemPromptFile, action.systemPrompt)
|
|
5881
6484
|
};
|
|
5882
|
-
|
|
6485
|
+
}
|
|
6486
|
+
async runAutomationAgent(match, rule, action, run) {
|
|
6487
|
+
const bundle = match.eventKind === "meeting.removed" ? void 0 : await this.maybeReadMeetingBundleById(match.meetingId, { requireCache: false });
|
|
6488
|
+
const harnesses = this.deps.agentHarnessStore ? await this.deps.agentHarnessStore.readHarnesses() : [];
|
|
6489
|
+
const primaryHarness = resolveAgentHarness(harnesses, {
|
|
6490
|
+
bundle,
|
|
6491
|
+
match
|
|
6492
|
+
}, action.harnessId);
|
|
6493
|
+
const fallbackHarnessIds = [...action.fallbackHarnessIds ?? [], ...primaryHarness?.fallbackHarnessIds ?? []].filter((value, index, values) => Boolean(value) && values.indexOf(value) === index);
|
|
6494
|
+
const attempts = [await this.buildAutomationAgentAttempt(match, rule, action, bundle, primaryHarness), ...await Promise.all(fallbackHarnessIds.filter((harnessId) => harnessId !== primaryHarness?.id).map(async (harnessId) => {
|
|
6495
|
+
const fallbackHarness = resolveAgentHarness(harnesses, {
|
|
6496
|
+
bundle,
|
|
6497
|
+
match
|
|
6498
|
+
}, harnessId);
|
|
6499
|
+
return await this.buildAutomationAgentAttempt(match, rule, action, bundle, fallbackHarness);
|
|
6500
|
+
}))];
|
|
6501
|
+
const runner = this.deps.agentRunner ?? createDefaultAutomationAgentRunner(this.config);
|
|
6502
|
+
const attemptMeta = [];
|
|
6503
|
+
let lastError;
|
|
6504
|
+
for (const attempt of attempts) try {
|
|
6505
|
+
const result = await runner.run(attempt.request);
|
|
6506
|
+
attemptMeta.push({
|
|
6507
|
+
harnessId: attempt.harness?.id,
|
|
6508
|
+
model: result.model,
|
|
6509
|
+
provider: result.provider === "codex" || result.provider === "openai" || result.provider === "openrouter" ? result.provider : void 0
|
|
6510
|
+
});
|
|
6511
|
+
if (action.pipeline) {
|
|
6512
|
+
const parsed = parsePipelineOutput({
|
|
6513
|
+
kind: action.pipeline.kind,
|
|
6514
|
+
meetingTitle: match.title,
|
|
6515
|
+
rawOutput: result.output ?? ""
|
|
6516
|
+
});
|
|
6517
|
+
const createdAt = this.nowIso();
|
|
6518
|
+
const artefact = {
|
|
6519
|
+
actionId: action.id,
|
|
6520
|
+
actionName: automationActionName(action),
|
|
6521
|
+
attempts: attemptMeta.map((item) => ({ ...item })),
|
|
6522
|
+
createdAt,
|
|
6523
|
+
eventId: match.eventId,
|
|
6524
|
+
history: [{
|
|
6525
|
+
action: "generated",
|
|
6526
|
+
at: createdAt,
|
|
6527
|
+
note: run.rerunOfId ? `Rerun of ${run.rerunOfId}` : void 0
|
|
6528
|
+
}],
|
|
6529
|
+
id: buildAutomationArtefactId(run.id, action.pipeline.kind),
|
|
6530
|
+
kind: action.pipeline.kind,
|
|
6531
|
+
matchId: match.id,
|
|
6532
|
+
meetingId: match.meetingId,
|
|
6533
|
+
model: result.model,
|
|
6534
|
+
parseMode: parsed.parseMode,
|
|
6535
|
+
prompt: result.prompt,
|
|
6536
|
+
provider: result.provider === "codex" || result.provider === "openai" || result.provider === "openrouter" ? result.provider : "codex",
|
|
6537
|
+
rawOutput: result.output ?? "",
|
|
6538
|
+
rerunOfId: run.rerunOfId ? buildAutomationArtefactId(run.rerunOfId, action.pipeline.kind) : void 0,
|
|
6539
|
+
ruleId: rule.id,
|
|
6540
|
+
ruleName: rule.name,
|
|
6541
|
+
runId: run.id,
|
|
6542
|
+
status: "generated",
|
|
6543
|
+
structured: parsed.structured,
|
|
6544
|
+
updatedAt: createdAt
|
|
6545
|
+
};
|
|
6546
|
+
await this.writeAutomationArtefacts([artefact, ...this.#automationArtefacts]);
|
|
6547
|
+
return {
|
|
6548
|
+
artefactIds: [artefact.id],
|
|
6549
|
+
attempts: attemptMeta,
|
|
6550
|
+
command: result.command,
|
|
6551
|
+
dryRun: result.dryRun,
|
|
6552
|
+
model: result.model,
|
|
6553
|
+
output: parsed.structured.summary ?? parsed.structured.markdown,
|
|
6554
|
+
pipelineKind: action.pipeline.kind,
|
|
6555
|
+
prompt: result.prompt,
|
|
6556
|
+
provider: result.provider,
|
|
6557
|
+
systemPrompt: result.systemPrompt
|
|
6558
|
+
};
|
|
6559
|
+
}
|
|
6560
|
+
return {
|
|
6561
|
+
attempts: attemptMeta,
|
|
6562
|
+
command: result.command,
|
|
6563
|
+
dryRun: result.dryRun,
|
|
6564
|
+
model: result.model,
|
|
6565
|
+
output: result.output,
|
|
6566
|
+
prompt: result.prompt,
|
|
6567
|
+
provider: result.provider,
|
|
6568
|
+
systemPrompt: result.systemPrompt
|
|
6569
|
+
};
|
|
6570
|
+
} catch (error) {
|
|
6571
|
+
lastError = error;
|
|
6572
|
+
attemptMeta.push({
|
|
6573
|
+
error: error instanceof Error ? error.message : String(error),
|
|
6574
|
+
harnessId: attempt.harness?.id,
|
|
6575
|
+
model: attempt.request.model,
|
|
6576
|
+
provider: attempt.request.provider
|
|
6577
|
+
});
|
|
6578
|
+
}
|
|
6579
|
+
throw lastError instanceof Error ? lastError : new Error(typeof lastError === "string" ? lastError : lastError ? JSON.stringify(lastError) : "automation agent failed");
|
|
5883
6580
|
}
|
|
5884
6581
|
async runAutomationActions(rules, matches) {
|
|
5885
6582
|
const rulesById = new Map(rules.map((rule) => [rule.id, rule]));
|
|
@@ -5896,7 +6593,7 @@ var GranolaApp = class {
|
|
|
5896
6593
|
exportNotes: async (nextMatch, nextAction) => await this.runAutomationNotesAction(nextMatch, nextAction),
|
|
5897
6594
|
exportTranscripts: async (nextMatch, nextAction) => await this.runAutomationTranscriptAction(nextMatch, nextAction),
|
|
5898
6595
|
nowIso: () => this.nowIso(),
|
|
5899
|
-
runAgent: async (nextMatch, nextRule, nextAction) => await this.runAutomationAgent(nextMatch, nextRule, nextAction),
|
|
6596
|
+
runAgent: async (nextMatch, nextRule, nextAction, run) => await this.runAutomationAgent(nextMatch, nextRule, nextAction, run),
|
|
5900
6597
|
runCommand: async (nextMatch, nextRule, nextAction) => await this.runAutomationCommand(nextMatch, nextRule, nextAction)
|
|
5901
6598
|
}));
|
|
5902
6599
|
}
|
|
@@ -6317,6 +7014,8 @@ var GranolaApp = class {
|
|
|
6317
7014
|
};
|
|
6318
7015
|
async function createGranolaApp(config, options = {}) {
|
|
6319
7016
|
const auth = await inspectDefaultGranolaAuth(config);
|
|
7017
|
+
const automationArtefactStore = createDefaultAutomationArtefactStore(config.automation?.artefactsFile);
|
|
7018
|
+
const automationArtefacts = await automationArtefactStore.readArtefacts({ limit: 0 });
|
|
6320
7019
|
const automationMatchStore = createDefaultAutomationMatchStore();
|
|
6321
7020
|
const automationMatches = await automationMatchStore.readMatches(0);
|
|
6322
7021
|
const automationRunStore = createDefaultAutomationRunStore();
|
|
@@ -6339,6 +7038,8 @@ async function createGranolaApp(config, options = {}) {
|
|
|
6339
7038
|
agentRunner: createDefaultAutomationAgentRunner(config),
|
|
6340
7039
|
agentHarnessStore,
|
|
6341
7040
|
authController,
|
|
7041
|
+
automationArtefactStore,
|
|
7042
|
+
automationArtefacts,
|
|
6342
7043
|
automationMatches,
|
|
6343
7044
|
automationMatchStore,
|
|
6344
7045
|
automationRunStore,
|
|
@@ -6426,7 +7127,10 @@ async function loadConfig(options) {
|
|
|
6426
7127
|
const agentTimeoutValue = pickString(env.GRANOLA_AGENT_TIMEOUT) ?? pickString(configValues["agent-timeout"]) ?? pickString(configValues.agentTimeout) ?? "5m";
|
|
6427
7128
|
const timeoutValue = pickString(options.subcommandFlags.timeout) ?? pickString(env.TIMEOUT) ?? pickString(configValues.timeout) ?? "2m";
|
|
6428
7129
|
return {
|
|
6429
|
-
automation: {
|
|
7130
|
+
automation: {
|
|
7131
|
+
artefactsFile: pickString(env.GRANOLA_AUTOMATION_ARTEFACTS_FILE) ?? pickString(configValues["automation-artefacts-file"]) ?? pickString(configValues.automationArtefactsFile) ?? defaultGranolaToolkitPersistenceLayout().automationArtefactsFile,
|
|
7132
|
+
rulesFile: pickString(options.globalFlags.rules) ?? pickString(env.GRANOLA_AUTOMATION_RULES_FILE) ?? pickString(configValues["automation-rules-file"]) ?? pickString(configValues.automationRulesFile) ?? defaultGranolaToolkitPersistenceLayout().automationRulesFile
|
|
7133
|
+
},
|
|
6430
7134
|
agents: {
|
|
6431
7135
|
codexCommand: pickString(env.GRANOLA_CODEX_COMMAND) ?? pickString(configValues["codex-command"]) ?? pickString(configValues.codexCommand) ?? "codex",
|
|
6432
7136
|
defaultModel: pickString(env.GRANOLA_AGENT_MODEL) ?? pickString(configValues["agent-model"]) ?? pickString(configValues.agentModel),
|
|
@@ -6525,18 +7229,26 @@ function automationHelp() {
|
|
|
6525
7229
|
return `Granola automation
|
|
6526
7230
|
|
|
6527
7231
|
Usage:
|
|
6528
|
-
granola automation <rules|matches|runs|approve|reject> [options]
|
|
7232
|
+
granola automation <rules|matches|runs|artefacts|approve|reject|approve-artefact|reject-artefact|rerun> [options]
|
|
6529
7233
|
|
|
6530
7234
|
Subcommands:
|
|
6531
7235
|
rules List configured automation rules
|
|
6532
7236
|
matches Show recent rule matches from sync events
|
|
6533
7237
|
runs Show recent automation action runs
|
|
7238
|
+
artefacts Show generated note and enrichment artefacts
|
|
6534
7239
|
approve <id> Approve a pending ask-user action run
|
|
6535
7240
|
reject <id> Reject a pending ask-user action run
|
|
7241
|
+
approve-artefact <id>
|
|
7242
|
+
Approve a generated automation artefact
|
|
7243
|
+
reject-artefact <id>
|
|
7244
|
+
Reject a generated automation artefact
|
|
7245
|
+
rerun <id> Re-run the pipeline that produced an artefact
|
|
6536
7246
|
|
|
6537
7247
|
Options:
|
|
6538
7248
|
--format <value> text, json, yaml (default: text)
|
|
6539
7249
|
--limit <n> Number of matches to show (default: 20)
|
|
7250
|
+
--kind <value> notes or enrichment
|
|
7251
|
+
--meeting <id> Filter artefacts to one meeting id
|
|
6540
7252
|
--status <value> completed, failed, pending, skipped
|
|
6541
7253
|
--note <text> Note to store with approve/reject decisions
|
|
6542
7254
|
--rules <path> Path to automation rules JSON
|
|
@@ -6594,6 +7306,32 @@ function parseRunStatus(value) {
|
|
|
6594
7306
|
default: throw new Error("invalid automation status: expected completed, failed, pending, or skipped");
|
|
6595
7307
|
}
|
|
6596
7308
|
}
|
|
7309
|
+
function parseArtefactKind(value) {
|
|
7310
|
+
switch (value) {
|
|
7311
|
+
case void 0: return;
|
|
7312
|
+
case "enrichment":
|
|
7313
|
+
case "notes": return value;
|
|
7314
|
+
default: throw new Error("invalid automation artefact kind: expected notes or enrichment");
|
|
7315
|
+
}
|
|
7316
|
+
}
|
|
7317
|
+
function parseArtefactStatus(value) {
|
|
7318
|
+
switch (value) {
|
|
7319
|
+
case void 0: return;
|
|
7320
|
+
case "approved":
|
|
7321
|
+
case "generated":
|
|
7322
|
+
case "rejected":
|
|
7323
|
+
case "superseded": return value;
|
|
7324
|
+
default: throw new Error("invalid automation artefact status: expected approved, generated, rejected, or superseded");
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
function renderArtefacts(artefacts, format) {
|
|
7328
|
+
if (format === "json") return toJson({ artefacts });
|
|
7329
|
+
if (format === "yaml") return toYaml({ artefacts });
|
|
7330
|
+
if (artefacts.length === 0) return "No automation artefacts yet\n";
|
|
7331
|
+
return `${["UPDATED AT STATUS KIND TITLE", ...artefacts.map((artefact) => {
|
|
7332
|
+
return `${artefact.updatedAt.slice(0, 19).padEnd(21)} ${artefact.status.padEnd(12).slice(0, 12)} ${artefact.kind.padEnd(12).slice(0, 12)} ${[artefact.structured.title, artefact.structured.summary || artefact.id].filter(Boolean).join(" - ")}`;
|
|
7333
|
+
})].join("\n")}\n`;
|
|
7334
|
+
}
|
|
6597
7335
|
function renderRuns(runs, format) {
|
|
6598
7336
|
if (format === "json") return toJson({ runs });
|
|
6599
7337
|
if (format === "yaml") return toYaml({ runs });
|
|
@@ -6607,7 +7345,9 @@ const automationCommand = {
|
|
|
6607
7345
|
flags: {
|
|
6608
7346
|
format: { type: "string" },
|
|
6609
7347
|
help: { type: "boolean" },
|
|
7348
|
+
kind: { type: "string" },
|
|
6610
7349
|
limit: { type: "string" },
|
|
7350
|
+
meeting: { type: "string" },
|
|
6611
7351
|
note: { type: "string" },
|
|
6612
7352
|
status: { type: "string" },
|
|
6613
7353
|
timeout: { type: "string" }
|
|
@@ -6643,6 +7383,16 @@ const automationCommand = {
|
|
|
6643
7383
|
console.log(renderRuns(result.runs, format).trimEnd());
|
|
6644
7384
|
return 0;
|
|
6645
7385
|
}
|
|
7386
|
+
case "artefacts": {
|
|
7387
|
+
const result = await app.listAutomationArtefacts({
|
|
7388
|
+
kind: parseArtefactKind(commandFlags.kind),
|
|
7389
|
+
limit: parseLimit$4(commandFlags.limit),
|
|
7390
|
+
meetingId: typeof commandFlags.meeting === "string" ? commandFlags.meeting.trim() : void 0,
|
|
7391
|
+
status: parseArtefactStatus(commandFlags.status)
|
|
7392
|
+
});
|
|
7393
|
+
console.log(renderArtefacts(result.artefacts, format).trimEnd());
|
|
7394
|
+
return 0;
|
|
7395
|
+
}
|
|
6646
7396
|
case "approve":
|
|
6647
7397
|
case "reject": {
|
|
6648
7398
|
const id = commandArgs[1]?.trim();
|
|
@@ -6651,10 +7401,25 @@ const automationCommand = {
|
|
|
6651
7401
|
console.log(`${action === "approve" ? "Approved" : "Rejected"} ${run.actionName} for ${run.title} (${run.id})`);
|
|
6652
7402
|
return 0;
|
|
6653
7403
|
}
|
|
7404
|
+
case "approve-artefact":
|
|
7405
|
+
case "reject-artefact": {
|
|
7406
|
+
const id = commandArgs[1]?.trim();
|
|
7407
|
+
if (!id) throw new Error(`missing automation artefact id for ${action}`);
|
|
7408
|
+
const artefact = await app.resolveAutomationArtefact(id, action === "approve-artefact" ? "approve" : "reject", { note: typeof commandFlags.note === "string" ? commandFlags.note : void 0 });
|
|
7409
|
+
console.log(`${action === "approve-artefact" ? "Approved" : "Rejected"} artefact ${artefact.structured.title} (${artefact.id})`);
|
|
7410
|
+
return 0;
|
|
7411
|
+
}
|
|
7412
|
+
case "rerun": {
|
|
7413
|
+
const id = commandArgs[1]?.trim();
|
|
7414
|
+
if (!id) throw new Error("missing automation artefact id for rerun");
|
|
7415
|
+
const artefact = await app.rerunAutomationArtefact(id);
|
|
7416
|
+
console.log(`Re-ran ${artefact.kind} pipeline for ${artefact.structured.title} (${artefact.id})`);
|
|
7417
|
+
return 0;
|
|
7418
|
+
}
|
|
6654
7419
|
case void 0:
|
|
6655
7420
|
console.log(automationHelp());
|
|
6656
7421
|
return 1;
|
|
6657
|
-
default: throw new Error("invalid automation command: expected rules, matches, runs, approve, or
|
|
7422
|
+
default: throw new Error("invalid automation command: expected rules, matches, runs, artefacts, approve, reject, approve-artefact, reject-artefact, or rerun");
|
|
6658
7423
|
}
|
|
6659
7424
|
}
|
|
6660
7425
|
};
|
|
@@ -7004,8 +7769,8 @@ async function openExternalUrl(url, options = {}) {
|
|
|
7004
7769
|
}
|
|
7005
7770
|
//#endregion
|
|
7006
7771
|
//#region src/web/generated.ts
|
|
7007
|
-
const granolaWebClientCss = ":root {\n --bg: #f2ede2;\n --panel: rgba(255, 252, 247, 0.86);\n --panel-strong: #fffaf2;\n --line: rgba(36, 39, 44, 0.12);\n --ink: #1d242c;\n --muted: #5d6b77;\n --accent: #0d6a6d;\n --accent-soft: rgba(13, 106, 109, 0.12);\n --warm: #a34f2f;\n --ok: #246b4f;\n --error: #9d2c2c;\n --shadow: 0 24px 80px rgba(40, 32, 16, 0.12);\n --radius: 24px;\n --mono: \"SF Mono\", \"IBM Plex Mono\", \"Cascadia Code\", monospace;\n --serif: \"Iowan Old Style\", \"Palatino Linotype\", \"Book Antiqua\", Georgia, serif;\n --sans: \"Avenir Next\", \"Segoe UI\", sans-serif;\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n min-height: 100vh;\n font-family: var(--sans);\n color: var(--ink);\n background:\n radial-gradient(circle at top left, rgba(163, 79, 47, 0.18), transparent 32%),\n radial-gradient(circle at right 12%, rgba(13, 106, 109, 0.16), transparent 28%),\n linear-gradient(180deg, #f8f2e8 0%, var(--bg) 100%);\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\n#granola-web-root {\n min-height: 100vh;\n}\n\n.shell {\n display: grid;\n grid-template-columns: 320px minmax(0, 1fr);\n gap: 18px;\n min-height: 100vh;\n padding: 24px;\n}\n\n.pane {\n background: var(--panel);\n backdrop-filter: blur(18px);\n border: 1px solid var(--line);\n border-radius: var(--radius);\n box-shadow: var(--shadow);\n}\n\n.sidebar {\n display: grid;\n grid-template-rows: auto auto auto auto 1fr;\n overflow: hidden;\n}\n\n.hero,\n.toolbar,\n.detail-head,\n.folder-panel {\n padding: 22px 24px;\n border-bottom: 1px solid var(--line);\n}\n\n.hero h1 {\n margin: 0;\n font-family: var(--serif);\n font-size: clamp(2rem, 3vw, 2.8rem);\n font-weight: 600;\n letter-spacing: -0.04em;\n}\n\n.hero p,\n.toolbar p {\n margin: 8px 0 0;\n color: var(--muted);\n line-height: 1.5;\n}\n\n.search,\n.select,\n.field-input,\n.input {\n width: 100%;\n margin-top: 16px;\n padding: 12px 14px;\n border: 1px solid var(--line);\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.7);\n color: var(--ink);\n}\n\n.field-row {\n display: grid;\n gap: 10px;\n margin-top: 12px;\n}\n\n.field-row--inline {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.field-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.folder-panel {\n display: grid;\n gap: 14px;\n}\n\n.folder-panel__head h2 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.folder-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.folder-list,\n.jobs-list,\n.saved-filter-list {\n display: grid;\n gap: 10px;\n}\n\n.saved-filter-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n\n.folder-row,\n.meeting-row,\n.saved-filter-card__main {\n width: 100%;\n display: grid;\n gap: 4px;\n text-align: left;\n padding: 12px 14px;\n border: 1px solid transparent;\n border-radius: 16px;\n background: rgba(255, 255, 255, 0.72);\n color: inherit;\n cursor: pointer;\n transition:\n transform 140ms ease,\n border-color 140ms ease,\n background 140ms ease;\n}\n\n.meeting-row {\n margin: 0 0 10px;\n padding: 14px 16px;\n border-radius: 18px;\n}\n\n.saved-filter-card {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px;\n align-items: start;\n}\n\n.saved-filter-card__main {\n margin: 0;\n}\n\n.saved-filter-card__remove {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 12px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n cursor: pointer;\n font-weight: 700;\n}\n\n.folder-row:hover,\n.folder-row[data-selected=\"true\"],\n.saved-filter-card__main:hover {\n transform: translateY(-1px);\n border-color: rgba(163, 79, 47, 0.26);\n background: var(--panel-strong);\n}\n\n.meeting-row:hover,\n.meeting-row[data-selected=\"true\"] {\n transform: translateY(-1px);\n border-color: rgba(13, 106, 109, 0.25);\n background: var(--panel-strong);\n}\n\n.folder-row__title,\n.job-card__title,\n.saved-filter-card__title {\n font-weight: 700;\n}\n\n.meeting-row__title {\n font-weight: 600;\n}\n\n.folder-row__meta,\n.meeting-row__meta,\n.auth-card__meta,\n.job-card__meta,\n.folder-empty,\n.job-empty,\n.meeting-empty,\n.saved-filter-card__meta {\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.folder-empty--error,\n.meeting-empty--error,\n.auth-card__error {\n color: var(--error);\n}\n\n.meeting-list {\n padding: 14px;\n overflow: auto;\n}\n\n.detail {\n display: grid;\n grid-template-rows: auto auto 1fr;\n min-width: 0;\n}\n\n.detail-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 18px;\n}\n\n.detail-head h2 {\n margin: 0;\n font-family: var(--serif);\n font-size: clamp(1.8rem, 2.4vw, 2.4rem);\n font-weight: 600;\n}\n\n.state-badge {\n padding: 10px 14px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.92rem;\n font-weight: 700;\n}\n\n.state-badge[data-tone=\"busy\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.state-badge[data-tone=\"error\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.state-badge[data-tone=\"ok\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 14px;\n}\n\n.toolbar-actions,\n.auth-card__actions,\n.job-card__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n\n.toolbar-form {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px;\n width: min(440px, 100%);\n}\n\n.security-panel,\n.auth-panel,\n.jobs-panel {\n padding: 0 24px 18px;\n}\n\n.security-panel__head h3,\n.auth-panel__head h3,\n.jobs-panel__head h3 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.security-panel__head p,\n.auth-panel__head p,\n.jobs-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.security-panel__body,\n.auth-panel__body {\n display: grid;\n gap: 12px;\n margin-top: 14px;\n}\n\n.auth-card,\n.job-card {\n display: grid;\n gap: 12px;\n padding: 14px 16px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.72);\n}\n\n.job-card__head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n}\n\n.job-card__status {\n padding: 6px 10px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.82rem;\n font-weight: 700;\n}\n\n.job-card__status[data-status=\"running\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"failed\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"completed\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.workspace-tabs {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.workspace-tab,\n.button {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 14px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--ink);\n cursor: pointer;\n font-weight: 700;\n}\n\n.button {\n padding: 12px 16px;\n}\n\n.workspace-tab[data-selected=\"true\"],\n.button--primary {\n background: var(--ink);\n color: #fff;\n border-color: var(--ink);\n}\n\n.button--secondary {\n background: rgba(255, 255, 255, 0.72);\n}\n\n.button:disabled {\n cursor: not-allowed;\n opacity: 0.56;\n}\n\n.workspace-hint {\n margin-left: auto;\n color: var(--muted);\n font-size: 0.86rem;\n}\n\n.status-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 14px;\n}\n\n.status-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.detail-chip {\n padding: 10px 12px;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n color: var(--muted);\n font-size: 0.88rem;\n}\n\n.detail-body {\n padding: 0 24px 24px;\n overflow: auto;\n}\n\n.workspace-grid {\n display: grid;\n grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);\n gap: 18px;\n}\n\n.detail-section {\n margin-bottom: 20px;\n padding: 20px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n border-radius: 20px;\n}\n\n.detail-section h2 {\n margin: 0 0 14px;\n font-size: 1rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--mono);\n line-height: 1.55;\n}\n\n.empty {\n margin: 24px;\n padding: 24px;\n border-radius: 20px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n}\n\n@media (max-width: 1024px) {\n .shell,\n .workspace-grid {\n grid-template-columns: 1fr;\n }\n}\n/*$vite$:1*/";
|
|
7008
|
-
const granolaWebClientJs = "//#region node_modules/solid-js/dist/solid.js\nvar sharedConfig = {\n context: void 0,\n registry: void 0,\n effects: void 0,\n done: false,\n getContextId() {\n return getContextId(this.context.count);\n },\n getNextContextId() {\n return getContextId(this.context.count++);\n }\n};\nfunction getContextId(count) {\n const num = String(count), len = num.length - 1;\n return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : \"\") + num;\n}\nfunction setHydrateContext(context) {\n sharedConfig.context = context;\n}\nfunction nextHydrateContext() {\n return {\n ...sharedConfig.context,\n id: sharedConfig.getNextContextId(),\n count: 0\n };\n}\nvar equalFn = (a, b) => a === b;\nvar $PROXY = Symbol(\"solid-proxy\");\nvar $TRACK = Symbol(\"solid-track\");\nvar signalOptions = { equals: equalFn };\nvar ERROR = null;\nvar runEffects = runQueue;\nvar STALE = 1;\nvar PENDING = 2;\nvar UNOWNED = {\n owned: null,\n cleanups: null,\n context: null,\n owner: null\n};\nvar Owner = null;\nvar Transition = null;\nvar Scheduler = null;\nvar ExternalSourceConfig = null;\nvar Listener = null;\nvar Updates = null;\nvar Effects = null;\nvar ExecCount = 0;\nfunction createRoot(fn, detachedOwner) {\n const listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === void 0 ? owner : detachedOwner, root = unowned ? UNOWNED : {\n owned: null,\n cleanups: null,\n context: current ? current.context : null,\n owner: current\n }, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));\n Owner = root;\n Listener = null;\n try {\n return runUpdates(updateFn, true);\n } finally {\n Listener = listener;\n Owner = owner;\n }\n}\nfunction createSignal(value, options) {\n options = options ? Object.assign({}, signalOptions, options) : signalOptions;\n const s = {\n value,\n observers: null,\n observerSlots: null,\n comparator: options.equals || void 0\n };\n const setter = (value) => {\n if (typeof value === \"function\") if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);\n else value = value(s.value);\n return writeSignal(s, value);\n };\n return [readSignal.bind(s), setter];\n}\nfunction createRenderEffect(fn, value, options) {\n const c = createComputation(fn, value, false, STALE);\n if (Scheduler && Transition && Transition.running) Updates.push(c);\n else updateComputation(c);\n}\nfunction createEffect(fn, value, options) {\n runEffects = runUserEffects;\n const c = createComputation(fn, value, false, STALE), s = SuspenseContext && useContext(SuspenseContext);\n if (s) c.suspense = s;\n if (!options || !options.render) c.user = true;\n Effects ? Effects.push(c) : updateComputation(c);\n}\nfunction createMemo(fn, value, options) {\n options = options ? Object.assign({}, signalOptions, options) : signalOptions;\n const c = createComputation(fn, value, true, 0);\n c.observers = null;\n c.observerSlots = null;\n c.comparator = options.equals || void 0;\n if (Scheduler && Transition && Transition.running) {\n c.tState = STALE;\n Updates.push(c);\n } else updateComputation(c);\n return readSignal.bind(c);\n}\nfunction batch(fn) {\n return runUpdates(fn, false);\n}\nfunction untrack(fn) {\n if (!ExternalSourceConfig && Listener === null) return fn();\n const listener = Listener;\n Listener = null;\n try {\n if (ExternalSourceConfig) return ExternalSourceConfig.untrack(fn);\n return fn();\n } finally {\n Listener = listener;\n }\n}\nfunction onMount(fn) {\n createEffect(() => untrack(fn));\n}\nfunction onCleanup(fn) {\n if (Owner === null);\n else if (Owner.cleanups === null) Owner.cleanups = [fn];\n else Owner.cleanups.push(fn);\n return fn;\n}\nfunction getListener() {\n return Listener;\n}\nfunction startTransition(fn) {\n if (Transition && Transition.running) {\n fn();\n return Transition.done;\n }\n const l = Listener;\n const o = Owner;\n return Promise.resolve().then(() => {\n Listener = l;\n Owner = o;\n let t;\n if (Scheduler || SuspenseContext) {\n t = Transition || (Transition = {\n sources: /* @__PURE__ */ new Set(),\n effects: [],\n promises: /* @__PURE__ */ new Set(),\n disposed: /* @__PURE__ */ new Set(),\n queue: /* @__PURE__ */ new Set(),\n running: true\n });\n t.done || (t.done = new Promise((res) => t.resolve = res));\n t.running = true;\n }\n runUpdates(fn, false);\n Listener = Owner = null;\n return t ? t.done : void 0;\n });\n}\nvar [transPending, setTransPending] = /* @__PURE__ */ createSignal(false);\nfunction useContext(context) {\n let value;\n return Owner && Owner.context && (value = Owner.context[context.id]) !== void 0 ? value : context.defaultValue;\n}\nvar SuspenseContext;\nfunction readSignal() {\n const runningTransition = Transition && Transition.running;\n if (this.sources && (runningTransition ? this.tState : this.state)) if ((runningTransition ? this.tState : this.state) === STALE) updateComputation(this);\n else {\n const updates = Updates;\n Updates = null;\n runUpdates(() => lookUpstream(this), false);\n Updates = updates;\n }\n if (Listener) {\n const sSlot = this.observers ? this.observers.length : 0;\n if (!Listener.sources) {\n Listener.sources = [this];\n Listener.sourceSlots = [sSlot];\n } else {\n Listener.sources.push(this);\n Listener.sourceSlots.push(sSlot);\n }\n if (!this.observers) {\n this.observers = [Listener];\n this.observerSlots = [Listener.sources.length - 1];\n } else {\n this.observers.push(Listener);\n this.observerSlots.push(Listener.sources.length - 1);\n }\n }\n if (runningTransition && Transition.sources.has(this)) return this.tValue;\n return this.value;\n}\nfunction writeSignal(node, value, isComp) {\n let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;\n if (!node.comparator || !node.comparator(current, value)) {\n if (Transition) {\n const TransitionRunning = Transition.running;\n if (TransitionRunning || !isComp && Transition.sources.has(node)) {\n Transition.sources.add(node);\n node.tValue = value;\n }\n if (!TransitionRunning) node.value = value;\n } else node.value = value;\n if (node.observers && node.observers.length) runUpdates(() => {\n for (let i = 0; i < node.observers.length; i += 1) {\n const o = node.observers[i];\n const TransitionRunning = Transition && Transition.running;\n if (TransitionRunning && Transition.disposed.has(o)) continue;\n if (TransitionRunning ? !o.tState : !o.state) {\n if (o.pure) Updates.push(o);\n else Effects.push(o);\n if (o.observers) markDownstream(o);\n }\n if (!TransitionRunning) o.state = STALE;\n else o.tState = STALE;\n }\n if (Updates.length > 1e6) {\n Updates = [];\n throw new Error();\n }\n }, false);\n }\n return value;\n}\nfunction updateComputation(node) {\n if (!node.fn) return;\n cleanNode(node);\n const time = ExecCount;\n runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time);\n if (Transition && !Transition.running && Transition.sources.has(node)) queueMicrotask(() => {\n runUpdates(() => {\n Transition && (Transition.running = true);\n Listener = Owner = node;\n runComputation(node, node.tValue, time);\n Listener = Owner = null;\n }, false);\n });\n}\nfunction runComputation(node, value, time) {\n let nextValue;\n const owner = Owner, listener = Listener;\n Listener = Owner = node;\n try {\n nextValue = node.fn(value);\n } catch (err) {\n if (node.pure) if (Transition && Transition.running) {\n node.tState = STALE;\n node.tOwned && node.tOwned.forEach(cleanNode);\n node.tOwned = void 0;\n } else {\n node.state = STALE;\n node.owned && node.owned.forEach(cleanNode);\n node.owned = null;\n }\n node.updatedAt = time + 1;\n return handleError(err);\n } finally {\n Listener = listener;\n Owner = owner;\n }\n if (!node.updatedAt || node.updatedAt <= time) {\n if (node.updatedAt != null && \"observers\" in node) writeSignal(node, nextValue, true);\n else if (Transition && Transition.running && node.pure) {\n if (!Transition.sources.has(node)) node.value = nextValue;\n Transition.sources.add(node);\n node.tValue = nextValue;\n } else node.value = nextValue;\n node.updatedAt = time;\n }\n}\nfunction createComputation(fn, init, pure, state = STALE, options) {\n const c = {\n fn,\n state,\n updatedAt: null,\n owned: null,\n sources: null,\n sourceSlots: null,\n cleanups: null,\n value: init,\n owner: Owner,\n context: Owner ? Owner.context : null,\n pure\n };\n if (Transition && Transition.running) {\n c.state = 0;\n c.tState = state;\n }\n if (Owner === null);\n else if (Owner !== UNOWNED) if (Transition && Transition.running && Owner.pure) if (!Owner.tOwned) Owner.tOwned = [c];\n else Owner.tOwned.push(c);\n else if (!Owner.owned) Owner.owned = [c];\n else Owner.owned.push(c);\n if (ExternalSourceConfig && c.fn) {\n const sourceFn = c.fn;\n const [track, trigger] = createSignal(void 0, { equals: false });\n const ordinary = ExternalSourceConfig.factory(sourceFn, trigger);\n onCleanup(() => ordinary.dispose());\n let inTransition;\n const triggerInTransition = () => startTransition(trigger).then(() => {\n if (inTransition) {\n inTransition.dispose();\n inTransition = void 0;\n }\n });\n c.fn = (x) => {\n track();\n if (Transition && Transition.running) {\n if (!inTransition) inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition);\n return inTransition.track(x);\n }\n return ordinary.track(x);\n };\n }\n return c;\n}\nfunction runTop(node) {\n const runningTransition = Transition && Transition.running;\n if ((runningTransition ? node.tState : node.state) === 0) return;\n if ((runningTransition ? node.tState : node.state) === PENDING) return lookUpstream(node);\n if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);\n const ancestors = [node];\n while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {\n if (runningTransition && Transition.disposed.has(node)) return;\n if (runningTransition ? node.tState : node.state) ancestors.push(node);\n }\n for (let i = ancestors.length - 1; i >= 0; i--) {\n node = ancestors[i];\n if (runningTransition) {\n let top = node, prev = ancestors[i + 1];\n while ((top = top.owner) && top !== prev) if (Transition.disposed.has(top)) return;\n }\n if ((runningTransition ? node.tState : node.state) === STALE) updateComputation(node);\n else if ((runningTransition ? node.tState : node.state) === PENDING) {\n const updates = Updates;\n Updates = null;\n runUpdates(() => lookUpstream(node, ancestors[0]), false);\n Updates = updates;\n }\n }\n}\nfunction runUpdates(fn, init) {\n if (Updates) return fn();\n let wait = false;\n if (!init) Updates = [];\n if (Effects) wait = true;\n else Effects = [];\n ExecCount++;\n try {\n const res = fn();\n completeUpdates(wait);\n return res;\n } catch (err) {\n if (!wait) Effects = null;\n Updates = null;\n handleError(err);\n }\n}\nfunction completeUpdates(wait) {\n if (Updates) {\n if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);\n else runQueue(Updates);\n Updates = null;\n }\n if (wait) return;\n let res;\n if (Transition) {\n if (!Transition.promises.size && !Transition.queue.size) {\n const sources = Transition.sources;\n const disposed = Transition.disposed;\n Effects.push.apply(Effects, Transition.effects);\n res = Transition.resolve;\n for (const e of Effects) {\n \"tState\" in e && (e.state = e.tState);\n delete e.tState;\n }\n Transition = null;\n runUpdates(() => {\n for (const d of disposed) cleanNode(d);\n for (const v of sources) {\n v.value = v.tValue;\n if (v.owned) for (let i = 0, len = v.owned.length; i < len; i++) cleanNode(v.owned[i]);\n if (v.tOwned) v.owned = v.tOwned;\n delete v.tValue;\n delete v.tOwned;\n v.tState = 0;\n }\n setTransPending(false);\n }, false);\n } else if (Transition.running) {\n Transition.running = false;\n Transition.effects.push.apply(Transition.effects, Effects);\n Effects = null;\n setTransPending(true);\n return;\n }\n }\n const e = Effects;\n Effects = null;\n if (e.length) runUpdates(() => runEffects(e), false);\n if (res) res();\n}\nfunction runQueue(queue) {\n for (let i = 0; i < queue.length; i++) runTop(queue[i]);\n}\nfunction scheduleQueue(queue) {\n for (let i = 0; i < queue.length; i++) {\n const item = queue[i];\n const tasks = Transition.queue;\n if (!tasks.has(item)) {\n tasks.add(item);\n Scheduler(() => {\n tasks.delete(item);\n runUpdates(() => {\n Transition.running = true;\n runTop(item);\n }, false);\n Transition && (Transition.running = false);\n });\n }\n }\n}\nfunction runUserEffects(queue) {\n let i, userLength = 0;\n for (i = 0; i < queue.length; i++) {\n const e = queue[i];\n if (!e.user) runTop(e);\n else queue[userLength++] = e;\n }\n if (sharedConfig.context) {\n if (sharedConfig.count) {\n sharedConfig.effects || (sharedConfig.effects = []);\n sharedConfig.effects.push(...queue.slice(0, userLength));\n return;\n }\n setHydrateContext();\n }\n if (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count)) {\n queue = [...sharedConfig.effects, ...queue];\n userLength += sharedConfig.effects.length;\n delete sharedConfig.effects;\n }\n for (i = 0; i < userLength; i++) runTop(queue[i]);\n}\nfunction lookUpstream(node, ignore) {\n const runningTransition = Transition && Transition.running;\n if (runningTransition) node.tState = 0;\n else node.state = 0;\n for (let i = 0; i < node.sources.length; i += 1) {\n const source = node.sources[i];\n if (source.sources) {\n const state = runningTransition ? source.tState : source.state;\n if (state === STALE) {\n if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source);\n } else if (state === PENDING) lookUpstream(source, ignore);\n }\n }\n}\nfunction markDownstream(node) {\n const runningTransition = Transition && Transition.running;\n for (let i = 0; i < node.observers.length; i += 1) {\n const o = node.observers[i];\n if (runningTransition ? !o.tState : !o.state) {\n if (runningTransition) o.tState = PENDING;\n else o.state = PENDING;\n if (o.pure) Updates.push(o);\n else Effects.push(o);\n o.observers && markDownstream(o);\n }\n }\n}\nfunction cleanNode(node) {\n let i;\n if (node.sources) while (node.sources.length) {\n const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;\n if (obs && obs.length) {\n const n = obs.pop(), s = source.observerSlots.pop();\n if (index < obs.length) {\n n.sourceSlots[s] = index;\n obs[index] = n;\n source.observerSlots[index] = s;\n }\n }\n }\n if (node.tOwned) {\n for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]);\n delete node.tOwned;\n }\n if (Transition && Transition.running && node.pure) reset(node, true);\n else if (node.owned) {\n for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);\n node.owned = null;\n }\n if (node.cleanups) {\n for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();\n node.cleanups = null;\n }\n if (Transition && Transition.running) node.tState = 0;\n else node.state = 0;\n}\nfunction reset(node, top) {\n if (!top) {\n node.tState = 0;\n Transition.disposed.add(node);\n }\n if (node.owned) for (let i = 0; i < node.owned.length; i++) reset(node.owned[i]);\n}\nfunction castError(err) {\n if (err instanceof Error) return err;\n return new Error(typeof err === \"string\" ? err : \"Unknown error\", { cause: err });\n}\nfunction runErrors(err, fns, owner) {\n try {\n for (const f of fns) f(err);\n } catch (e) {\n handleError(e, owner && owner.owner || null);\n }\n}\nfunction handleError(err, owner = Owner) {\n const fns = ERROR && owner && owner.context && owner.context[ERROR];\n const error = castError(err);\n if (!fns) throw error;\n if (Effects) Effects.push({\n fn() {\n runErrors(error, fns, owner);\n },\n state: STALE\n });\n else runErrors(error, fns, owner);\n}\nvar FALLBACK = Symbol(\"fallback\");\nfunction dispose(d) {\n for (let i = 0; i < d.length; i++) d[i]();\n}\nfunction mapArray(list, mapFn, options = {}) {\n let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null;\n onCleanup(() => dispose(disposers));\n return () => {\n let newItems = list() || [], newLen = newItems.length, i, j;\n newItems[$TRACK];\n return untrack(() => {\n let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;\n if (newLen === 0) {\n if (len !== 0) {\n dispose(disposers);\n disposers = [];\n items = [];\n mapped = [];\n len = 0;\n indexes && (indexes = []);\n }\n if (options.fallback) {\n items = [FALLBACK];\n mapped[0] = createRoot((disposer) => {\n disposers[0] = disposer;\n return options.fallback();\n });\n len = 1;\n }\n } else if (len === 0) {\n mapped = new Array(newLen);\n for (j = 0; j < newLen; j++) {\n items[j] = newItems[j];\n mapped[j] = createRoot(mapper);\n }\n len = newLen;\n } else {\n temp = new Array(newLen);\n tempdisposers = new Array(newLen);\n indexes && (tempIndexes = new Array(newLen));\n for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++);\n for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {\n temp[newEnd] = mapped[end];\n tempdisposers[newEnd] = disposers[end];\n indexes && (tempIndexes[newEnd] = indexes[end]);\n }\n newIndices = /* @__PURE__ */ new Map();\n newIndicesNext = new Array(newEnd + 1);\n for (j = newEnd; j >= start; j--) {\n item = newItems[j];\n i = newIndices.get(item);\n newIndicesNext[j] = i === void 0 ? -1 : i;\n newIndices.set(item, j);\n }\n for (i = start; i <= end; i++) {\n item = items[i];\n j = newIndices.get(item);\n if (j !== void 0 && j !== -1) {\n temp[j] = mapped[i];\n tempdisposers[j] = disposers[i];\n indexes && (tempIndexes[j] = indexes[i]);\n j = newIndicesNext[j];\n newIndices.set(item, j);\n } else disposers[i]();\n }\n for (j = start; j < newLen; j++) if (j in temp) {\n mapped[j] = temp[j];\n disposers[j] = tempdisposers[j];\n if (indexes) {\n indexes[j] = tempIndexes[j];\n indexes[j](j);\n }\n } else mapped[j] = createRoot(mapper);\n mapped = mapped.slice(0, len = newLen);\n items = newItems.slice(0);\n }\n return mapped;\n });\n function mapper(disposer) {\n disposers[j] = disposer;\n if (indexes) {\n const [s, set] = createSignal(j);\n indexes[j] = set;\n return mapFn(newItems[j], s);\n }\n return mapFn(newItems[j]);\n }\n };\n}\nvar hydrationEnabled = false;\nfunction createComponent(Comp, props) {\n if (hydrationEnabled) {\n if (sharedConfig.context) {\n const c = sharedConfig.context;\n setHydrateContext(nextHydrateContext());\n const r = untrack(() => Comp(props || {}));\n setHydrateContext(c);\n return r;\n }\n }\n return untrack(() => Comp(props || {}));\n}\nvar narrowedError = (name) => `Stale read from <${name}>.`;\nfunction For(props) {\n const fallback = \"fallback\" in props && { fallback: () => props.fallback };\n return createMemo(mapArray(() => props.each, props.children, fallback || void 0));\n}\nfunction Show(props) {\n const keyed = props.keyed;\n const conditionValue = createMemo(() => props.when, void 0, void 0);\n const condition = keyed ? conditionValue : createMemo(conditionValue, void 0, { equals: (a, b) => !a === !b });\n return createMemo(() => {\n const c = condition();\n if (c) {\n const child = props.children;\n return typeof child === \"function\" && child.length > 0 ? untrack(() => child(keyed ? c : () => {\n if (!untrack(condition)) throw narrowedError(\"Show\");\n return conditionValue();\n })) : child;\n }\n return props.fallback;\n }, void 0, void 0);\n}\n//#endregion\n//#region node_modules/solid-js/web/dist/web.js\nvar memo = (fn) => createMemo(() => fn());\nfunction reconcileArrays(parentNode, a, b) {\n let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null;\n while (aStart < aEnd || bStart < bEnd) {\n if (a[aStart] === b[bStart]) {\n aStart++;\n bStart++;\n continue;\n }\n while (a[aEnd - 1] === b[bEnd - 1]) {\n aEnd--;\n bEnd--;\n }\n if (aEnd === aStart) {\n const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;\n while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node);\n } else if (bEnd === bStart) while (aStart < aEnd) {\n if (!map || !map.has(a[aStart])) a[aStart].remove();\n aStart++;\n }\n else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {\n const node = a[--aEnd].nextSibling;\n parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling);\n parentNode.insertBefore(b[--bEnd], node);\n a[aEnd] = b[bEnd];\n } else {\n if (!map) {\n map = /* @__PURE__ */ new Map();\n let i = bStart;\n while (i < bEnd) map.set(b[i], i++);\n }\n const index = map.get(a[aStart]);\n if (index != null) if (bStart < index && index < bEnd) {\n let i = aStart, sequence = 1, t;\n while (++i < aEnd && i < bEnd) {\n if ((t = map.get(a[i])) == null || t !== index + sequence) break;\n sequence++;\n }\n if (sequence > index - bStart) {\n const node = a[aStart];\n while (bStart < index) parentNode.insertBefore(b[bStart++], node);\n } else parentNode.replaceChild(b[bStart++], a[aStart++]);\n } else aStart++;\n else a[aStart++].remove();\n }\n }\n}\nvar $$EVENTS = \"_$DX_DELEGATE\";\nfunction render(code, element, init, options = {}) {\n let disposer;\n createRoot((dispose) => {\n disposer = dispose;\n element === document ? code() : insert(element, code(), element.firstChild ? null : void 0, init);\n }, options.owner);\n return () => {\n disposer();\n element.textContent = \"\";\n };\n}\nfunction template(html, isImportNode, isSVG, isMathML) {\n let node;\n const create = () => {\n const t = isMathML ? document.createElementNS(\"http://www.w3.org/1998/Math/MathML\", \"template\") : document.createElement(\"template\");\n t.innerHTML = html;\n return isSVG ? t.content.firstChild.firstChild : isMathML ? t.firstChild : t.content.firstChild;\n };\n const fn = isImportNode ? () => untrack(() => document.importNode(node || (node = create()), true)) : () => (node || (node = create())).cloneNode(true);\n fn.cloneNode = fn;\n return fn;\n}\nfunction delegateEvents(eventNames, document = window.document) {\n const e = document[$$EVENTS] || (document[$$EVENTS] = /* @__PURE__ */ new Set());\n for (let i = 0, l = eventNames.length; i < l; i++) {\n const name = eventNames[i];\n if (!e.has(name)) {\n e.add(name);\n document.addEventListener(name, eventHandler);\n }\n }\n}\nfunction setAttribute(node, name, value) {\n if (isHydrating(node)) return;\n if (value == null) node.removeAttribute(name);\n else node.setAttribute(name, value);\n}\nfunction addEventListener(node, name, handler, delegate) {\n if (delegate) if (Array.isArray(handler)) {\n node[`$$${name}`] = handler[0];\n node[`$$${name}Data`] = handler[1];\n } else node[`$$${name}`] = handler;\n else if (Array.isArray(handler)) {\n const handlerFn = handler[0];\n node.addEventListener(name, handler[0] = (e) => handlerFn.call(node, handler[1], e));\n } else node.addEventListener(name, handler, typeof handler !== \"function\" && handler);\n}\nfunction insert(parent, accessor, marker, initial) {\n if (marker !== void 0 && !initial) initial = [];\n if (typeof accessor !== \"function\") return insertExpression(parent, accessor, initial, marker);\n createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);\n}\nfunction isHydrating(node) {\n return !!sharedConfig.context && !sharedConfig.done && (!node || node.isConnected);\n}\nfunction eventHandler(e) {\n if (sharedConfig.registry && sharedConfig.events) {\n if (sharedConfig.events.find(([el, ev]) => ev === e)) return;\n }\n let node = e.target;\n const key = `$$${e.type}`;\n const oriTarget = e.target;\n const oriCurrentTarget = e.currentTarget;\n const retarget = (value) => Object.defineProperty(e, \"target\", {\n configurable: true,\n value\n });\n const handleNode = () => {\n const handler = node[key];\n if (handler && !node.disabled) {\n const data = node[`${key}Data`];\n data !== void 0 ? handler.call(node, data, e) : handler.call(node, e);\n if (e.cancelBubble) return;\n }\n node.host && typeof node.host !== \"string\" && !node.host._$host && node.contains(e.target) && retarget(node.host);\n return true;\n };\n const walkUpTree = () => {\n while (handleNode() && (node = node._$host || node.parentNode || node.host));\n };\n Object.defineProperty(e, \"currentTarget\", {\n configurable: true,\n get() {\n return node || document;\n }\n });\n if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;\n if (e.composedPath) {\n const path = e.composedPath();\n retarget(path[0]);\n for (let i = 0; i < path.length - 2; i++) {\n node = path[i];\n if (!handleNode()) break;\n if (node._$host) {\n node = node._$host;\n walkUpTree();\n break;\n }\n if (node.parentNode === oriCurrentTarget) break;\n }\n } else walkUpTree();\n retarget(oriTarget);\n}\nfunction insertExpression(parent, value, current, marker, unwrapArray) {\n const hydrating = isHydrating(parent);\n if (hydrating) {\n !current && (current = [...parent.childNodes]);\n let cleaned = [];\n for (let i = 0; i < current.length; i++) {\n const node = current[i];\n if (node.nodeType === 8 && node.data.slice(0, 2) === \"!$\") node.remove();\n else cleaned.push(node);\n }\n current = cleaned;\n }\n while (typeof current === \"function\") current = current();\n if (value === current) return current;\n const t = typeof value, multi = marker !== void 0;\n parent = multi && current[0] && current[0].parentNode || parent;\n if (t === \"string\" || t === \"number\") {\n if (hydrating) return current;\n if (t === \"number\") {\n value = value.toString();\n if (value === current) return current;\n }\n if (multi) {\n let node = current[0];\n if (node && node.nodeType === 3) node.data !== value && (node.data = value);\n else node = document.createTextNode(value);\n current = cleanChildren(parent, current, marker, node);\n } else if (current !== \"\" && typeof current === \"string\") current = parent.firstChild.data = value;\n else current = parent.textContent = value;\n } else if (value == null || t === \"boolean\") {\n if (hydrating) return current;\n current = cleanChildren(parent, current, marker);\n } else if (t === \"function\") {\n createRenderEffect(() => {\n let v = value();\n while (typeof v === \"function\") v = v();\n current = insertExpression(parent, v, current, marker);\n });\n return () => current;\n } else if (Array.isArray(value)) {\n const array = [];\n const currentArray = current && Array.isArray(current);\n if (normalizeIncomingArray(array, value, current, unwrapArray)) {\n createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));\n return () => current;\n }\n if (hydrating) {\n if (!array.length) return current;\n if (marker === void 0) return current = [...parent.childNodes];\n let node = array[0];\n if (node.parentNode !== parent) return current;\n const nodes = [node];\n while ((node = node.nextSibling) !== marker) nodes.push(node);\n return current = nodes;\n }\n if (array.length === 0) {\n current = cleanChildren(parent, current, marker);\n if (multi) return current;\n } else if (currentArray) if (current.length === 0) appendNodes(parent, array, marker);\n else reconcileArrays(parent, current, array);\n else {\n current && cleanChildren(parent);\n appendNodes(parent, array);\n }\n current = array;\n } else if (value.nodeType) {\n if (hydrating && value.parentNode) return current = multi ? [value] : value;\n if (Array.isArray(current)) {\n if (multi) return current = cleanChildren(parent, current, marker, value);\n cleanChildren(parent, current, null, value);\n } else if (current == null || current === \"\" || !parent.firstChild) parent.appendChild(value);\n else parent.replaceChild(value, parent.firstChild);\n current = value;\n }\n return current;\n}\nfunction normalizeIncomingArray(normalized, array, current, unwrap) {\n let dynamic = false;\n for (let i = 0, len = array.length; i < len; i++) {\n let item = array[i], prev = current && current[normalized.length], t;\n if (item == null || item === true || item === false);\n else if ((t = typeof item) === \"object\" && item.nodeType) normalized.push(item);\n else if (Array.isArray(item)) dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;\n else if (t === \"function\") if (unwrap) {\n while (typeof item === \"function\") item = item();\n dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;\n } else {\n normalized.push(item);\n dynamic = true;\n }\n else {\n const value = String(item);\n if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);\n else normalized.push(document.createTextNode(value));\n }\n }\n return dynamic;\n}\nfunction appendNodes(parent, array, marker = null) {\n for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);\n}\nfunction cleanChildren(parent, current, marker, replacement) {\n if (marker === void 0) return parent.textContent = \"\";\n const node = replacement || document.createTextNode(\"\");\n if (current.length) {\n let inserted = false;\n for (let i = current.length - 1; i >= 0; i--) {\n const el = current[i];\n if (node !== el) {\n const isParent = el.parentNode === parent;\n if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);\n else isParent && el.remove();\n } else inserted = true;\n }\n } else parent.insertBefore(node, marker);\n return [node];\n}\n//#endregion\n//#region node_modules/solid-js/store/dist/store.js\nvar $RAW = Symbol(\"store-raw\"), $NODE = Symbol(\"store-node\"), $HAS = Symbol(\"store-has\"), $SELF = Symbol(\"store-self\");\nfunction wrap$1(value) {\n let p = value[$PROXY];\n if (!p) {\n Object.defineProperty(value, $PROXY, { value: p = new Proxy(value, proxyTraps$1) });\n if (!Array.isArray(value)) {\n const keys = Object.keys(value), desc = Object.getOwnPropertyDescriptors(value);\n for (let i = 0, l = keys.length; i < l; i++) {\n const prop = keys[i];\n if (desc[prop].get) Object.defineProperty(value, prop, {\n enumerable: desc[prop].enumerable,\n get: desc[prop].get.bind(p)\n });\n }\n }\n }\n return p;\n}\nfunction isWrappable(obj) {\n let proto;\n return obj != null && typeof obj === \"object\" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj));\n}\nfunction unwrap(item, set = /* @__PURE__ */ new Set()) {\n let result, unwrapped, v, prop;\n if (result = item != null && item[$RAW]) return result;\n if (!isWrappable(item) || set.has(item)) return item;\n if (Array.isArray(item)) {\n if (Object.isFrozen(item)) item = item.slice(0);\n else set.add(item);\n for (let i = 0, l = item.length; i < l; i++) {\n v = item[i];\n if ((unwrapped = unwrap(v, set)) !== v) item[i] = unwrapped;\n }\n } else {\n if (Object.isFrozen(item)) item = Object.assign({}, item);\n else set.add(item);\n const keys = Object.keys(item), desc = Object.getOwnPropertyDescriptors(item);\n for (let i = 0, l = keys.length; i < l; i++) {\n prop = keys[i];\n if (desc[prop].get) continue;\n v = item[prop];\n if ((unwrapped = unwrap(v, set)) !== v) item[prop] = unwrapped;\n }\n }\n return item;\n}\nfunction getNodes(target, symbol) {\n let nodes = target[symbol];\n if (!nodes) Object.defineProperty(target, symbol, { value: nodes = Object.create(null) });\n return nodes;\n}\nfunction getNode(nodes, property, value) {\n if (nodes[property]) return nodes[property];\n const [s, set] = createSignal(value, {\n equals: false,\n internal: true\n });\n s.$ = set;\n return nodes[property] = s;\n}\nfunction proxyDescriptor$1(target, property) {\n const desc = Reflect.getOwnPropertyDescriptor(target, property);\n if (!desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE) return desc;\n delete desc.value;\n delete desc.writable;\n desc.get = () => target[$PROXY][property];\n return desc;\n}\nfunction trackSelf(target) {\n getListener() && getNode(getNodes(target, $NODE), $SELF)();\n}\nfunction ownKeys(target) {\n trackSelf(target);\n return Reflect.ownKeys(target);\n}\nvar proxyTraps$1 = {\n get(target, property, receiver) {\n if (property === $RAW) return target;\n if (property === $PROXY) return receiver;\n if (property === $TRACK) {\n trackSelf(target);\n return receiver;\n }\n const nodes = getNodes(target, $NODE);\n const tracked = nodes[property];\n let value = tracked ? tracked() : target[property];\n if (property === $NODE || property === $HAS || property === \"__proto__\") return value;\n if (!tracked) {\n const desc = Object.getOwnPropertyDescriptor(target, property);\n if (getListener() && (typeof value !== \"function\" || target.hasOwnProperty(property)) && !(desc && desc.get)) value = getNode(nodes, property, value)();\n }\n return isWrappable(value) ? wrap$1(value) : value;\n },\n has(target, property) {\n if (property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === $HAS || property === \"__proto__\") return true;\n getListener() && getNode(getNodes(target, $HAS), property)();\n return property in target;\n },\n set() {\n return true;\n },\n deleteProperty() {\n return true;\n },\n ownKeys,\n getOwnPropertyDescriptor: proxyDescriptor$1\n};\nfunction setProperty(state, property, value, deleting = false) {\n if (!deleting && state[property] === value) return;\n const prev = state[property], len = state.length;\n if (value === void 0) {\n delete state[property];\n if (state[$HAS] && state[$HAS][property] && prev !== void 0) state[$HAS][property].$();\n } else {\n state[property] = value;\n if (state[$HAS] && state[$HAS][property] && prev === void 0) state[$HAS][property].$();\n }\n let nodes = getNodes(state, $NODE), node;\n if (node = getNode(nodes, property, prev)) node.$(() => value);\n if (Array.isArray(state) && state.length !== len) {\n for (let i = state.length; i < len; i++) (node = nodes[i]) && node.$();\n (node = getNode(nodes, \"length\", len)) && node.$(state.length);\n }\n (node = nodes[$SELF]) && node.$();\n}\nfunction mergeStoreNode(state, value) {\n const keys = Object.keys(value);\n for (let i = 0; i < keys.length; i += 1) {\n const key = keys[i];\n setProperty(state, key, value[key]);\n }\n}\nfunction updateArray(current, next) {\n if (typeof next === \"function\") next = next(current);\n next = unwrap(next);\n if (Array.isArray(next)) {\n if (current === next) return;\n let i = 0, len = next.length;\n for (; i < len; i++) {\n const value = next[i];\n if (current[i] !== value) setProperty(current, i, value);\n }\n setProperty(current, \"length\", len);\n } else mergeStoreNode(current, next);\n}\nfunction updatePath(current, path, traversed = []) {\n let part, prev = current;\n if (path.length > 1) {\n part = path.shift();\n const partType = typeof part, isArray = Array.isArray(current);\n if (Array.isArray(part)) {\n for (let i = 0; i < part.length; i++) updatePath(current, [part[i]].concat(path), traversed);\n return;\n } else if (isArray && partType === \"function\") {\n for (let i = 0; i < current.length; i++) if (part(current[i], i)) updatePath(current, [i].concat(path), traversed);\n return;\n } else if (isArray && partType === \"object\") {\n const { from = 0, to = current.length - 1, by = 1 } = part;\n for (let i = from; i <= to; i += by) updatePath(current, [i].concat(path), traversed);\n return;\n } else if (path.length > 1) {\n updatePath(current[part], path, [part].concat(traversed));\n return;\n }\n prev = current[part];\n traversed = [part].concat(traversed);\n }\n let value = path[0];\n if (typeof value === \"function\") {\n value = value(prev, traversed);\n if (value === prev) return;\n }\n if (part === void 0 && value == void 0) return;\n value = unwrap(value);\n if (part === void 0 || isWrappable(prev) && isWrappable(value) && !Array.isArray(value)) mergeStoreNode(prev, value);\n else setProperty(current, part, value);\n}\nfunction createStore(...[store, options]) {\n const unwrappedStore = unwrap(store || {});\n const isArray = Array.isArray(unwrappedStore);\n const wrappedStore = wrap$1(unwrappedStore);\n function setStore(...args) {\n batch(() => {\n isArray && args.length === 1 ? updateArray(unwrappedStore, args[0]) : updatePath(unwrappedStore, args);\n });\n }\n return [wrappedStore, setStore];\n}\n//#endregion\n//#region src/transport.ts\nvar granolaTransportPaths = {\n authLock: \"/auth/lock\",\n authLogin: \"/auth/login\",\n authLogout: \"/auth/logout\",\n authMode: \"/auth/mode\",\n authRefresh: \"/auth/refresh\",\n authStatus: \"/auth/status\",\n authUnlock: \"/auth/unlock\",\n automationMatches: \"/automation/matches\",\n automationRules: \"/automation/rules\",\n automationRuns: \"/automation/runs\",\n events: \"/events\",\n exportJobs: \"/exports/jobs\",\n exportNotes: \"/exports/notes\",\n exportTranscripts: \"/exports/transcripts\",\n folderResolve: \"/folders/resolve\",\n folders: \"/folders\",\n health: \"/health\",\n meetingResolve: \"/meetings/resolve\",\n meetings: \"/meetings\",\n root: \"/\",\n serverInfo: \"/server/info\",\n syncRun: \"/sync\",\n syncEvents: \"/sync/events\",\n state: \"/state\"\n};\nfunction appendSearchParams(path, params) {\n const url = new URL(path, \"http://localhost\");\n for (const [key, value] of Object.entries(params)) {\n if (value === void 0 || value === false || value === \"\") continue;\n url.searchParams.set(key, String(value));\n }\n return `${url.pathname}${url.search}`;\n}\nfunction granolaMeetingPath(id) {\n return `${granolaTransportPaths.meetings}/${encodeURIComponent(id)}`;\n}\nfunction granolaMeetingResolvePath(query, options = {}) {\n return appendSearchParams(granolaTransportPaths.meetingResolve, {\n includeTranscript: options.includeTranscript ? \"true\" : void 0,\n q: query\n });\n}\nfunction granolaMeetingsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.meetings, {\n folderId: options.folderId,\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search,\n sort: options.sort,\n updatedFrom: options.updatedFrom,\n updatedTo: options.updatedTo\n });\n}\nfunction granolaFolderPath(id) {\n return `${granolaTransportPaths.folders}/${encodeURIComponent(id)}`;\n}\nfunction granolaFolderResolvePath(query) {\n return appendSearchParams(granolaTransportPaths.folderResolve, { q: query });\n}\nfunction granolaFoldersPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.folders, {\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search\n });\n}\nfunction granolaExportJobsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.exportJobs, { limit: options.limit });\n}\nfunction granolaAutomationRunsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationRuns, {\n limit: options.limit,\n status: options.status\n });\n}\nfunction granolaAutomationRunDecisionPath(id, decision) {\n return `${granolaTransportPaths.automationRuns}/${encodeURIComponent(id)}/${decision}`;\n}\nfunction granolaExportJobRerunPath(id) {\n return `${granolaTransportPaths.exportJobs}/${encodeURIComponent(id)}/rerun`;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/checkPrivateRedeclaration.js\nfunction _checkPrivateRedeclaration(e, t) {\n if (t.has(e)) throw new TypeError(\"Cannot initialize the same private elements twice on an object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldInitSpec.js\nfunction _classPrivateFieldInitSpec(e, t, a) {\n _checkPrivateRedeclaration(e, t), t.set(e, a);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/typeof.js\nfunction _typeof(o) {\n \"@babel/helpers - typeof\";\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function(o) {\n return typeof o;\n } : function(o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/defineProperty.js\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/assertClassBrand.js\nfunction _assertClassBrand(e, t, n) {\n if (\"function\" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;\n throw new TypeError(\"Private element is not present on this object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldSet2.js\nfunction _classPrivateFieldSet2(s, a, r) {\n return s.set(_assertClassBrand(s, a), r), r;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldGet2.js\nfunction _classPrivateFieldGet2(s, a) {\n return s.get(_assertClassBrand(s, a));\n}\n//#endregion\n//#region src/server/client.ts\nfunction cloneValue(value) {\n return structuredClone(value);\n}\nfunction normaliseServerUrl(serverUrl) {\n const raw = serverUrl instanceof URL ? serverUrl.href : serverUrl.trim();\n if (!raw) throw new Error(\"server URL is required\");\n const withProtocol = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw) ? raw : `http://${raw}`;\n const parsed = new URL(withProtocol);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") throw new Error(\"server URL must use http or https\");\n parsed.pathname = \"/\";\n parsed.search = \"\";\n parsed.hash = \"\";\n return parsed;\n}\nfunction mergeHeaders(...values) {\n const headers = new Headers();\n for (const value of values) {\n if (!value) continue;\n new Headers(value).forEach((headerValue, headerName) => {\n headers.set(headerName, headerValue);\n });\n }\n return headers;\n}\nasync function responseError(response) {\n let message = `${response.status} ${response.statusText}`.trim();\n try {\n const payload = await response.json();\n if (typeof payload.error === \"string\" && payload.error.trim()) message = payload.error;\n else if (typeof payload.message === \"string\" && payload.message.trim()) message = payload.message;\n } catch {\n const text = (await response.text()).trim();\n if (text) message = text;\n }\n return new Error(message);\n}\nfunction parseSseEvent(payload) {\n const data = payload.replaceAll(\"\\r\\n\", \"\\n\").split(\"\\n\").filter((line) => line.startsWith(\"data:\")).map((line) => line.slice(5).trimStart()).join(\"\\n\");\n if (!data) return;\n return JSON.parse(data);\n}\nvar _closed = /* @__PURE__ */ new WeakMap();\nvar _eventLoop = /* @__PURE__ */ new WeakMap();\nvar _listeners = /* @__PURE__ */ new WeakMap();\nvar _fetchImpl = /* @__PURE__ */ new WeakMap();\nvar _password = /* @__PURE__ */ new WeakMap();\nvar _reconnectDelayMs = /* @__PURE__ */ new WeakMap();\nvar _streamAbortController = /* @__PURE__ */ new WeakMap();\nvar _state = /* @__PURE__ */ new WeakMap();\nvar GranolaServerClient = class GranolaServerClient {\n constructor(info, url, initialState, options = {}) {\n _classPrivateFieldInitSpec(this, _closed, false);\n _classPrivateFieldInitSpec(this, _eventLoop, void 0);\n _classPrivateFieldInitSpec(this, _listeners, /* @__PURE__ */ new Set());\n _classPrivateFieldInitSpec(this, _fetchImpl, void 0);\n _classPrivateFieldInitSpec(this, _password, void 0);\n _classPrivateFieldInitSpec(this, _reconnectDelayMs, void 0);\n _defineProperty(this, \"info\", void 0);\n _classPrivateFieldInitSpec(this, _streamAbortController, void 0);\n _classPrivateFieldInitSpec(this, _state, void 0);\n this.url = url;\n _classPrivateFieldSet2(_fetchImpl, this, options.fetchImpl ?? fetch);\n this.info = cloneValue(info);\n _classPrivateFieldSet2(_password, this, options.password?.trim() || void 0);\n _classPrivateFieldSet2(_reconnectDelayMs, this, options.reconnectDelayMs ?? 1e3);\n _classPrivateFieldSet2(_state, this, cloneValue(initialState));\n }\n static async connect(serverUrl, options = {}) {\n const url = normaliseServerUrl(serverUrl);\n const fetchImpl = options.fetchImpl ?? fetch;\n const infoResponse = await fetchImpl(new URL(granolaTransportPaths.serverInfo, url), { headers: mergeHeaders({\n ...options.password?.trim() ? { \"x-granola-password\": options.password.trim() } : {},\n accept: \"application/json\"\n }) });\n if (!infoResponse.ok) throw await responseError(infoResponse);\n const info = await infoResponse.json();\n if (info.protocolVersion !== 2) throw new Error(`unsupported Granola transport protocol: expected 2, got ${info.protocolVersion}`);\n const response = await fetchImpl(new URL(granolaTransportPaths.state, url), { headers: mergeHeaders({\n ...options.password?.trim() ? { \"x-granola-password\": options.password.trim() } : {},\n accept: \"application/json\"\n }) });\n if (!response.ok) throw await responseError(response);\n const client = new GranolaServerClient(info, url, await response.json(), options);\n client.startEvents();\n return client;\n }\n async close() {\n _classPrivateFieldSet2(_closed, this, true);\n _classPrivateFieldGet2(_streamAbortController, this)?.abort();\n try {\n await _classPrivateFieldGet2(_eventLoop, this);\n } catch {}\n }\n getState() {\n return cloneValue(_classPrivateFieldGet2(_state, this));\n }\n subscribe(listener) {\n _classPrivateFieldGet2(_listeners, this).add(listener);\n return () => {\n _classPrivateFieldGet2(_listeners, this).delete(listener);\n };\n }\n async inspectAuth() {\n return await this.requestJson(granolaTransportPaths.authStatus);\n }\n async listAutomationRules() {\n return await this.requestJson(granolaTransportPaths.automationRules);\n }\n async listAutomationMatches(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.automationMatches}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.automationMatches;\n return await this.requestJson(path);\n }\n async listAutomationRuns(options = {}) {\n return await this.requestJson(granolaAutomationRunsPath(options));\n }\n async resolveAutomationRun(id, decision, options = {}) {\n return await this.requestJson(granolaAutomationRunDecisionPath(id, decision), {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async inspectSync() {\n return cloneValue(_classPrivateFieldGet2(_state, this).sync);\n }\n async listSyncEvents(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.syncEvents}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.syncEvents;\n return await this.requestJson(path);\n }\n async loginAuth(options = {}) {\n return await this.requestJson(granolaTransportPaths.authLogin, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async logoutAuth() {\n return await this.requestJson(granolaTransportPaths.authLogout, { method: \"POST\" });\n }\n async refreshAuth() {\n return await this.requestJson(granolaTransportPaths.authRefresh, { method: \"POST\" });\n }\n async switchAuthMode(mode) {\n return await this.requestJson(granolaTransportPaths.authMode, {\n body: JSON.stringify({ mode }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async sync(options = {}) {\n return await this.requestJson(granolaTransportPaths.syncRun, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async listFolders(options = {}) {\n return await this.requestJson(granolaFoldersPath(options));\n }\n async getFolder(id) {\n return await this.requestJson(granolaFolderPath(id));\n }\n async findFolder(query) {\n return await this.requestJson(granolaFolderResolvePath(query));\n }\n async listMeetings(options = {}) {\n return await this.requestJson(granolaMeetingsPath(options));\n }\n async getMeeting(id, options = {}) {\n return await this.requestJson(`${granolaMeetingPath(id)}${options.requireCache ? \"?includeTranscript=true\" : \"\"}`);\n }\n async findMeeting(query, options = {}) {\n return await this.requestJson(granolaMeetingResolvePath(query, { includeTranscript: options.requireCache }));\n }\n async listExportJobs(options = {}) {\n return await this.requestJson(granolaExportJobsPath(options));\n }\n async exportNotes(format = \"markdown\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportNotes, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async exportTranscripts(format = \"text\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportTranscripts, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async rerunExportJob(id) {\n return await this.requestJson(granolaExportJobRerunPath(id), { method: \"POST\" });\n }\n async request(path, init = {}) {\n const response = await _classPrivateFieldGet2(_fetchImpl, this).call(this, new URL(path, this.url), {\n ...init,\n headers: mergeHeaders({\n ..._classPrivateFieldGet2(_password, this) ? { \"x-granola-password\": _classPrivateFieldGet2(_password, this) } : {},\n accept: \"application/json\"\n }, init.headers)\n });\n if (!response.ok) throw await responseError(response);\n return response;\n }\n async requestJson(path, init = {}) {\n return cloneValue(await (await this.request(path, init)).json());\n }\n emit(event) {\n _classPrivateFieldSet2(_state, this, cloneValue(event.state));\n const nextEvent = cloneValue(event);\n for (const listener of _classPrivateFieldGet2(_listeners, this)) listener(nextEvent);\n }\n startEvents() {\n if (_classPrivateFieldGet2(_eventLoop, this)) return;\n _classPrivateFieldSet2(_eventLoop, this, this.runEventsLoop());\n }\n async runEventsLoop() {\n while (!_classPrivateFieldGet2(_closed, this)) {\n const controller = new AbortController();\n _classPrivateFieldSet2(_streamAbortController, this, controller);\n try {\n const response = await this.request(granolaTransportPaths.events, {\n headers: { accept: \"text/event-stream\" },\n signal: controller.signal\n });\n await this.consumeEventStream(response);\n } catch {\n if (_classPrivateFieldGet2(_closed, this) || controller.signal.aborted) break;\n await new Promise((resolve) => {\n setTimeout(resolve, _classPrivateFieldGet2(_reconnectDelayMs, this));\n });\n }\n }\n }\n async consumeEventStream(response) {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"server did not provide an event stream\");\n const decoder = new TextDecoder();\n let buffer = \"\";\n while (!_classPrivateFieldGet2(_closed, this)) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n buffer = buffer.replaceAll(\"\\r\\n\", \"\\n\");\n while (true) {\n const boundary = buffer.indexOf(\"\\n\\n\");\n if (boundary < 0) break;\n const chunk = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n const event = parseSseEvent(chunk);\n if (event) this.emit(event);\n }\n }\n }\n};\nasync function createGranolaServerClient(serverUrl, options = {}) {\n return await GranolaServerClient.connect(serverUrl, options);\n}\n//#endregion\n//#region src/web/client-state.ts\nvar granolaWebWorkspaceStorageKey = \"granola-toolkit.web-workspace\";\nvar maxRecentMeetings = 6;\nvar maxSavedFilters = 6;\nfunction normaliseFilterValue(value) {\n const trimmed = value?.trim();\n return trimmed ? trimmed : void 0;\n}\nfunction normaliseFilters(filters) {\n const selectedFolderId = normaliseFilterValue(filters.selectedFolderId);\n return {\n search: normaliseFilterValue(filters.search),\n selectedFolderId,\n sort: normaliseFilterValue(filters.sort) ?? \"updated-desc\",\n updatedFrom: normaliseFilterValue(filters.updatedFrom),\n updatedTo: normaliseFilterValue(filters.updatedTo)\n };\n}\nfunction filtersKey(filters) {\n return JSON.stringify(normaliseFilters(filters));\n}\nfunction defaultWorkspacePreferences() {\n return {\n recentMeetings: [],\n savedFilters: []\n };\n}\nfunction parseWorkspacePreferences(raw) {\n if (!raw) return defaultWorkspacePreferences();\n try {\n const parsed = JSON.parse(raw);\n return {\n recentMeetings: Array.isArray(parsed?.recentMeetings) ? parsed.recentMeetings.map((entry) => ({\n folderId: normaliseFilterValue(entry?.folderId),\n id: normaliseFilterValue(entry?.id) || \"\",\n title: normaliseFilterValue(entry?.title) || \"\",\n updatedAt: normaliseFilterValue(entry?.updatedAt) || \"\"\n })).filter((entry) => entry.id && entry.title).slice(0, maxRecentMeetings) : [],\n savedFilters: Array.isArray(parsed?.savedFilters) ? parsed.savedFilters.map((preset) => ({\n filters: normaliseFilters(preset?.filters ?? {}),\n id: normaliseFilterValue(preset?.id) || \"\",\n label: normaliseFilterValue(preset?.label) || \"\"\n })).filter((preset) => preset.id && preset.label).slice(0, maxSavedFilters) : []\n };\n } catch {\n return defaultWorkspacePreferences();\n }\n}\nfunction serialiseWorkspacePreferences(preferences) {\n return JSON.stringify({\n recentMeetings: preferences.recentMeetings.slice(0, maxRecentMeetings),\n savedFilters: preferences.savedFilters.slice(0, maxSavedFilters)\n });\n}\nfunction hasActiveFilters(filters) {\n const normalised = normaliseFilters(filters);\n return Boolean(normalised.search || normalised.selectedFolderId || normalised.updatedFrom || normalised.updatedTo || normalised.sort !== \"updated-desc\");\n}\nfunction filterLabel(filters) {\n const summary = currentFilterSummary(filters);\n if (!summary) return \"Current workspace\";\n return summary;\n}\nfunction rememberRecentMeeting(preferences, meeting) {\n const nextEntry = {\n folderId: meeting.folders?.[0]?.id,\n id: meeting.id,\n title: meeting.title?.trim() || meeting.id,\n updatedAt: meeting.updatedAt\n };\n return {\n ...preferences,\n recentMeetings: [nextEntry, ...preferences.recentMeetings.filter((entry) => entry.id !== nextEntry.id)].slice(0, maxRecentMeetings)\n };\n}\nfunction saveWorkspaceFilter(preferences, filters, options = {}) {\n const nextFilters = normaliseFilters(filters);\n if (!hasActiveFilters(nextFilters)) return preferences;\n const key = filtersKey(nextFilters);\n const nextPreset = {\n filters: nextFilters,\n id: preferences.savedFilters.find((preset) => filtersKey(preset.filters) === key)?.id ?? options.idFactory?.() ?? `filter-${preferences.savedFilters.length + 1}`,\n label: filterLabel(filters)\n };\n return {\n ...preferences,\n savedFilters: [nextPreset, ...preferences.savedFilters.filter((preset) => preset.id !== nextPreset.id)].slice(0, maxSavedFilters)\n };\n}\nfunction removeWorkspaceFilter(preferences, id) {\n return {\n ...preferences,\n savedFilters: preferences.savedFilters.filter((preset) => preset.id !== id)\n };\n}\nfunction applyWorkspaceFilter(preset) {\n return {\n search: preset.filters.search ?? \"\",\n selectedFolderId: preset.filters.selectedFolderId ?? null,\n sort: preset.filters.sort ?? \"updated-desc\",\n updatedFrom: preset.filters.updatedFrom ?? \"\",\n updatedTo: preset.filters.updatedTo ?? \"\"\n };\n}\nfunction parseWorkspaceTab(value) {\n switch (value) {\n case \"metadata\":\n case \"raw\":\n case \"transcript\": return value;\n default: return \"notes\";\n }\n}\nfunction startupSelectionFromSearch(search) {\n const params = new URLSearchParams(search);\n return {\n folderId: params.get(\"folder\")?.trim() || \"\",\n meetingId: params.get(\"meeting\")?.trim() || \"\",\n workspaceTab: parseWorkspaceTab(params.get(\"tab\"))\n };\n}\nfunction buildBrowserUrlPath(currentHref, selection) {\n const url = new URL(currentHref);\n if (selection.selectedFolderId) url.searchParams.set(\"folder\", selection.selectedFolderId);\n else url.searchParams.delete(\"folder\");\n if (selection.selectedMeetingId) url.searchParams.set(\"meeting\", selection.selectedMeetingId);\n else url.searchParams.delete(\"meeting\");\n if (parseWorkspaceTab(selection.workspaceTab) !== \"notes\") url.searchParams.set(\"tab\", parseWorkspaceTab(selection.workspaceTab));\n else url.searchParams.delete(\"tab\");\n return `${url.pathname}${url.search}${url.hash}`;\n}\nfunction exportScopeLabel(scope) {\n return scope && scope.mode === \"folder\" ? `Folder: ${scope.folderName || scope.folderId}` : \"Scope: All meetings\";\n}\nfunction currentFilterSummary(filters) {\n const parts = [];\n if (filters.selectedFolderId) {\n const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);\n parts.push(`folder \"${folder ? folder.name : filters.selectedFolderId}\"`);\n }\n if (filters.search) parts.push(`search \"${filters.search}\"`);\n if (filters.updatedFrom) parts.push(`from ${filters.updatedFrom}`);\n if (filters.updatedTo) parts.push(`to ${filters.updatedTo}`);\n if (filters.sort && filters.sort !== \"updated-desc\") parts.push(filters.sort === \"updated-asc\" ? \"oldest first\" : filters.sort === \"title-asc\" ? \"title A-Z\" : \"title Z-A\");\n return parts.join(\", \");\n}\nfunction selectMeetingId(meetings, selectedMeetingId) {\n if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;\n return meetings[0]?.id ?? null;\n}\nfunction nextWorkspaceTab(currentTab, key) {\n const current = parseWorkspaceTab(currentTab);\n switch (key) {\n case \"1\": return \"notes\";\n case \"2\": return \"transcript\";\n case \"3\": return \"metadata\";\n case \"4\": return \"raw\";\n case \"]\":\n switch (current) {\n case \"notes\": return \"transcript\";\n case \"transcript\": return \"metadata\";\n case \"metadata\": return \"raw\";\n case \"raw\": return \"notes\";\n }\n break;\n case \"[\":\n switch (current) {\n case \"notes\": return \"raw\";\n case \"transcript\": return \"notes\";\n case \"metadata\": return \"transcript\";\n case \"raw\": return \"metadata\";\n }\n break;\n default: return;\n }\n}\nfunction describeSyncStatus(sync) {\n if (sync.running) return \"Sync running\";\n if (sync.lastError) return \"Sync needs attention\";\n if (sync.lastCompletedAt) {\n const suffix = sync.summary?.changedCount ? ` · ${sync.summary.changedCount} changes` : \"\";\n return `Synced ${sync.lastCompletedAt.slice(11, 19)}${suffix}`;\n }\n return \"Sync idle\";\n}\nfunction describeAuthStatus(auth) {\n if (!auth) return \"Waiting for auth\";\n if (auth.lastError) return \"Auth needs attention\";\n switch (auth.mode) {\n case \"api-key\": return \"API key active\";\n case \"stored-session\": return \"Stored session active\";\n default: return \"supabase.json active\";\n }\n}\n//#endregion\n//#region src/web-app/components.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$$1 = /* @__PURE__ */ template(`<section class=hero><h1>Granola Toolkit</h1><p>Browser workspace for folders, meetings, notes, transcripts, and export flows on top of one local server instance.</p><input class=search placeholder=\"Search meetings, ids, or tags\"><div class=\"field-row field-row--inline\"><label><span class=field-label>Sort</span><select class=select><option value=updated-desc>Newest first</option><option value=updated-asc>Oldest first</option><option value=title-asc>Title A-Z</option><option value=title-desc>Title Z-A</option></select></label><label><span class=field-label>Updated From</span><input class=field-input type=date></label></div><label class=field-row><span class=field-label>Updated To</span><input class=field-input type=date>`), _tmpl$2 = /* @__PURE__ */ template(`<section class=toolbar><div><p>Meetings are loaded from the shared server state so this view can stay aligned with the terminal UI and sync loop.</p></div><div class=toolbar-form><input class=field-input placeholder=\"Quick open by id or title\"><button class=\"button button--secondary\"type=button>Open`), _tmpl$3 = /* @__PURE__ */ template(`<div class=\"folder-empty folder-empty--error\">`), _tmpl$4 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Folders</h2><p>Pick a folder to scope the meeting browser, or stay on All meetings.</p></div><div class=folder-list>`), _tmpl$5 = /* @__PURE__ */ template(`<button class=folder-row type=button><span class=folder-row__title>All meetings</span><span class=folder-row__meta>Browse the full meeting list.`), _tmpl$6 = /* @__PURE__ */ template(`<div class=folder-empty>No folders found.`), _tmpl$7 = /* @__PURE__ */ template(`<button class=folder-row type=button><span class=folder-row__title></span><span class=folder-row__meta>`), _tmpl$8 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Saved Filters</h2><p>Keep the slices you revisit often close at hand.</p></div><div class=saved-filter-actions><button class=\"button button--secondary\"type=button>Save current filter</button></div><div class=saved-filter-list>`), _tmpl$9 = /* @__PURE__ */ template(`<div class=folder-empty>No saved filters yet.`), _tmpl$0 = /* @__PURE__ */ template(`<div class=saved-filter-card><button class=saved-filter-card__main type=button><span class=folder-row__title></span><span class=folder-row__meta></span></button><button class=saved-filter-card__remove type=button>Remove`), _tmpl$1 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Recent Meetings</h2><p>Jump back into the conversations you opened most recently.</p></div><div class=folder-list>`), _tmpl$10 = /* @__PURE__ */ template(`<div class=folder-empty>No recent meetings yet.`), _tmpl$11 = /* @__PURE__ */ template(`<div class=\"meeting-empty meeting-empty--error\">`), _tmpl$12 = /* @__PURE__ */ template(`<section class=meeting-list>`), _tmpl$13 = /* @__PURE__ */ template(`<div class=meeting-empty>`), _tmpl$14 = /* @__PURE__ */ template(`<button class=meeting-row type=button><span class=meeting-row__title></span><span class=meeting-row__meta></span><span class=meeting-row__meta>`), _tmpl$15 = /* @__PURE__ */ template(`<p>`), _tmpl$16 = /* @__PURE__ */ template(`<section class=detail-head><div><h2>Meeting Workspace</h2></div><div class=state-badge>`), _tmpl$17 = /* @__PURE__ */ template(`<p>Waiting for server state…`), _tmpl$18 = /* @__PURE__ */ template(`<div class=status-grid><div><span class=status-label>Surface</span><strong></strong></div><div><span class=status-label>View</span><strong></strong></div><div><span class=status-label>Auth</span><strong></strong></div><div><span class=status-label>Sync</span><strong></strong></div><div><span class=status-label>Documents</span><strong></strong></div><div><span class=status-label>Folders</span><strong></strong></div><div><span class=status-label>Cache</span><strong></strong></div><div><span class=status-label>Index</span><strong></strong></div><div><span class=status-label>Automation</span><strong>`), _tmpl$19 = /* @__PURE__ */ template(`<section class=security-panel><div class=security-panel__head><h3>Server Access</h3><p>This server is locked with a password. Unlock it to load meetings and live state.</p></div><div class=security-panel__body><input class=field-input placeholder=\"Server password\"type=password><div class=toolbar-actions><button class=\"button button--primary\"type=button>Unlock</button><button class=\"button button--secondary\"type=button>Lock`), _tmpl$20 = /* @__PURE__ */ template(`<section class=auth-panel><div class=auth-panel__head><h3>Auth Session</h3><p>Inspect, refresh, and switch between API key, stored session, and <code>supabase.json</code>.</p></div><div class=auth-panel__body>`), _tmpl$21 = /* @__PURE__ */ template(`<div class=auth-card><div class=auth-card__meta>Auth state unavailable.`), _tmpl$22 = /* @__PURE__ */ template(`<div class=auth-card__meta>Client ID: `), _tmpl$23 = /* @__PURE__ */ template(`<div class=auth-card__meta>Sign-in method: `), _tmpl$24 = /* @__PURE__ */ template(`<div class=auth-card__meta>supabase path: `), _tmpl$25 = /* @__PURE__ */ template(`<div class=\"auth-card__meta auth-card__error\">`), _tmpl$26 = /* @__PURE__ */ template(`<div class=auth-card><div class=status-grid><div><span class=status-label>Active</span><strong></strong></div><div><span class=status-label>API key</span><strong></strong></div><div><span class=status-label>Stored</span><strong></strong></div><div><span class=status-label>supabase.json</span><strong></strong></div><div><span class=status-label>Refresh</span><strong></strong></div></div><div class=auth-card__meta>Store a Granola Personal API key here or use <code>granola auth login --api-key <token></code>.</div><div class=auth-card__actions><input class=input placeholder=grn_... type=password><button class=\"button button--secondary\"type=button>Save API key</button><button class=\"button button--secondary\"type=button>Import desktop session</button><button class=\"button button--secondary\"type=button>Refresh stored session</button><button class=\"button button--secondary\"type=button>Use API key</button><button class=\"button button--secondary\"type=button>Use stored session</button><button class=\"button button--secondary\"type=button>Use supabase.json</button><button class=\"button button--secondary\"type=button>Sign out`), _tmpl$27 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Recent Export Jobs</h3><p>Tracked across CLI and web runs.</p></div><div class=jobs-list>`), _tmpl$28 = /* @__PURE__ */ template(`<div class=job-empty>No export jobs yet.`), _tmpl$29 = /* @__PURE__ */ template(`<div class=job-card__meta>`), _tmpl$30 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Rerun`), _tmpl$31 = /* @__PURE__ */ template(`<article class=job-card><div class=job-card__head><div><div class=job-card__title> export</div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta>Started: </div><div class=job-card__meta>Output: </div><div class=job-card__actions>`), _tmpl$32 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Automation Runs</h3><p>Recent action runs triggered by durable sync events.</p></div><div class=jobs-list>`), _tmpl$33 = /* @__PURE__ */ template(`<div class=job-empty>No automation runs yet.`), _tmpl$34 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Approve`), _tmpl$35 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Reject`), _tmpl$36 = /* @__PURE__ */ template(`<article class=job-card><div class=job-card__head><div><div class=job-card__title></div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta></div><div class=job-card__actions>`), _tmpl$37 = /* @__PURE__ */ template(`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle`), _tmpl$38 = /* @__PURE__ */ template(`<button class=workspace-tab type=button>`), _tmpl$39 = /* @__PURE__ */ template(`<div class=empty>`), _tmpl$40 = /* @__PURE__ */ template(`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>`), _tmpl$41 = /* @__PURE__ */ template(`<div class=detail-body><div class=workspace-grid><aside class=\"detail-section workspace-sidebar\"><h2>Meeting Metadata</h2><pre class=detail-pre></pre></aside><section class=\"detail-section workspace-main\"><h2></h2><pre class=detail-pre>`);\nfunction authModeLabel(mode) {\n switch (mode) {\n case \"api-key\": return \"API key\";\n case \"stored-session\": return \"Stored session\";\n default: return \"supabase.json\";\n }\n}\nfunction metadataLines(record) {\n return [\n `Title: ${record.meeting.title || record.meeting.id}`,\n `Created: ${record.meeting.createdAt}`,\n `Updated: ${record.meeting.updatedAt}`,\n `Folders: ${record.meeting.folders.length ? record.meeting.folders.map((folder) => folder.name).join(\", \") : \"none\"}`,\n `Tags: ${record.meeting.tags.length ? record.meeting.tags.join(\", \") : \"none\"}`,\n `Transcript loaded: ${record.meeting.transcriptLoaded ? \"yes\" : \"no\"}`\n ].join(\"\\n\");\n}\nfunction workspaceBody(bundle, record, tab) {\n switch (tab) {\n case \"transcript\": return {\n body: record.transcriptText || \"(Transcript unavailable)\",\n title: \"Transcript\"\n };\n case \"metadata\": return {\n body: metadataLines(record),\n title: \"Metadata\"\n };\n case \"raw\": return {\n body: JSON.stringify(bundle || record, null, 2),\n title: \"Raw Bundle\"\n };\n default: return {\n body: record.noteMarkdown || \"(No notes available)\",\n title: \"Notes\"\n };\n }\n}\nfunction scopeLabel(scope) {\n return exportScopeLabel(scope);\n}\nfunction ToolbarFilters(props) {\n return [(() => {\n var _el$ = _tmpl$$1(), _el$4 = _el$.firstChild.nextSibling.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$8 = _el$6.firstChild.nextSibling, _el$1 = _el$6.nextSibling.firstChild.nextSibling, _el$12 = _el$5.nextSibling.firstChild.nextSibling;\n _el$4.$$input = (event) => {\n props.onSearchInput(event.currentTarget.value);\n };\n _el$8.addEventListener(\"change\", (event) => {\n props.onSortChange(event.currentTarget.value);\n });\n _el$1.addEventListener(\"change\", (event) => {\n props.onUpdatedFromChange(event.currentTarget.value);\n });\n _el$12.addEventListener(\"change\", (event) => {\n props.onUpdatedToChange(event.currentTarget.value);\n });\n createRenderEffect(() => _el$4.value = props.search);\n createRenderEffect(() => _el$8.value = props.sort);\n createRenderEffect(() => _el$1.value = props.updatedFrom);\n createRenderEffect(() => _el$12.value = props.updatedTo);\n return _el$;\n })(), (() => {\n var _el$13 = _tmpl$2(), _el$16 = _el$13.firstChild.nextSibling.firstChild, _el$17 = _el$16.nextSibling;\n _el$16.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onQuickOpen();\n }\n };\n _el$16.$$input = (event) => {\n props.onQuickOpenInput(event.currentTarget.value);\n };\n addEventListener(_el$17, \"click\", props.onQuickOpen, true);\n createRenderEffect(() => _el$16.value = props.quickOpen);\n return _el$13;\n })()];\n}\nfunction FolderList(props) {\n return (() => {\n var _el$18 = _tmpl$4(), _el$20 = _el$18.firstChild.nextSibling;\n insert(_el$20, createComponent(Show, {\n get fallback() {\n return [\n (() => {\n var _el$22 = _tmpl$5();\n _el$22.$$click = () => {\n props.onSelect(null);\n };\n createRenderEffect(() => setAttribute(_el$22, \"data-selected\", !props.selectedFolderId ? \"true\" : void 0));\n return _el$22;\n })(),\n createComponent(For, {\n get each() {\n return props.folders;\n },\n children: (folder) => (() => {\n var _el$24 = _tmpl$7(), _el$25 = _el$24.firstChild, _el$26 = _el$25.nextSibling;\n _el$24.$$click = () => {\n props.onSelect(folder.id);\n };\n insert(_el$25, () => (folder.isFavourite ? \"★ \" : \"\") + (folder.name || folder.id));\n insert(_el$26, () => `${folder.documentCount} meetings`);\n createRenderEffect(() => setAttribute(_el$24, \"data-selected\", folder.id === props.selectedFolderId ? \"true\" : void 0));\n return _el$24;\n })()\n }),\n createComponent(Show, {\n get when() {\n return props.folders.length === 0;\n },\n get children() {\n return _tmpl$6();\n }\n })\n ];\n },\n get when() {\n return !props.error;\n },\n get children() {\n var _el$21 = _tmpl$3();\n insert(_el$21, () => props.error);\n return _el$21;\n }\n }));\n return _el$18;\n })();\n}\nfunction SavedFiltersPanel(props) {\n const canSaveCurrent = () => hasActiveFilters({\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n sort: props.sort,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$27 = _tmpl$8(), _el$29 = _el$27.firstChild.nextSibling, _el$30 = _el$29.firstChild, _el$31 = _el$29.nextSibling;\n _el$30.$$click = () => {\n props.onSaveCurrent();\n };\n insert(_el$31, createComponent(Show, {\n get when() {\n return props.savedFilters.length > 0;\n },\n get fallback() {\n return _tmpl$9();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.savedFilters;\n },\n children: (preset) => (() => {\n var _el$33 = _tmpl$0(), _el$34 = _el$33.firstChild, _el$35 = _el$34.firstChild, _el$36 = _el$35.nextSibling, _el$37 = _el$34.nextSibling;\n _el$34.$$click = () => {\n props.onApply(preset);\n };\n insert(_el$35, () => preset.label);\n insert(_el$36, () => currentFilterSummary({\n folders: props.folders,\n ...preset.filters\n }) || \"Saved workspace scope\");\n _el$37.$$click = () => {\n props.onRemove(preset.id);\n };\n return _el$33;\n })()\n });\n }\n }));\n createRenderEffect(() => _el$30.disabled = !canSaveCurrent());\n return _el$27;\n })();\n}\nfunction RecentMeetingsPanel(props) {\n return (() => {\n var _el$38 = _tmpl$1(), _el$40 = _el$38.firstChild.nextSibling;\n insert(_el$40, createComponent(Show, {\n get when() {\n return props.recentMeetings.length > 0;\n },\n get fallback() {\n return _tmpl$10();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.recentMeetings;\n },\n children: (meeting) => (() => {\n var _el$42 = _tmpl$7(), _el$43 = _el$42.firstChild, _el$44 = _el$43.nextSibling;\n _el$42.$$click = () => {\n props.onOpen(meeting);\n };\n insert(_el$43, () => meeting.title);\n insert(_el$44, () => meeting.updatedAt.slice(0, 10));\n return _el$42;\n })()\n });\n }\n }));\n return _el$38;\n })();\n}\nfunction MeetingList(props) {\n const summary = () => currentFilterSummary({\n folders: props.folders,\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$45 = _tmpl$12();\n insert(_el$45, createComponent(Show, {\n get fallback() {\n return createComponent(Show, {\n get fallback() {\n return (() => {\n var _el$47 = _tmpl$13();\n insert(_el$47, (() => {\n var _c$ = memo(() => !!summary());\n return () => _c$() ? `No meetings match ${summary()}.` : props.emptyHint || \"No meetings yet. Try Sync now.\";\n })());\n return _el$47;\n })();\n },\n get when() {\n return props.meetings.length > 0;\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.meetings;\n },\n children: (meeting) => (() => {\n var _el$48 = _tmpl$14(), _el$49 = _el$48.firstChild, _el$50 = _el$49.nextSibling, _el$51 = _el$50.nextSibling;\n _el$48.$$click = () => {\n props.onSelect(meeting.id);\n };\n insert(_el$49, () => meeting.title || meeting.id);\n insert(_el$50, (() => {\n var _c$2 = memo(() => !!meeting.tags.length);\n return () => _c$2() ? meeting.tags.map((tag) => `#${tag}`).join(\" \") : \"untagged\";\n })());\n insert(_el$51, (() => {\n var _c$3 = memo(() => !!meeting.updatedAt);\n return () => _c$3() ? meeting.updatedAt.slice(0, 10) : \"unknown\";\n })());\n createRenderEffect(() => setAttribute(_el$48, \"data-selected\", meeting.id === props.selectedMeetingId ? \"true\" : void 0));\n return _el$48;\n })()\n });\n }\n });\n },\n get when() {\n return props.error;\n },\n get children() {\n var _el$46 = _tmpl$11();\n insert(_el$46, () => props.error);\n return _el$46;\n }\n }));\n return _el$45;\n })();\n}\nfunction AppStatePanel(props) {\n const syncStatus = () => describeSyncStatus(props.appState?.sync ?? {});\n const authStatus = () => describeAuthStatus(props.appState?.auth);\n return (() => {\n var _el$52 = _tmpl$16(), _el$53 = _el$52.firstChild;\n _el$53.firstChild;\n var _el$56 = _el$53.nextSibling;\n insert(_el$53, createComponent(Show, {\n get fallback() {\n return _tmpl$17();\n },\n get when() {\n return props.appState;\n },\n children: (appState) => (() => {\n var _el$58 = _tmpl$18(), _el$59 = _el$58.firstChild, _el$61 = _el$59.firstChild.nextSibling, _el$62 = _el$59.nextSibling, _el$64 = _el$62.firstChild.nextSibling, _el$65 = _el$62.nextSibling, _el$67 = _el$65.firstChild.nextSibling, _el$68 = _el$65.nextSibling, _el$70 = _el$68.firstChild.nextSibling, _el$71 = _el$68.nextSibling, _el$73 = _el$71.firstChild.nextSibling, _el$74 = _el$71.nextSibling, _el$76 = _el$74.firstChild.nextSibling, _el$77 = _el$74.nextSibling, _el$79 = _el$77.firstChild.nextSibling, _el$80 = _el$77.nextSibling, _el$82 = _el$80.firstChild.nextSibling, _el$85 = _el$80.nextSibling.firstChild.nextSibling;\n insert(_el$61, () => appState().ui.surface);\n insert(_el$64, () => appState().ui.view);\n insert(_el$67, authStatus);\n insert(_el$70, syncStatus);\n insert(_el$73, (() => {\n var _c$4 = memo(() => !!appState().documents.loaded);\n return () => _c$4() ? String(appState().documents.count) : \"not loaded\";\n })());\n insert(_el$76, (() => {\n var _c$5 = memo(() => !!appState().folders.loaded);\n return () => _c$5() ? String(appState().folders.count) : \"not loaded\";\n })());\n insert(_el$79, (() => {\n var _c$6 = memo(() => !!appState().cache.loaded);\n return () => _c$6() ? `${appState().cache.transcriptCount} transcript sets` : appState().cache.configured ? \"configured\" : \"not configured\";\n })());\n insert(_el$82, (() => {\n var _c$7 = memo(() => !!appState().index.loaded);\n return () => _c$7() ? `${appState().index.meetingCount} meetings` : appState().index.available ? \"available\" : \"not built\";\n })());\n insert(_el$85, () => `${appState().automation.runCount} runs / ${appState().automation.pendingRunCount} pending`);\n return _el$58;\n })()\n }), null);\n insert(_el$53, createComponent(Show, {\n get when() {\n return props.appState?.auth.lastError;\n },\n get children() {\n var _el$55 = _tmpl$15();\n insert(_el$55, () => props.appState?.auth.lastError);\n return _el$55;\n }\n }), null);\n insert(_el$56, () => props.statusLabel);\n createRenderEffect(() => setAttribute(_el$56, \"data-tone\", props.statusTone));\n return _el$52;\n })();\n}\nfunction SecurityPanel(props) {\n return createComponent(Show, {\n get when() {\n return props.visible;\n },\n get children() {\n var _el$86 = _tmpl$19(), _el$89 = _el$86.firstChild.nextSibling.firstChild, _el$91 = _el$89.nextSibling.firstChild, _el$92 = _el$91.nextSibling;\n _el$89.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onUnlock();\n }\n };\n _el$89.$$input = (event) => {\n props.onPasswordChange(event.currentTarget.value);\n };\n addEventListener(_el$91, \"click\", props.onUnlock, true);\n addEventListener(_el$92, \"click\", props.onLock, true);\n createRenderEffect(() => _el$89.value = props.password);\n return _el$86;\n }\n });\n}\nfunction AuthPanel(props) {\n return (() => {\n var _el$93 = _tmpl$20(), _el$95 = _el$93.firstChild.nextSibling;\n insert(_el$95, createComponent(Show, {\n get fallback() {\n return _tmpl$21();\n },\n get when() {\n return props.auth;\n },\n children: (auth) => (() => {\n var _el$97 = _tmpl$26(), _el$98 = _el$97.firstChild, _el$99 = _el$98.firstChild, _el$101 = _el$99.firstChild.nextSibling, _el$102 = _el$99.nextSibling, _el$104 = _el$102.firstChild.nextSibling, _el$105 = _el$102.nextSibling, _el$107 = _el$105.firstChild.nextSibling, _el$108 = _el$105.nextSibling, _el$110 = _el$108.firstChild.nextSibling, _el$113 = _el$108.nextSibling.firstChild.nextSibling, _el$121 = _el$98.nextSibling, _el$123 = _el$121.nextSibling.firstChild, _el$124 = _el$123.nextSibling, _el$125 = _el$124.nextSibling, _el$126 = _el$125.nextSibling, _el$127 = _el$126.nextSibling, _el$128 = _el$127.nextSibling, _el$129 = _el$128.nextSibling, _el$130 = _el$129.nextSibling;\n insert(_el$101, () => authModeLabel(auth().mode));\n insert(_el$104, () => auth().apiKeyAvailable ? \"available\" : \"missing\");\n insert(_el$107, () => auth().storedSessionAvailable ? \"available\" : \"missing\");\n insert(_el$110, () => auth().supabaseAvailable ? \"available\" : \"missing\");\n insert(_el$113, () => auth().refreshAvailable ? \"available\" : \"missing\");\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().clientId;\n },\n get children() {\n var _el$114 = _tmpl$22();\n _el$114.firstChild;\n insert(_el$114, () => auth().clientId, null);\n return _el$114;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().signInMethod;\n },\n get children() {\n var _el$116 = _tmpl$23();\n _el$116.firstChild;\n insert(_el$116, () => auth().signInMethod, null);\n return _el$116;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().supabasePath;\n },\n get children() {\n var _el$118 = _tmpl$24();\n _el$118.firstChild;\n insert(_el$118, () => auth().supabasePath, null);\n return _el$118;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().lastError;\n },\n get children() {\n var _el$120 = _tmpl$25();\n insert(_el$120, () => auth().lastError);\n return _el$120;\n }\n }), _el$121);\n _el$123.$$input = (event) => {\n props.onApiKeyDraftChange(event.currentTarget.value);\n };\n addEventListener(_el$124, \"click\", props.onSaveApiKey, true);\n addEventListener(_el$125, \"click\", props.onImportDesktopSession, true);\n addEventListener(_el$126, \"click\", props.onRefresh, true);\n _el$127.$$click = () => {\n props.onSwitchMode(\"api-key\");\n };\n _el$128.$$click = () => {\n props.onSwitchMode(\"stored-session\");\n };\n _el$129.$$click = () => {\n props.onSwitchMode(\"supabase-file\");\n };\n addEventListener(_el$130, \"click\", props.onLogout, true);\n createRenderEffect((_p$) => {\n var _v$ = !auth().supabaseAvailable, _v$2 = !auth().storedSessionAvailable || !auth().refreshAvailable, _v$3 = !auth().apiKeyAvailable || auth().mode === \"api-key\", _v$4 = !auth().storedSessionAvailable || auth().mode === \"stored-session\", _v$5 = !auth().supabaseAvailable || auth().mode === \"supabase-file\", _v$6 = !auth().apiKeyAvailable && !auth().storedSessionAvailable;\n _v$ !== _p$.e && (_el$125.disabled = _p$.e = _v$);\n _v$2 !== _p$.t && (_el$126.disabled = _p$.t = _v$2);\n _v$3 !== _p$.a && (_el$127.disabled = _p$.a = _v$3);\n _v$4 !== _p$.o && (_el$128.disabled = _p$.o = _v$4);\n _v$5 !== _p$.i && (_el$129.disabled = _p$.i = _v$5);\n _v$6 !== _p$.n && (_el$130.disabled = _p$.n = _v$6);\n return _p$;\n }, {\n e: void 0,\n t: void 0,\n a: void 0,\n o: void 0,\n i: void 0,\n n: void 0\n });\n createRenderEffect(() => _el$123.value = props.apiKeyDraft);\n return _el$97;\n })()\n }));\n return _el$93;\n })();\n}\nfunction ExportJobsPanel(props) {\n return (() => {\n var _el$131 = _tmpl$27(), _el$133 = _el$131.firstChild.nextSibling;\n insert(_el$133, createComponent(Show, {\n get when() {\n return props.jobs.length > 0;\n },\n get fallback() {\n return _tmpl$28();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.jobs.slice(0, 6);\n },\n children: (job) => (() => {\n var _el$135 = _tmpl$31(), _el$136 = _el$135.firstChild, _el$137 = _el$136.firstChild, _el$138 = _el$137.firstChild, _el$139 = _el$138.firstChild, _el$140 = _el$138.nextSibling, _el$141 = _el$137.nextSibling, _el$142 = _el$136.nextSibling, _el$143 = _el$142.nextSibling;\n _el$143.firstChild;\n var _el$145 = _el$143.nextSibling;\n _el$145.firstChild;\n var _el$148 = _el$145.nextSibling;\n insert(_el$138, () => job.kind, _el$139);\n insert(_el$140, () => job.id);\n insert(_el$141, () => job.status);\n insert(_el$142, () => `Format: ${job.format} • ${scopeLabel(job.scope)} • ${job.itemCount > 0 ? `${job.completedCount}/${job.itemCount} items` : \"0 items\"} • Written: ${job.written}`);\n insert(_el$143, () => job.startedAt.slice(0, 19), null);\n insert(_el$145, () => job.outputDir, null);\n insert(_el$135, createComponent(Show, {\n get when() {\n return job.error;\n },\n get children() {\n var _el$147 = _tmpl$29();\n insert(_el$147, () => job.error);\n return _el$147;\n }\n }), _el$148);\n insert(_el$148, createComponent(Show, {\n get when() {\n return job.status !== \"running\";\n },\n get children() {\n var _el$149 = _tmpl$30();\n _el$149.$$click = () => {\n props.onRerun(job.id);\n };\n return _el$149;\n }\n }));\n createRenderEffect(() => setAttribute(_el$141, \"data-status\", job.status));\n return _el$135;\n })()\n });\n }\n }));\n return _el$131;\n })();\n}\nfunction AutomationRunsPanel(props) {\n return (() => {\n var _el$150 = _tmpl$32(), _el$152 = _el$150.firstChild.nextSibling;\n insert(_el$152, createComponent(Show, {\n get when() {\n return props.runs.length > 0;\n },\n get fallback() {\n return _tmpl$33();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.runs.slice(0, 6);\n },\n children: (run) => (() => {\n var _el$154 = _tmpl$36(), _el$155 = _el$154.firstChild, _el$156 = _el$155.firstChild, _el$157 = _el$156.firstChild, _el$158 = _el$157.nextSibling, _el$159 = _el$156.nextSibling, _el$160 = _el$155.nextSibling, _el$161 = _el$160.nextSibling, _el$165 = _el$161.nextSibling;\n insert(_el$157, () => run.actionName);\n insert(_el$158, () => `${run.ruleName} • ${run.id}`);\n insert(_el$159, () => run.status);\n insert(_el$160, () => `${run.title} • ${run.eventKind}`);\n insert(_el$161, () => `Started: ${run.startedAt.slice(0, 19)}`);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.prompt;\n },\n get children() {\n var _el$162 = _tmpl$29();\n insert(_el$162, () => run.prompt);\n return _el$162;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.result;\n },\n get children() {\n var _el$163 = _tmpl$29();\n insert(_el$163, () => run.result);\n return _el$163;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.error;\n },\n get children() {\n var _el$164 = _tmpl$29();\n insert(_el$164, () => run.error);\n return _el$164;\n }\n }), _el$165);\n insert(_el$165, createComponent(Show, {\n get when() {\n return run.status === \"pending\";\n },\n get children() {\n return [(() => {\n var _el$166 = _tmpl$34();\n _el$166.$$click = () => {\n props.onApprove(run.id);\n };\n return _el$166;\n })(), (() => {\n var _el$167 = _tmpl$35();\n _el$167.$$click = () => {\n props.onReject(run.id);\n };\n return _el$167;\n })()];\n }\n }));\n createRenderEffect(() => setAttribute(_el$159, \"data-status\", run.status));\n return _el$154;\n })()\n });\n }\n }));\n return _el$150;\n })();\n}\nfunction Workspace(props) {\n const parsedTab = () => parseWorkspaceTab(props.tab);\n const details = () => {\n if (!props.selectedMeeting) return null;\n return workspaceBody(props.bundle, props.selectedMeeting, parsedTab());\n };\n return [(() => {\n var _el$168 = _tmpl$37(), _el$169 = _el$168.firstChild;\n insert(_el$168, createComponent(For, {\n each: [\n \"notes\",\n \"transcript\",\n \"metadata\",\n \"raw\"\n ],\n children: (tab) => (() => {\n var _el$170 = _tmpl$38();\n _el$170.$$click = () => {\n props.onSelectTab(tab);\n };\n insert(_el$170, tab === \"notes\" ? \"Notes\" : tab === \"transcript\" ? \"Transcript\" : tab === \"metadata\" ? \"Metadata\" : \"Raw\");\n createRenderEffect(() => setAttribute(_el$170, \"data-selected\", parsedTab() === tab ? \"true\" : void 0));\n return _el$170;\n })()\n }), _el$169);\n return _el$168;\n })(), createComponent(Show, {\n get when() {\n return props.selectedMeeting;\n },\n get fallback() {\n return (() => {\n var _el$171 = _tmpl$39();\n insert(_el$171, () => props.detailError || \"Select a meeting to inspect its notes and transcript.\");\n return _el$171;\n })();\n },\n children: (meeting) => [(() => {\n var _el$172 = _tmpl$40(), _el$173 = _el$172.firstChild, _el$174 = _el$173.nextSibling, _el$175 = _el$174.nextSibling;\n insert(_el$173, () => `ID: ${meeting().meeting.id}`);\n insert(_el$174, () => `Source: ${meeting().meeting.noteContentSource}`);\n insert(_el$175, () => `Transcript: ${meeting().meeting.transcriptSegmentCount} segments`);\n return _el$172;\n })(), createComponent(Show, {\n get when() {\n return !props.detailError;\n },\n get fallback() {\n return (() => {\n var _el$184 = _tmpl$39();\n insert(_el$184, () => props.detailError);\n return _el$184;\n })();\n },\n get children() {\n var _el$176 = _tmpl$41(), _el$178 = _el$176.firstChild.firstChild, _el$180 = _el$178.firstChild.nextSibling, _el$182 = _el$178.nextSibling.firstChild, _el$183 = _el$182.nextSibling;\n insert(_el$180, () => metadataLines(meeting()));\n insert(_el$182, () => details()?.title);\n insert(_el$183, () => details()?.body);\n return _el$176;\n }\n })]\n })];\n}\ndelegateEvents([\n \"input\",\n \"keydown\",\n \"click\"\n]);\n//#endregion\n//#region src/web-app/App.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$ = /* @__PURE__ */ template(`<div class=shell><aside class=\"pane sidebar\"></aside><main class=\"pane detail\"><section class=toolbar><div class=toolbar-actions><button class=\"button button--primary\"type=button>Sync now</button><button class=\"button button--secondary\"type=button>Clear Filters</button><button class=\"button button--secondary\"type=button>Export Notes</button><button class=\"button button--secondary\"type=button>Export Transcripts</button></div><p>Solid-powered web workspace on top of the same local server, sync loop, and shared app contracts.`);\nfunction browserConfig() {\n return { passwordRequired: Boolean(window.__GRANOLA_SERVER__?.passwordRequired) };\n}\nasync function requestJson(path, init) {\n const response = await fetch(path, init);\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const error = typeof payload.error === \"string\" && payload.error.trim() ? payload.error : response.statusText || \"Request failed\";\n throw new Error(error);\n }\n return payload;\n}\nfunction App() {\n const startup = startupSelectionFromSearch(window.location.search);\n const initialPreferences = parseWorkspacePreferences(window.localStorage.getItem(granolaWebWorkspaceStorageKey));\n const [state, setState] = createStore({\n apiKeyDraft: \"\",\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n listError: \"\",\n meetingSource: \"live\",\n meetings: [],\n quickOpen: \"\",\n recentMeetings: initialPreferences.recentMeetings,\n savedFilters: initialPreferences.savedFilters,\n search: \"\",\n selectedFolderId: startup.folderId || null,\n selectedMeetingBundle: null,\n selectedMeetingId: startup.meetingId || null,\n selectedMeeting: null,\n serverLocked: browserConfig().passwordRequired,\n serverPassword: \"\",\n sort: \"updated-desc\",\n statusLabel: browserConfig().passwordRequired ? \"Server locked\" : \"Connecting…\",\n statusTone: browserConfig().passwordRequired ? \"error\" : \"idle\",\n updatedFrom: \"\",\n updatedTo: \"\",\n workspaceTab: parseWorkspaceTab(startup.workspaceTab)\n });\n let client = null;\n let unsubscribe;\n const setStatus = (label, tone = \"idle\") => {\n setState({\n statusLabel: label,\n statusTone: tone\n });\n };\n const updatePreferences = (updater) => {\n const next = updater({\n recentMeetings: state.recentMeetings,\n savedFilters: state.savedFilters\n });\n window.localStorage.setItem(granolaWebWorkspaceStorageKey, serialiseWorkspacePreferences(next));\n setState(\"recentMeetings\", next.recentMeetings);\n setState(\"savedFilters\", next.savedFilters);\n };\n const mergeAuthState = async (authState) => {\n if (!client) return;\n const nextState = client.getState();\n if (authState) {\n setState(\"appState\", {\n ...nextState,\n auth: authState\n });\n return;\n }\n try {\n setState(\"appState\", {\n ...nextState,\n auth: await client.inspectAuth()\n });\n } catch {\n setState(\"appState\", nextState);\n }\n };\n const detachClient = async () => {\n unsubscribe?.();\n unsubscribe = void 0;\n if (client) {\n await client.close().catch(() => void 0);\n client = null;\n }\n };\n const attachClient = async () => {\n await detachClient();\n client = await createGranolaServerClient(window.location.origin);\n setState(\"appState\", client.getState());\n unsubscribe = client.subscribe((event) => {\n setState(\"appState\", event.state);\n });\n await mergeAuthState();\n };\n const loadFolders = async (refresh = false) => {\n if (!client) return;\n try {\n setState(\"folderError\", \"\");\n const result = await client.listFolders({\n forceRefresh: refresh,\n limit: 100\n });\n setState(\"folders\", result.folders);\n if (state.selectedFolderId && !result.folders.some((folder) => folder.id === state.selectedFolderId)) setState(\"selectedFolderId\", null);\n } catch (error) {\n setState(\"folderError\", error instanceof Error ? error.message : String(error));\n setState(\"folders\", []);\n setState(\"selectedFolderId\", null);\n }\n };\n const loadAutomationRuns = async () => {\n if (!client) return;\n try {\n setState(\"automationRuns\", (await client.listAutomationRuns({ limit: 20 })).runs);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadMeeting = async (meetingId) => {\n if (!client) return;\n setState(\"selectedMeetingId\", meetingId);\n try {\n setState(\"detailError\", \"\");\n const bundle = await client.getMeeting(meetingId);\n setState(\"selectedMeetingBundle\", bundle);\n setState(\"selectedMeeting\", bundle.meeting);\n updatePreferences((preferences) => rememberRecentMeeting(preferences, bundle.meeting.meeting));\n } catch (error) {\n setState(\"selectedMeetingBundle\", null);\n setState(\"selectedMeeting\", null);\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadMeetings = async (options = {}) => {\n if (!client) return;\n try {\n setState(\"listError\", \"\");\n const result = await client.listMeetings({\n folderId: state.selectedFolderId || void 0,\n forceRefresh: options.refresh,\n limit: 100,\n search: state.search || void 0,\n sort: state.sort,\n updatedFrom: state.updatedFrom || void 0,\n updatedTo: state.updatedTo || void 0\n });\n const preferredMeetingId = options.preferredMeetingId ?? state.selectedMeetingId;\n const nextMeetingId = selectMeetingId(result.meetings, preferredMeetingId);\n setState(\"meetings\", result.meetings);\n setState(\"meetingSource\", result.source);\n setState(\"selectedMeetingId\", nextMeetingId);\n if (nextMeetingId) await loadMeeting(nextMeetingId);\n else {\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", \"\");\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n setState(\"listError\", message);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", message);\n }\n };\n const refreshAll = async (forceRefresh = false) => {\n if (!client) await attachClient();\n setStatus(forceRefresh ? \"Syncing…\" : \"Refreshing…\", \"busy\");\n if (forceRefresh) await client?.sync({\n forceRefresh: true,\n foreground: true\n });\n await Promise.all([\n loadFolders(forceRefresh),\n loadAutomationRuns(),\n mergeAuthState()\n ]);\n await loadMeetings({ refresh: forceRefresh });\n setState(\"serverLocked\", false);\n setStatus(forceRefresh ? \"Sync complete\" : state.meetingSource === \"index\" ? \"Loaded from index\" : \"Connected\", \"ok\");\n };\n const connectAndRefresh = async (forceRefresh = false) => {\n try {\n await refreshAll(forceRefresh);\n } catch (error) {\n setStatus(\"Connection failed\", \"error\");\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const quickOpenMeeting = async () => {\n if (!client) return;\n const query = state.quickOpen.trim();\n if (!query) {\n setStatus(\"Enter a title or id\", \"error\");\n return;\n }\n setStatus(\"Opening meeting…\", \"busy\");\n try {\n const bundle = await client.findMeeting(query);\n setState(\"selectedFolderId\", bundle.meeting.meeting.folders[0]?.id || null);\n setState(\"search\", \"\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n await loadMeetings({ preferredMeetingId: bundle.document.id });\n setStatus(\"Connected\", \"ok\");\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Quick open failed\", \"error\");\n }\n };\n const saveApiKey = async () => {\n if (!client) return;\n if (!state.apiKeyDraft.trim()) {\n setStatus(\"Enter a Granola API key\", \"error\");\n return;\n }\n setStatus(\"Saving API key…\", \"busy\");\n try {\n const auth = await client.loginAuth({ apiKey: state.apiKeyDraft.trim() });\n setState(\"apiKeyDraft\", \"\");\n await mergeAuthState(auth);\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"API key save failed\", \"error\");\n }\n };\n const importDesktopSession = async () => {\n if (!client) return;\n setStatus(\"Importing desktop session…\", \"busy\");\n try {\n await mergeAuthState(await client.loginAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Auth import failed\", \"error\");\n }\n };\n const refreshAuth = async () => {\n if (!client) return;\n setStatus(\"Refreshing session…\", \"busy\");\n try {\n await mergeAuthState(await client.refreshAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Refresh failed\", \"error\");\n }\n };\n const switchAuthMode = async (mode) => {\n if (!client) return;\n setStatus(\"Switching auth source…\", \"busy\");\n try {\n await mergeAuthState(await client.switchAuthMode(mode));\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Switch failed\", \"error\");\n }\n };\n const logout = async () => {\n if (!client) return;\n setStatus(\"Signing out…\", \"busy\");\n try {\n await mergeAuthState(await client.logoutAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Sign out failed\", \"error\");\n }\n };\n const exportNotes = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder notes…\" : \"Exporting notes…\", \"busy\");\n try {\n await client.exportNotes(\"markdown\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const clearFilters = async () => {\n setState(\"search\", \"\");\n setState(\"sort\", \"updated-desc\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n setState(\"selectedFolderId\", null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(\"Filters cleared\", \"ok\");\n };\n const saveCurrentFilter = () => {\n updatePreferences((preferences) => saveWorkspaceFilter(preferences, {\n folders: state.folders,\n search: state.search,\n selectedFolderId: state.selectedFolderId,\n sort: state.sort,\n updatedFrom: state.updatedFrom,\n updatedTo: state.updatedTo\n }, { idFactory: () => `filter-${Date.now()}` }));\n setStatus(\"Saved filter\", \"ok\");\n };\n const applySavedFilterPreset = async (id) => {\n const preset = state.savedFilters.find((candidate) => candidate.id === id);\n if (!preset) return;\n const nextFilters = applyWorkspaceFilter(preset);\n setState(\"search\", nextFilters.search);\n setState(\"selectedFolderId\", nextFilters.selectedFolderId);\n setState(\"sort\", nextFilters.sort);\n setState(\"updatedFrom\", nextFilters.updatedFrom);\n setState(\"updatedTo\", nextFilters.updatedTo);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(`Applied ${preset.label}`, \"ok\");\n };\n const removeSavedFilterPreset = (id) => {\n updatePreferences((preferences) => removeWorkspaceFilter(preferences, id));\n setStatus(\"Removed saved filter\", \"ok\");\n };\n const openRecentMeeting = async (meetingId, folderId) => {\n if (folderId !== void 0) {\n setState(\"selectedFolderId\", folderId || null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings({ preferredMeetingId: meetingId });\n return;\n }\n await loadMeeting(meetingId);\n };\n const meetingEmptyHint = () => {\n if (!state.appState) return \"Connect to the local server to load meetings.\";\n if (state.appState.auth.lastError) return \"Resolve auth first, then sync again.\";\n if (!state.appState.documents.loaded && !state.appState.sync.lastCompletedAt) return \"Run Sync now to populate your local meeting index.\";\n return \"Try a different folder or filter, or sync again.\";\n };\n const exportTranscripts = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder transcripts…\" : \"Exporting transcripts…\", \"busy\");\n try {\n await client.exportTranscripts(\"text\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const rerunJob = async (jobId) => {\n if (!client) return;\n setStatus(\"Rerunning export…\", \"busy\");\n try {\n await client.rerunExportJob(jobId);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Rerun failed\", \"error\");\n }\n };\n const resolveAutomationRun = async (id, decision) => {\n if (!client) return;\n setStatus(decision === \"approve\" ? \"Approving automation…\" : \"Rejecting automation…\", \"busy\");\n try {\n await client.resolveAutomationRun(id, decision);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Automation decision failed\", \"error\");\n }\n };\n const unlockServer = async () => {\n if (!state.serverPassword.trim()) {\n setStatus(\"Enter the server password\", \"error\");\n return;\n }\n setStatus(\"Unlocking server…\", \"busy\");\n try {\n await requestJson(\"/auth/unlock\", {\n body: JSON.stringify({ password: state.serverPassword }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n setState(\"serverPassword\", \"\");\n setState(\"serverLocked\", false);\n await connectAndRefresh(true);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unlock failed\", \"error\");\n }\n };\n const lockServer = async () => {\n try {\n await requestJson(\"/auth/lock\", { method: \"POST\" });\n } catch {}\n await detachClient();\n setState({\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n listError: \"\",\n meetings: [],\n selectedFolderId: null,\n selectedMeeting: null,\n selectedMeetingBundle: null,\n selectedMeetingId: null,\n serverLocked: true,\n serverPassword: \"\"\n });\n setStatus(\"Server locked\", \"error\");\n };\n createEffect(() => {\n const nextPath = buildBrowserUrlPath(window.location.href, {\n selectedFolderId: state.selectedFolderId,\n selectedMeetingId: state.selectedMeetingId,\n workspaceTab: state.workspaceTab\n });\n if (nextPath !== `${window.location.pathname}${window.location.search}${window.location.hash}`) history.replaceState(null, \"\", nextPath);\n });\n createEffect(() => {\n if (!state.appState?.automation.loaded || !client) return;\n loadAutomationRuns();\n });\n onMount(() => {\n const onKeyDown = (event) => {\n const target = event.target;\n if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return;\n const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);\n if (nextTab) setState(\"workspaceTab\", nextTab);\n };\n document.addEventListener(\"keydown\", onKeyDown);\n onCleanup(() => {\n document.removeEventListener(\"keydown\", onKeyDown);\n });\n if (!state.serverLocked) connectAndRefresh();\n });\n onCleanup(() => {\n detachClient();\n });\n return (() => {\n var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$6 = _el$4.firstChild.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling;\n insert(_el$2, createComponent(ToolbarFilters, {\n onQuickOpen: () => {\n quickOpenMeeting();\n },\n onQuickOpenInput: (value) => {\n setState(\"quickOpen\", value);\n },\n onSearchInput: (value) => {\n setState(\"search\", value.trim());\n loadMeetings();\n },\n onSortChange: (value) => {\n setState(\"sort\", value);\n loadMeetings();\n },\n onUpdatedFromChange: (value) => {\n setState(\"updatedFrom\", value);\n loadMeetings();\n },\n onUpdatedToChange: (value) => {\n setState(\"updatedTo\", value);\n loadMeetings();\n },\n get quickOpen() {\n return state.quickOpen;\n },\n get search() {\n return state.search;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(SavedFiltersPanel, {\n get folders() {\n return state.folders;\n },\n onApply: (preset) => {\n applySavedFilterPreset(preset.id);\n },\n onRemove: removeSavedFilterPreset,\n onSaveCurrent: saveCurrentFilter,\n get savedFilters() {\n return state.savedFilters;\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(RecentMeetingsPanel, {\n onOpen: (meeting) => {\n openRecentMeeting(meeting.id, meeting.folderId);\n },\n get recentMeetings() {\n return state.recentMeetings;\n }\n }), null);\n insert(_el$2, createComponent(FolderList, {\n get error() {\n return state.folderError;\n },\n get folders() {\n return state.folders;\n },\n onSelect: (folderId) => {\n setState(\"selectedFolderId\", folderId);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n loadMeetings();\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n }\n }), null);\n insert(_el$2, createComponent(MeetingList, {\n get error() {\n return state.listError;\n },\n get emptyHint() {\n return meetingEmptyHint();\n },\n get folders() {\n return state.folders;\n },\n get meetings() {\n return state.meetings;\n },\n onSelect: (meetingId) => {\n loadMeeting(meetingId);\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get selectedMeetingId() {\n return state.selectedMeetingId;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$3, createComponent(AppStatePanel, {\n get appState() {\n return state.appState;\n },\n get statusLabel() {\n return state.statusLabel;\n },\n get statusTone() {\n return state.statusTone;\n }\n }), _el$4);\n _el$6.$$click = () => {\n connectAndRefresh(true);\n };\n _el$7.$$click = () => {\n clearFilters();\n };\n _el$8.$$click = () => {\n exportNotes();\n };\n _el$9.$$click = () => {\n exportTranscripts();\n };\n insert(_el$3, createComponent(SecurityPanel, {\n onLock: () => {\n lockServer();\n },\n onPasswordChange: (value) => {\n setState(\"serverPassword\", value);\n },\n onUnlock: () => {\n unlockServer();\n },\n get password() {\n return state.serverPassword;\n },\n get visible() {\n return state.serverLocked;\n }\n }), null);\n insert(_el$3, createComponent(AuthPanel, {\n get apiKeyDraft() {\n return state.apiKeyDraft;\n },\n get auth() {\n return state.appState?.auth;\n },\n onApiKeyDraftChange: (value) => {\n setState(\"apiKeyDraft\", value);\n },\n onImportDesktopSession: () => {\n importDesktopSession();\n },\n onLogout: () => {\n logout();\n },\n onRefresh: () => {\n refreshAuth();\n },\n onSaveApiKey: () => {\n saveApiKey();\n },\n onSwitchMode: (mode) => {\n switchAuthMode(mode);\n }\n }), null);\n insert(_el$3, createComponent(ExportJobsPanel, {\n get jobs() {\n return state.appState?.exports.jobs || [];\n },\n onRerun: (jobId) => {\n rerunJob(jobId);\n }\n }), null);\n insert(_el$3, createComponent(AutomationRunsPanel, {\n onApprove: (runId) => {\n resolveAutomationRun(runId, \"approve\");\n },\n onReject: (runId) => {\n resolveAutomationRun(runId, \"reject\");\n },\n get runs() {\n return state.automationRuns;\n }\n }), null);\n insert(_el$3, createComponent(Workspace, {\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get detailError() {\n return state.detailError;\n },\n onSelectTab: (tab) => {\n setState(\"workspaceTab\", tab);\n },\n get selectedMeeting() {\n return state.selectedMeeting;\n },\n get tab() {\n return state.workspaceTab;\n }\n }), null);\n return _el$;\n })();\n}\ndelegateEvents([\"click\"]);\n//#endregion\n//#region src/web-app/main.tsx\n/** @jsxImportSource solid-js */\nvar root = document.getElementById(\"granola-web-root\");\nif (!root) throw new Error(\"Granola web root element not found\");\nrender(() => createComponent(App, {}), root);\n//#endregion\n";
|
|
7772
|
+
const granolaWebClientCss = ":root {\n --bg: #f2ede2;\n --panel: rgba(255, 252, 247, 0.86);\n --panel-strong: #fffaf2;\n --line: rgba(36, 39, 44, 0.12);\n --ink: #1d242c;\n --muted: #5d6b77;\n --accent: #0d6a6d;\n --accent-soft: rgba(13, 106, 109, 0.12);\n --warm: #a34f2f;\n --ok: #246b4f;\n --error: #9d2c2c;\n --shadow: 0 24px 80px rgba(40, 32, 16, 0.12);\n --radius: 24px;\n --mono: \"SF Mono\", \"IBM Plex Mono\", \"Cascadia Code\", monospace;\n --serif: \"Iowan Old Style\", \"Palatino Linotype\", \"Book Antiqua\", Georgia, serif;\n --sans: \"Avenir Next\", \"Segoe UI\", sans-serif;\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n min-height: 100vh;\n font-family: var(--sans);\n color: var(--ink);\n background:\n radial-gradient(circle at top left, rgba(163, 79, 47, 0.18), transparent 32%),\n radial-gradient(circle at right 12%, rgba(13, 106, 109, 0.16), transparent 28%),\n linear-gradient(180deg, #f8f2e8 0%, var(--bg) 100%);\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\n#granola-web-root {\n min-height: 100vh;\n}\n\n.shell {\n display: grid;\n grid-template-columns: 320px minmax(0, 1fr);\n gap: 18px;\n min-height: 100vh;\n padding: 24px;\n}\n\n.pane {\n background: var(--panel);\n backdrop-filter: blur(18px);\n border: 1px solid var(--line);\n border-radius: var(--radius);\n box-shadow: var(--shadow);\n}\n\n.sidebar {\n display: grid;\n grid-template-rows: auto auto auto auto 1fr;\n overflow: hidden;\n}\n\n.hero,\n.toolbar,\n.detail-head,\n.folder-panel {\n padding: 22px 24px;\n border-bottom: 1px solid var(--line);\n}\n\n.hero h1 {\n margin: 0;\n font-family: var(--serif);\n font-size: clamp(2rem, 3vw, 2.8rem);\n font-weight: 600;\n letter-spacing: -0.04em;\n}\n\n.hero p,\n.toolbar p {\n margin: 8px 0 0;\n color: var(--muted);\n line-height: 1.5;\n}\n\n.search,\n.select,\n.field-input,\n.input {\n width: 100%;\n margin-top: 16px;\n padding: 12px 14px;\n border: 1px solid var(--line);\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.7);\n color: var(--ink);\n}\n\n.field-row {\n display: grid;\n gap: 10px;\n margin-top: 12px;\n}\n\n.field-row--inline {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.field-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.folder-panel {\n display: grid;\n gap: 14px;\n}\n\n.folder-panel__head h2 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.folder-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.folder-list,\n.jobs-list,\n.saved-filter-list {\n display: grid;\n gap: 10px;\n}\n\n.saved-filter-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n\n.folder-row,\n.meeting-row,\n.saved-filter-card__main {\n width: 100%;\n display: grid;\n gap: 4px;\n text-align: left;\n padding: 12px 14px;\n border: 1px solid transparent;\n border-radius: 16px;\n background: rgba(255, 255, 255, 0.72);\n color: inherit;\n cursor: pointer;\n transition:\n transform 140ms ease,\n border-color 140ms ease,\n background 140ms ease;\n}\n\n.meeting-row {\n margin: 0 0 10px;\n padding: 14px 16px;\n border-radius: 18px;\n}\n\n.saved-filter-card {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px;\n align-items: start;\n}\n\n.saved-filter-card__main {\n margin: 0;\n}\n\n.saved-filter-card__remove {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 12px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n cursor: pointer;\n font-weight: 700;\n}\n\n.folder-row:hover,\n.folder-row[data-selected=\"true\"],\n.saved-filter-card__main:hover {\n transform: translateY(-1px);\n border-color: rgba(163, 79, 47, 0.26);\n background: var(--panel-strong);\n}\n\n.meeting-row:hover,\n.meeting-row[data-selected=\"true\"] {\n transform: translateY(-1px);\n border-color: rgba(13, 106, 109, 0.25);\n background: var(--panel-strong);\n}\n\n.folder-row__title,\n.job-card__title,\n.saved-filter-card__title {\n font-weight: 700;\n}\n\n.meeting-row__title {\n font-weight: 600;\n}\n\n.folder-row__meta,\n.meeting-row__meta,\n.auth-card__meta,\n.job-card__meta,\n.folder-empty,\n.job-empty,\n.meeting-empty,\n.saved-filter-card__meta {\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.folder-empty--error,\n.meeting-empty--error,\n.auth-card__error {\n color: var(--error);\n}\n\n.meeting-list {\n padding: 14px;\n overflow: auto;\n}\n\n.detail {\n display: grid;\n grid-template-rows: auto auto 1fr;\n min-width: 0;\n}\n\n.detail-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 18px;\n}\n\n.detail-head h2 {\n margin: 0;\n font-family: var(--serif);\n font-size: clamp(1.8rem, 2.4vw, 2.4rem);\n font-weight: 600;\n}\n\n.state-badge {\n padding: 10px 14px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.92rem;\n font-weight: 700;\n}\n\n.state-badge[data-tone=\"busy\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.state-badge[data-tone=\"error\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.state-badge[data-tone=\"ok\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 14px;\n}\n\n.toolbar-actions,\n.auth-card__actions,\n.job-card__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n\n.toolbar-form {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px;\n width: min(440px, 100%);\n}\n\n.security-panel,\n.auth-panel,\n.jobs-panel {\n padding: 0 24px 18px;\n}\n\n.security-panel__head h3,\n.auth-panel__head h3,\n.jobs-panel__head h3 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.security-panel__head p,\n.auth-panel__head p,\n.jobs-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.security-panel__body,\n.auth-panel__body {\n display: grid;\n gap: 12px;\n margin-top: 14px;\n}\n\n.auth-card,\n.job-card {\n display: grid;\n gap: 12px;\n padding: 14px 16px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.72);\n}\n\n.job-card--button {\n width: 100%;\n text-align: left;\n cursor: pointer;\n}\n\n.job-card--button[data-selected=\"true\"] {\n border-color: rgba(13, 106, 109, 0.26);\n background: var(--panel-strong);\n}\n\n.job-card__head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n}\n\n.job-card__status {\n padding: 6px 10px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.82rem;\n font-weight: 700;\n}\n\n.job-card__status[data-status=\"running\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"failed\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"completed\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.job-card__status[data-status=\"approved\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.job-card__status[data-status=\"generated\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"rejected\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"superseded\"] {\n background: rgba(93, 107, 119, 0.12);\n color: var(--muted);\n}\n\n.workspace-tabs {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.workspace-tab,\n.button {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 14px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--ink);\n cursor: pointer;\n font-weight: 700;\n}\n\n.button {\n padding: 12px 16px;\n}\n\n.workspace-tab[data-selected=\"true\"],\n.button--primary {\n background: var(--ink);\n color: #fff;\n border-color: var(--ink);\n}\n\n.button--secondary {\n background: rgba(255, 255, 255, 0.72);\n}\n\n.button:disabled {\n cursor: not-allowed;\n opacity: 0.56;\n}\n\n.workspace-hint {\n margin-left: auto;\n color: var(--muted);\n font-size: 0.86rem;\n}\n\n.status-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 14px;\n}\n\n.status-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.detail-chip {\n padding: 10px 12px;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n color: var(--muted);\n font-size: 0.88rem;\n}\n\n.detail-body {\n padding: 0 24px 24px;\n overflow: auto;\n}\n\n.review-panel {\n padding: 0 24px 24px;\n}\n\n.review-body {\n display: grid;\n gap: 18px;\n}\n\n.review-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 18px;\n}\n\n.workspace-grid {\n display: grid;\n grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);\n gap: 18px;\n}\n\n.detail-section {\n margin-bottom: 20px;\n padding: 20px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n border-radius: 20px;\n}\n\n.detail-section h2 {\n margin: 0 0 14px;\n font-size: 1rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--mono);\n line-height: 1.55;\n}\n\n.field-input--plain,\n.review-textarea {\n width: 100%;\n margin-top: 0;\n padding: 12px 14px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.92);\n color: var(--ink);\n}\n\n.review-textarea {\n min-height: 220px;\n resize: vertical;\n}\n\n.review-textarea--summary {\n min-height: 96px;\n}\n\n.review-history {\n margin-bottom: 0;\n}\n\n.empty {\n margin: 24px;\n padding: 24px;\n border-radius: 20px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n}\n\n@media (max-width: 1024px) {\n .shell,\n .review-grid,\n .workspace-grid {\n grid-template-columns: 1fr;\n }\n}\n/*$vite$:1*/";
|
|
7773
|
+
const granolaWebClientJs = "//#region node_modules/solid-js/dist/solid.js\nvar sharedConfig = {\n context: void 0,\n registry: void 0,\n effects: void 0,\n done: false,\n getContextId() {\n return getContextId(this.context.count);\n },\n getNextContextId() {\n return getContextId(this.context.count++);\n }\n};\nfunction getContextId(count) {\n const num = String(count), len = num.length - 1;\n return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : \"\") + num;\n}\nfunction setHydrateContext(context) {\n sharedConfig.context = context;\n}\nfunction nextHydrateContext() {\n return {\n ...sharedConfig.context,\n id: sharedConfig.getNextContextId(),\n count: 0\n };\n}\nvar equalFn = (a, b) => a === b;\nvar $PROXY = Symbol(\"solid-proxy\");\nvar $TRACK = Symbol(\"solid-track\");\nvar signalOptions = { equals: equalFn };\nvar ERROR = null;\nvar runEffects = runQueue;\nvar STALE = 1;\nvar PENDING = 2;\nvar UNOWNED = {\n owned: null,\n cleanups: null,\n context: null,\n owner: null\n};\nvar Owner = null;\nvar Transition = null;\nvar Scheduler = null;\nvar ExternalSourceConfig = null;\nvar Listener = null;\nvar Updates = null;\nvar Effects = null;\nvar ExecCount = 0;\nfunction createRoot(fn, detachedOwner) {\n const listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === void 0 ? owner : detachedOwner, root = unowned ? UNOWNED : {\n owned: null,\n cleanups: null,\n context: current ? current.context : null,\n owner: current\n }, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));\n Owner = root;\n Listener = null;\n try {\n return runUpdates(updateFn, true);\n } finally {\n Listener = listener;\n Owner = owner;\n }\n}\nfunction createSignal(value, options) {\n options = options ? Object.assign({}, signalOptions, options) : signalOptions;\n const s = {\n value,\n observers: null,\n observerSlots: null,\n comparator: options.equals || void 0\n };\n const setter = (value) => {\n if (typeof value === \"function\") if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);\n else value = value(s.value);\n return writeSignal(s, value);\n };\n return [readSignal.bind(s), setter];\n}\nfunction createRenderEffect(fn, value, options) {\n const c = createComputation(fn, value, false, STALE);\n if (Scheduler && Transition && Transition.running) Updates.push(c);\n else updateComputation(c);\n}\nfunction createEffect(fn, value, options) {\n runEffects = runUserEffects;\n const c = createComputation(fn, value, false, STALE), s = SuspenseContext && useContext(SuspenseContext);\n if (s) c.suspense = s;\n if (!options || !options.render) c.user = true;\n Effects ? Effects.push(c) : updateComputation(c);\n}\nfunction createMemo(fn, value, options) {\n options = options ? Object.assign({}, signalOptions, options) : signalOptions;\n const c = createComputation(fn, value, true, 0);\n c.observers = null;\n c.observerSlots = null;\n c.comparator = options.equals || void 0;\n if (Scheduler && Transition && Transition.running) {\n c.tState = STALE;\n Updates.push(c);\n } else updateComputation(c);\n return readSignal.bind(c);\n}\nfunction batch(fn) {\n return runUpdates(fn, false);\n}\nfunction untrack(fn) {\n if (!ExternalSourceConfig && Listener === null) return fn();\n const listener = Listener;\n Listener = null;\n try {\n if (ExternalSourceConfig) return ExternalSourceConfig.untrack(fn);\n return fn();\n } finally {\n Listener = listener;\n }\n}\nfunction onMount(fn) {\n createEffect(() => untrack(fn));\n}\nfunction onCleanup(fn) {\n if (Owner === null);\n else if (Owner.cleanups === null) Owner.cleanups = [fn];\n else Owner.cleanups.push(fn);\n return fn;\n}\nfunction getListener() {\n return Listener;\n}\nfunction startTransition(fn) {\n if (Transition && Transition.running) {\n fn();\n return Transition.done;\n }\n const l = Listener;\n const o = Owner;\n return Promise.resolve().then(() => {\n Listener = l;\n Owner = o;\n let t;\n if (Scheduler || SuspenseContext) {\n t = Transition || (Transition = {\n sources: /* @__PURE__ */ new Set(),\n effects: [],\n promises: /* @__PURE__ */ new Set(),\n disposed: /* @__PURE__ */ new Set(),\n queue: /* @__PURE__ */ new Set(),\n running: true\n });\n t.done || (t.done = new Promise((res) => t.resolve = res));\n t.running = true;\n }\n runUpdates(fn, false);\n Listener = Owner = null;\n return t ? t.done : void 0;\n });\n}\nvar [transPending, setTransPending] = /* @__PURE__ */ createSignal(false);\nfunction useContext(context) {\n let value;\n return Owner && Owner.context && (value = Owner.context[context.id]) !== void 0 ? value : context.defaultValue;\n}\nvar SuspenseContext;\nfunction readSignal() {\n const runningTransition = Transition && Transition.running;\n if (this.sources && (runningTransition ? this.tState : this.state)) if ((runningTransition ? this.tState : this.state) === STALE) updateComputation(this);\n else {\n const updates = Updates;\n Updates = null;\n runUpdates(() => lookUpstream(this), false);\n Updates = updates;\n }\n if (Listener) {\n const sSlot = this.observers ? this.observers.length : 0;\n if (!Listener.sources) {\n Listener.sources = [this];\n Listener.sourceSlots = [sSlot];\n } else {\n Listener.sources.push(this);\n Listener.sourceSlots.push(sSlot);\n }\n if (!this.observers) {\n this.observers = [Listener];\n this.observerSlots = [Listener.sources.length - 1];\n } else {\n this.observers.push(Listener);\n this.observerSlots.push(Listener.sources.length - 1);\n }\n }\n if (runningTransition && Transition.sources.has(this)) return this.tValue;\n return this.value;\n}\nfunction writeSignal(node, value, isComp) {\n let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;\n if (!node.comparator || !node.comparator(current, value)) {\n if (Transition) {\n const TransitionRunning = Transition.running;\n if (TransitionRunning || !isComp && Transition.sources.has(node)) {\n Transition.sources.add(node);\n node.tValue = value;\n }\n if (!TransitionRunning) node.value = value;\n } else node.value = value;\n if (node.observers && node.observers.length) runUpdates(() => {\n for (let i = 0; i < node.observers.length; i += 1) {\n const o = node.observers[i];\n const TransitionRunning = Transition && Transition.running;\n if (TransitionRunning && Transition.disposed.has(o)) continue;\n if (TransitionRunning ? !o.tState : !o.state) {\n if (o.pure) Updates.push(o);\n else Effects.push(o);\n if (o.observers) markDownstream(o);\n }\n if (!TransitionRunning) o.state = STALE;\n else o.tState = STALE;\n }\n if (Updates.length > 1e6) {\n Updates = [];\n throw new Error();\n }\n }, false);\n }\n return value;\n}\nfunction updateComputation(node) {\n if (!node.fn) return;\n cleanNode(node);\n const time = ExecCount;\n runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time);\n if (Transition && !Transition.running && Transition.sources.has(node)) queueMicrotask(() => {\n runUpdates(() => {\n Transition && (Transition.running = true);\n Listener = Owner = node;\n runComputation(node, node.tValue, time);\n Listener = Owner = null;\n }, false);\n });\n}\nfunction runComputation(node, value, time) {\n let nextValue;\n const owner = Owner, listener = Listener;\n Listener = Owner = node;\n try {\n nextValue = node.fn(value);\n } catch (err) {\n if (node.pure) if (Transition && Transition.running) {\n node.tState = STALE;\n node.tOwned && node.tOwned.forEach(cleanNode);\n node.tOwned = void 0;\n } else {\n node.state = STALE;\n node.owned && node.owned.forEach(cleanNode);\n node.owned = null;\n }\n node.updatedAt = time + 1;\n return handleError(err);\n } finally {\n Listener = listener;\n Owner = owner;\n }\n if (!node.updatedAt || node.updatedAt <= time) {\n if (node.updatedAt != null && \"observers\" in node) writeSignal(node, nextValue, true);\n else if (Transition && Transition.running && node.pure) {\n if (!Transition.sources.has(node)) node.value = nextValue;\n Transition.sources.add(node);\n node.tValue = nextValue;\n } else node.value = nextValue;\n node.updatedAt = time;\n }\n}\nfunction createComputation(fn, init, pure, state = STALE, options) {\n const c = {\n fn,\n state,\n updatedAt: null,\n owned: null,\n sources: null,\n sourceSlots: null,\n cleanups: null,\n value: init,\n owner: Owner,\n context: Owner ? Owner.context : null,\n pure\n };\n if (Transition && Transition.running) {\n c.state = 0;\n c.tState = state;\n }\n if (Owner === null);\n else if (Owner !== UNOWNED) if (Transition && Transition.running && Owner.pure) if (!Owner.tOwned) Owner.tOwned = [c];\n else Owner.tOwned.push(c);\n else if (!Owner.owned) Owner.owned = [c];\n else Owner.owned.push(c);\n if (ExternalSourceConfig && c.fn) {\n const sourceFn = c.fn;\n const [track, trigger] = createSignal(void 0, { equals: false });\n const ordinary = ExternalSourceConfig.factory(sourceFn, trigger);\n onCleanup(() => ordinary.dispose());\n let inTransition;\n const triggerInTransition = () => startTransition(trigger).then(() => {\n if (inTransition) {\n inTransition.dispose();\n inTransition = void 0;\n }\n });\n c.fn = (x) => {\n track();\n if (Transition && Transition.running) {\n if (!inTransition) inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition);\n return inTransition.track(x);\n }\n return ordinary.track(x);\n };\n }\n return c;\n}\nfunction runTop(node) {\n const runningTransition = Transition && Transition.running;\n if ((runningTransition ? node.tState : node.state) === 0) return;\n if ((runningTransition ? node.tState : node.state) === PENDING) return lookUpstream(node);\n if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);\n const ancestors = [node];\n while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {\n if (runningTransition && Transition.disposed.has(node)) return;\n if (runningTransition ? node.tState : node.state) ancestors.push(node);\n }\n for (let i = ancestors.length - 1; i >= 0; i--) {\n node = ancestors[i];\n if (runningTransition) {\n let top = node, prev = ancestors[i + 1];\n while ((top = top.owner) && top !== prev) if (Transition.disposed.has(top)) return;\n }\n if ((runningTransition ? node.tState : node.state) === STALE) updateComputation(node);\n else if ((runningTransition ? node.tState : node.state) === PENDING) {\n const updates = Updates;\n Updates = null;\n runUpdates(() => lookUpstream(node, ancestors[0]), false);\n Updates = updates;\n }\n }\n}\nfunction runUpdates(fn, init) {\n if (Updates) return fn();\n let wait = false;\n if (!init) Updates = [];\n if (Effects) wait = true;\n else Effects = [];\n ExecCount++;\n try {\n const res = fn();\n completeUpdates(wait);\n return res;\n } catch (err) {\n if (!wait) Effects = null;\n Updates = null;\n handleError(err);\n }\n}\nfunction completeUpdates(wait) {\n if (Updates) {\n if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);\n else runQueue(Updates);\n Updates = null;\n }\n if (wait) return;\n let res;\n if (Transition) {\n if (!Transition.promises.size && !Transition.queue.size) {\n const sources = Transition.sources;\n const disposed = Transition.disposed;\n Effects.push.apply(Effects, Transition.effects);\n res = Transition.resolve;\n for (const e of Effects) {\n \"tState\" in e && (e.state = e.tState);\n delete e.tState;\n }\n Transition = null;\n runUpdates(() => {\n for (const d of disposed) cleanNode(d);\n for (const v of sources) {\n v.value = v.tValue;\n if (v.owned) for (let i = 0, len = v.owned.length; i < len; i++) cleanNode(v.owned[i]);\n if (v.tOwned) v.owned = v.tOwned;\n delete v.tValue;\n delete v.tOwned;\n v.tState = 0;\n }\n setTransPending(false);\n }, false);\n } else if (Transition.running) {\n Transition.running = false;\n Transition.effects.push.apply(Transition.effects, Effects);\n Effects = null;\n setTransPending(true);\n return;\n }\n }\n const e = Effects;\n Effects = null;\n if (e.length) runUpdates(() => runEffects(e), false);\n if (res) res();\n}\nfunction runQueue(queue) {\n for (let i = 0; i < queue.length; i++) runTop(queue[i]);\n}\nfunction scheduleQueue(queue) {\n for (let i = 0; i < queue.length; i++) {\n const item = queue[i];\n const tasks = Transition.queue;\n if (!tasks.has(item)) {\n tasks.add(item);\n Scheduler(() => {\n tasks.delete(item);\n runUpdates(() => {\n Transition.running = true;\n runTop(item);\n }, false);\n Transition && (Transition.running = false);\n });\n }\n }\n}\nfunction runUserEffects(queue) {\n let i, userLength = 0;\n for (i = 0; i < queue.length; i++) {\n const e = queue[i];\n if (!e.user) runTop(e);\n else queue[userLength++] = e;\n }\n if (sharedConfig.context) {\n if (sharedConfig.count) {\n sharedConfig.effects || (sharedConfig.effects = []);\n sharedConfig.effects.push(...queue.slice(0, userLength));\n return;\n }\n setHydrateContext();\n }\n if (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count)) {\n queue = [...sharedConfig.effects, ...queue];\n userLength += sharedConfig.effects.length;\n delete sharedConfig.effects;\n }\n for (i = 0; i < userLength; i++) runTop(queue[i]);\n}\nfunction lookUpstream(node, ignore) {\n const runningTransition = Transition && Transition.running;\n if (runningTransition) node.tState = 0;\n else node.state = 0;\n for (let i = 0; i < node.sources.length; i += 1) {\n const source = node.sources[i];\n if (source.sources) {\n const state = runningTransition ? source.tState : source.state;\n if (state === STALE) {\n if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source);\n } else if (state === PENDING) lookUpstream(source, ignore);\n }\n }\n}\nfunction markDownstream(node) {\n const runningTransition = Transition && Transition.running;\n for (let i = 0; i < node.observers.length; i += 1) {\n const o = node.observers[i];\n if (runningTransition ? !o.tState : !o.state) {\n if (runningTransition) o.tState = PENDING;\n else o.state = PENDING;\n if (o.pure) Updates.push(o);\n else Effects.push(o);\n o.observers && markDownstream(o);\n }\n }\n}\nfunction cleanNode(node) {\n let i;\n if (node.sources) while (node.sources.length) {\n const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;\n if (obs && obs.length) {\n const n = obs.pop(), s = source.observerSlots.pop();\n if (index < obs.length) {\n n.sourceSlots[s] = index;\n obs[index] = n;\n source.observerSlots[index] = s;\n }\n }\n }\n if (node.tOwned) {\n for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]);\n delete node.tOwned;\n }\n if (Transition && Transition.running && node.pure) reset(node, true);\n else if (node.owned) {\n for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);\n node.owned = null;\n }\n if (node.cleanups) {\n for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();\n node.cleanups = null;\n }\n if (Transition && Transition.running) node.tState = 0;\n else node.state = 0;\n}\nfunction reset(node, top) {\n if (!top) {\n node.tState = 0;\n Transition.disposed.add(node);\n }\n if (node.owned) for (let i = 0; i < node.owned.length; i++) reset(node.owned[i]);\n}\nfunction castError(err) {\n if (err instanceof Error) return err;\n return new Error(typeof err === \"string\" ? err : \"Unknown error\", { cause: err });\n}\nfunction runErrors(err, fns, owner) {\n try {\n for (const f of fns) f(err);\n } catch (e) {\n handleError(e, owner && owner.owner || null);\n }\n}\nfunction handleError(err, owner = Owner) {\n const fns = ERROR && owner && owner.context && owner.context[ERROR];\n const error = castError(err);\n if (!fns) throw error;\n if (Effects) Effects.push({\n fn() {\n runErrors(error, fns, owner);\n },\n state: STALE\n });\n else runErrors(error, fns, owner);\n}\nvar FALLBACK = Symbol(\"fallback\");\nfunction dispose(d) {\n for (let i = 0; i < d.length; i++) d[i]();\n}\nfunction mapArray(list, mapFn, options = {}) {\n let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null;\n onCleanup(() => dispose(disposers));\n return () => {\n let newItems = list() || [], newLen = newItems.length, i, j;\n newItems[$TRACK];\n return untrack(() => {\n let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;\n if (newLen === 0) {\n if (len !== 0) {\n dispose(disposers);\n disposers = [];\n items = [];\n mapped = [];\n len = 0;\n indexes && (indexes = []);\n }\n if (options.fallback) {\n items = [FALLBACK];\n mapped[0] = createRoot((disposer) => {\n disposers[0] = disposer;\n return options.fallback();\n });\n len = 1;\n }\n } else if (len === 0) {\n mapped = new Array(newLen);\n for (j = 0; j < newLen; j++) {\n items[j] = newItems[j];\n mapped[j] = createRoot(mapper);\n }\n len = newLen;\n } else {\n temp = new Array(newLen);\n tempdisposers = new Array(newLen);\n indexes && (tempIndexes = new Array(newLen));\n for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++);\n for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {\n temp[newEnd] = mapped[end];\n tempdisposers[newEnd] = disposers[end];\n indexes && (tempIndexes[newEnd] = indexes[end]);\n }\n newIndices = /* @__PURE__ */ new Map();\n newIndicesNext = new Array(newEnd + 1);\n for (j = newEnd; j >= start; j--) {\n item = newItems[j];\n i = newIndices.get(item);\n newIndicesNext[j] = i === void 0 ? -1 : i;\n newIndices.set(item, j);\n }\n for (i = start; i <= end; i++) {\n item = items[i];\n j = newIndices.get(item);\n if (j !== void 0 && j !== -1) {\n temp[j] = mapped[i];\n tempdisposers[j] = disposers[i];\n indexes && (tempIndexes[j] = indexes[i]);\n j = newIndicesNext[j];\n newIndices.set(item, j);\n } else disposers[i]();\n }\n for (j = start; j < newLen; j++) if (j in temp) {\n mapped[j] = temp[j];\n disposers[j] = tempdisposers[j];\n if (indexes) {\n indexes[j] = tempIndexes[j];\n indexes[j](j);\n }\n } else mapped[j] = createRoot(mapper);\n mapped = mapped.slice(0, len = newLen);\n items = newItems.slice(0);\n }\n return mapped;\n });\n function mapper(disposer) {\n disposers[j] = disposer;\n if (indexes) {\n const [s, set] = createSignal(j);\n indexes[j] = set;\n return mapFn(newItems[j], s);\n }\n return mapFn(newItems[j]);\n }\n };\n}\nvar hydrationEnabled = false;\nfunction createComponent(Comp, props) {\n if (hydrationEnabled) {\n if (sharedConfig.context) {\n const c = sharedConfig.context;\n setHydrateContext(nextHydrateContext());\n const r = untrack(() => Comp(props || {}));\n setHydrateContext(c);\n return r;\n }\n }\n return untrack(() => Comp(props || {}));\n}\nvar narrowedError = (name) => `Stale read from <${name}>.`;\nfunction For(props) {\n const fallback = \"fallback\" in props && { fallback: () => props.fallback };\n return createMemo(mapArray(() => props.each, props.children, fallback || void 0));\n}\nfunction Show(props) {\n const keyed = props.keyed;\n const conditionValue = createMemo(() => props.when, void 0, void 0);\n const condition = keyed ? conditionValue : createMemo(conditionValue, void 0, { equals: (a, b) => !a === !b });\n return createMemo(() => {\n const c = condition();\n if (c) {\n const child = props.children;\n return typeof child === \"function\" && child.length > 0 ? untrack(() => child(keyed ? c : () => {\n if (!untrack(condition)) throw narrowedError(\"Show\");\n return conditionValue();\n })) : child;\n }\n return props.fallback;\n }, void 0, void 0);\n}\n//#endregion\n//#region node_modules/solid-js/web/dist/web.js\nvar memo = (fn) => createMemo(() => fn());\nfunction reconcileArrays(parentNode, a, b) {\n let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null;\n while (aStart < aEnd || bStart < bEnd) {\n if (a[aStart] === b[bStart]) {\n aStart++;\n bStart++;\n continue;\n }\n while (a[aEnd - 1] === b[bEnd - 1]) {\n aEnd--;\n bEnd--;\n }\n if (aEnd === aStart) {\n const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;\n while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node);\n } else if (bEnd === bStart) while (aStart < aEnd) {\n if (!map || !map.has(a[aStart])) a[aStart].remove();\n aStart++;\n }\n else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {\n const node = a[--aEnd].nextSibling;\n parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling);\n parentNode.insertBefore(b[--bEnd], node);\n a[aEnd] = b[bEnd];\n } else {\n if (!map) {\n map = /* @__PURE__ */ new Map();\n let i = bStart;\n while (i < bEnd) map.set(b[i], i++);\n }\n const index = map.get(a[aStart]);\n if (index != null) if (bStart < index && index < bEnd) {\n let i = aStart, sequence = 1, t;\n while (++i < aEnd && i < bEnd) {\n if ((t = map.get(a[i])) == null || t !== index + sequence) break;\n sequence++;\n }\n if (sequence > index - bStart) {\n const node = a[aStart];\n while (bStart < index) parentNode.insertBefore(b[bStart++], node);\n } else parentNode.replaceChild(b[bStart++], a[aStart++]);\n } else aStart++;\n else a[aStart++].remove();\n }\n }\n}\nvar $$EVENTS = \"_$DX_DELEGATE\";\nfunction render(code, element, init, options = {}) {\n let disposer;\n createRoot((dispose) => {\n disposer = dispose;\n element === document ? code() : insert(element, code(), element.firstChild ? null : void 0, init);\n }, options.owner);\n return () => {\n disposer();\n element.textContent = \"\";\n };\n}\nfunction template(html, isImportNode, isSVG, isMathML) {\n let node;\n const create = () => {\n const t = isMathML ? document.createElementNS(\"http://www.w3.org/1998/Math/MathML\", \"template\") : document.createElement(\"template\");\n t.innerHTML = html;\n return isSVG ? t.content.firstChild.firstChild : isMathML ? t.firstChild : t.content.firstChild;\n };\n const fn = isImportNode ? () => untrack(() => document.importNode(node || (node = create()), true)) : () => (node || (node = create())).cloneNode(true);\n fn.cloneNode = fn;\n return fn;\n}\nfunction delegateEvents(eventNames, document = window.document) {\n const e = document[$$EVENTS] || (document[$$EVENTS] = /* @__PURE__ */ new Set());\n for (let i = 0, l = eventNames.length; i < l; i++) {\n const name = eventNames[i];\n if (!e.has(name)) {\n e.add(name);\n document.addEventListener(name, eventHandler);\n }\n }\n}\nfunction setAttribute(node, name, value) {\n if (isHydrating(node)) return;\n if (value == null) node.removeAttribute(name);\n else node.setAttribute(name, value);\n}\nfunction addEventListener(node, name, handler, delegate) {\n if (delegate) if (Array.isArray(handler)) {\n node[`$$${name}`] = handler[0];\n node[`$$${name}Data`] = handler[1];\n } else node[`$$${name}`] = handler;\n else if (Array.isArray(handler)) {\n const handlerFn = handler[0];\n node.addEventListener(name, handler[0] = (e) => handlerFn.call(node, handler[1], e));\n } else node.addEventListener(name, handler, typeof handler !== \"function\" && handler);\n}\nfunction insert(parent, accessor, marker, initial) {\n if (marker !== void 0 && !initial) initial = [];\n if (typeof accessor !== \"function\") return insertExpression(parent, accessor, initial, marker);\n createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);\n}\nfunction isHydrating(node) {\n return !!sharedConfig.context && !sharedConfig.done && (!node || node.isConnected);\n}\nfunction eventHandler(e) {\n if (sharedConfig.registry && sharedConfig.events) {\n if (sharedConfig.events.find(([el, ev]) => ev === e)) return;\n }\n let node = e.target;\n const key = `$$${e.type}`;\n const oriTarget = e.target;\n const oriCurrentTarget = e.currentTarget;\n const retarget = (value) => Object.defineProperty(e, \"target\", {\n configurable: true,\n value\n });\n const handleNode = () => {\n const handler = node[key];\n if (handler && !node.disabled) {\n const data = node[`${key}Data`];\n data !== void 0 ? handler.call(node, data, e) : handler.call(node, e);\n if (e.cancelBubble) return;\n }\n node.host && typeof node.host !== \"string\" && !node.host._$host && node.contains(e.target) && retarget(node.host);\n return true;\n };\n const walkUpTree = () => {\n while (handleNode() && (node = node._$host || node.parentNode || node.host));\n };\n Object.defineProperty(e, \"currentTarget\", {\n configurable: true,\n get() {\n return node || document;\n }\n });\n if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;\n if (e.composedPath) {\n const path = e.composedPath();\n retarget(path[0]);\n for (let i = 0; i < path.length - 2; i++) {\n node = path[i];\n if (!handleNode()) break;\n if (node._$host) {\n node = node._$host;\n walkUpTree();\n break;\n }\n if (node.parentNode === oriCurrentTarget) break;\n }\n } else walkUpTree();\n retarget(oriTarget);\n}\nfunction insertExpression(parent, value, current, marker, unwrapArray) {\n const hydrating = isHydrating(parent);\n if (hydrating) {\n !current && (current = [...parent.childNodes]);\n let cleaned = [];\n for (let i = 0; i < current.length; i++) {\n const node = current[i];\n if (node.nodeType === 8 && node.data.slice(0, 2) === \"!$\") node.remove();\n else cleaned.push(node);\n }\n current = cleaned;\n }\n while (typeof current === \"function\") current = current();\n if (value === current) return current;\n const t = typeof value, multi = marker !== void 0;\n parent = multi && current[0] && current[0].parentNode || parent;\n if (t === \"string\" || t === \"number\") {\n if (hydrating) return current;\n if (t === \"number\") {\n value = value.toString();\n if (value === current) return current;\n }\n if (multi) {\n let node = current[0];\n if (node && node.nodeType === 3) node.data !== value && (node.data = value);\n else node = document.createTextNode(value);\n current = cleanChildren(parent, current, marker, node);\n } else if (current !== \"\" && typeof current === \"string\") current = parent.firstChild.data = value;\n else current = parent.textContent = value;\n } else if (value == null || t === \"boolean\") {\n if (hydrating) return current;\n current = cleanChildren(parent, current, marker);\n } else if (t === \"function\") {\n createRenderEffect(() => {\n let v = value();\n while (typeof v === \"function\") v = v();\n current = insertExpression(parent, v, current, marker);\n });\n return () => current;\n } else if (Array.isArray(value)) {\n const array = [];\n const currentArray = current && Array.isArray(current);\n if (normalizeIncomingArray(array, value, current, unwrapArray)) {\n createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));\n return () => current;\n }\n if (hydrating) {\n if (!array.length) return current;\n if (marker === void 0) return current = [...parent.childNodes];\n let node = array[0];\n if (node.parentNode !== parent) return current;\n const nodes = [node];\n while ((node = node.nextSibling) !== marker) nodes.push(node);\n return current = nodes;\n }\n if (array.length === 0) {\n current = cleanChildren(parent, current, marker);\n if (multi) return current;\n } else if (currentArray) if (current.length === 0) appendNodes(parent, array, marker);\n else reconcileArrays(parent, current, array);\n else {\n current && cleanChildren(parent);\n appendNodes(parent, array);\n }\n current = array;\n } else if (value.nodeType) {\n if (hydrating && value.parentNode) return current = multi ? [value] : value;\n if (Array.isArray(current)) {\n if (multi) return current = cleanChildren(parent, current, marker, value);\n cleanChildren(parent, current, null, value);\n } else if (current == null || current === \"\" || !parent.firstChild) parent.appendChild(value);\n else parent.replaceChild(value, parent.firstChild);\n current = value;\n }\n return current;\n}\nfunction normalizeIncomingArray(normalized, array, current, unwrap) {\n let dynamic = false;\n for (let i = 0, len = array.length; i < len; i++) {\n let item = array[i], prev = current && current[normalized.length], t;\n if (item == null || item === true || item === false);\n else if ((t = typeof item) === \"object\" && item.nodeType) normalized.push(item);\n else if (Array.isArray(item)) dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;\n else if (t === \"function\") if (unwrap) {\n while (typeof item === \"function\") item = item();\n dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;\n } else {\n normalized.push(item);\n dynamic = true;\n }\n else {\n const value = String(item);\n if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);\n else normalized.push(document.createTextNode(value));\n }\n }\n return dynamic;\n}\nfunction appendNodes(parent, array, marker = null) {\n for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);\n}\nfunction cleanChildren(parent, current, marker, replacement) {\n if (marker === void 0) return parent.textContent = \"\";\n const node = replacement || document.createTextNode(\"\");\n if (current.length) {\n let inserted = false;\n for (let i = current.length - 1; i >= 0; i--) {\n const el = current[i];\n if (node !== el) {\n const isParent = el.parentNode === parent;\n if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);\n else isParent && el.remove();\n } else inserted = true;\n }\n } else parent.insertBefore(node, marker);\n return [node];\n}\n//#endregion\n//#region node_modules/solid-js/store/dist/store.js\nvar $RAW = Symbol(\"store-raw\"), $NODE = Symbol(\"store-node\"), $HAS = Symbol(\"store-has\"), $SELF = Symbol(\"store-self\");\nfunction wrap$1(value) {\n let p = value[$PROXY];\n if (!p) {\n Object.defineProperty(value, $PROXY, { value: p = new Proxy(value, proxyTraps$1) });\n if (!Array.isArray(value)) {\n const keys = Object.keys(value), desc = Object.getOwnPropertyDescriptors(value);\n for (let i = 0, l = keys.length; i < l; i++) {\n const prop = keys[i];\n if (desc[prop].get) Object.defineProperty(value, prop, {\n enumerable: desc[prop].enumerable,\n get: desc[prop].get.bind(p)\n });\n }\n }\n }\n return p;\n}\nfunction isWrappable(obj) {\n let proto;\n return obj != null && typeof obj === \"object\" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj));\n}\nfunction unwrap(item, set = /* @__PURE__ */ new Set()) {\n let result, unwrapped, v, prop;\n if (result = item != null && item[$RAW]) return result;\n if (!isWrappable(item) || set.has(item)) return item;\n if (Array.isArray(item)) {\n if (Object.isFrozen(item)) item = item.slice(0);\n else set.add(item);\n for (let i = 0, l = item.length; i < l; i++) {\n v = item[i];\n if ((unwrapped = unwrap(v, set)) !== v) item[i] = unwrapped;\n }\n } else {\n if (Object.isFrozen(item)) item = Object.assign({}, item);\n else set.add(item);\n const keys = Object.keys(item), desc = Object.getOwnPropertyDescriptors(item);\n for (let i = 0, l = keys.length; i < l; i++) {\n prop = keys[i];\n if (desc[prop].get) continue;\n v = item[prop];\n if ((unwrapped = unwrap(v, set)) !== v) item[prop] = unwrapped;\n }\n }\n return item;\n}\nfunction getNodes(target, symbol) {\n let nodes = target[symbol];\n if (!nodes) Object.defineProperty(target, symbol, { value: nodes = Object.create(null) });\n return nodes;\n}\nfunction getNode(nodes, property, value) {\n if (nodes[property]) return nodes[property];\n const [s, set] = createSignal(value, {\n equals: false,\n internal: true\n });\n s.$ = set;\n return nodes[property] = s;\n}\nfunction proxyDescriptor$1(target, property) {\n const desc = Reflect.getOwnPropertyDescriptor(target, property);\n if (!desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE) return desc;\n delete desc.value;\n delete desc.writable;\n desc.get = () => target[$PROXY][property];\n return desc;\n}\nfunction trackSelf(target) {\n getListener() && getNode(getNodes(target, $NODE), $SELF)();\n}\nfunction ownKeys(target) {\n trackSelf(target);\n return Reflect.ownKeys(target);\n}\nvar proxyTraps$1 = {\n get(target, property, receiver) {\n if (property === $RAW) return target;\n if (property === $PROXY) return receiver;\n if (property === $TRACK) {\n trackSelf(target);\n return receiver;\n }\n const nodes = getNodes(target, $NODE);\n const tracked = nodes[property];\n let value = tracked ? tracked() : target[property];\n if (property === $NODE || property === $HAS || property === \"__proto__\") return value;\n if (!tracked) {\n const desc = Object.getOwnPropertyDescriptor(target, property);\n if (getListener() && (typeof value !== \"function\" || target.hasOwnProperty(property)) && !(desc && desc.get)) value = getNode(nodes, property, value)();\n }\n return isWrappable(value) ? wrap$1(value) : value;\n },\n has(target, property) {\n if (property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === $HAS || property === \"__proto__\") return true;\n getListener() && getNode(getNodes(target, $HAS), property)();\n return property in target;\n },\n set() {\n return true;\n },\n deleteProperty() {\n return true;\n },\n ownKeys,\n getOwnPropertyDescriptor: proxyDescriptor$1\n};\nfunction setProperty(state, property, value, deleting = false) {\n if (!deleting && state[property] === value) return;\n const prev = state[property], len = state.length;\n if (value === void 0) {\n delete state[property];\n if (state[$HAS] && state[$HAS][property] && prev !== void 0) state[$HAS][property].$();\n } else {\n state[property] = value;\n if (state[$HAS] && state[$HAS][property] && prev === void 0) state[$HAS][property].$();\n }\n let nodes = getNodes(state, $NODE), node;\n if (node = getNode(nodes, property, prev)) node.$(() => value);\n if (Array.isArray(state) && state.length !== len) {\n for (let i = state.length; i < len; i++) (node = nodes[i]) && node.$();\n (node = getNode(nodes, \"length\", len)) && node.$(state.length);\n }\n (node = nodes[$SELF]) && node.$();\n}\nfunction mergeStoreNode(state, value) {\n const keys = Object.keys(value);\n for (let i = 0; i < keys.length; i += 1) {\n const key = keys[i];\n setProperty(state, key, value[key]);\n }\n}\nfunction updateArray(current, next) {\n if (typeof next === \"function\") next = next(current);\n next = unwrap(next);\n if (Array.isArray(next)) {\n if (current === next) return;\n let i = 0, len = next.length;\n for (; i < len; i++) {\n const value = next[i];\n if (current[i] !== value) setProperty(current, i, value);\n }\n setProperty(current, \"length\", len);\n } else mergeStoreNode(current, next);\n}\nfunction updatePath(current, path, traversed = []) {\n let part, prev = current;\n if (path.length > 1) {\n part = path.shift();\n const partType = typeof part, isArray = Array.isArray(current);\n if (Array.isArray(part)) {\n for (let i = 0; i < part.length; i++) updatePath(current, [part[i]].concat(path), traversed);\n return;\n } else if (isArray && partType === \"function\") {\n for (let i = 0; i < current.length; i++) if (part(current[i], i)) updatePath(current, [i].concat(path), traversed);\n return;\n } else if (isArray && partType === \"object\") {\n const { from = 0, to = current.length - 1, by = 1 } = part;\n for (let i = from; i <= to; i += by) updatePath(current, [i].concat(path), traversed);\n return;\n } else if (path.length > 1) {\n updatePath(current[part], path, [part].concat(traversed));\n return;\n }\n prev = current[part];\n traversed = [part].concat(traversed);\n }\n let value = path[0];\n if (typeof value === \"function\") {\n value = value(prev, traversed);\n if (value === prev) return;\n }\n if (part === void 0 && value == void 0) return;\n value = unwrap(value);\n if (part === void 0 || isWrappable(prev) && isWrappable(value) && !Array.isArray(value)) mergeStoreNode(prev, value);\n else setProperty(current, part, value);\n}\nfunction createStore(...[store, options]) {\n const unwrappedStore = unwrap(store || {});\n const isArray = Array.isArray(unwrappedStore);\n const wrappedStore = wrap$1(unwrappedStore);\n function setStore(...args) {\n batch(() => {\n isArray && args.length === 1 ? updateArray(unwrappedStore, args[0]) : updatePath(unwrappedStore, args);\n });\n }\n return [wrappedStore, setStore];\n}\n//#endregion\n//#region src/transport.ts\nvar granolaTransportPaths = {\n authLock: \"/auth/lock\",\n authLogin: \"/auth/login\",\n authLogout: \"/auth/logout\",\n authMode: \"/auth/mode\",\n authRefresh: \"/auth/refresh\",\n authStatus: \"/auth/status\",\n authUnlock: \"/auth/unlock\",\n automationMatches: \"/automation/matches\",\n automationArtefacts: \"/automation/artefacts\",\n automationRules: \"/automation/rules\",\n automationRuns: \"/automation/runs\",\n events: \"/events\",\n exportJobs: \"/exports/jobs\",\n exportNotes: \"/exports/notes\",\n exportTranscripts: \"/exports/transcripts\",\n folderResolve: \"/folders/resolve\",\n folders: \"/folders\",\n health: \"/health\",\n meetingResolve: \"/meetings/resolve\",\n meetings: \"/meetings\",\n root: \"/\",\n serverInfo: \"/server/info\",\n syncRun: \"/sync\",\n syncEvents: \"/sync/events\",\n state: \"/state\"\n};\nfunction appendSearchParams(path, params) {\n const url = new URL(path, \"http://localhost\");\n for (const [key, value] of Object.entries(params)) {\n if (value === void 0 || value === false || value === \"\") continue;\n url.searchParams.set(key, String(value));\n }\n return `${url.pathname}${url.search}`;\n}\nfunction granolaMeetingPath(id) {\n return `${granolaTransportPaths.meetings}/${encodeURIComponent(id)}`;\n}\nfunction granolaMeetingResolvePath(query, options = {}) {\n return appendSearchParams(granolaTransportPaths.meetingResolve, {\n includeTranscript: options.includeTranscript ? \"true\" : void 0,\n q: query\n });\n}\nfunction granolaMeetingsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.meetings, {\n folderId: options.folderId,\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search,\n sort: options.sort,\n updatedFrom: options.updatedFrom,\n updatedTo: options.updatedTo\n });\n}\nfunction granolaFolderPath(id) {\n return `${granolaTransportPaths.folders}/${encodeURIComponent(id)}`;\n}\nfunction granolaFolderResolvePath(query) {\n return appendSearchParams(granolaTransportPaths.folderResolve, { q: query });\n}\nfunction granolaFoldersPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.folders, {\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search\n });\n}\nfunction granolaExportJobsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.exportJobs, { limit: options.limit });\n}\nfunction granolaAutomationRunsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationRuns, {\n limit: options.limit,\n status: options.status\n });\n}\nfunction granolaAutomationArtefactsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationArtefacts, {\n kind: options.kind,\n limit: options.limit,\n meetingId: options.meetingId,\n status: options.status\n });\n}\nfunction granolaAutomationRunDecisionPath(id, decision) {\n return `${granolaTransportPaths.automationRuns}/${encodeURIComponent(id)}/${decision}`;\n}\nfunction granolaAutomationArtefactRerunPath(id) {\n return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}/rerun`;\n}\nfunction granolaAutomationArtefactPath(id) {\n return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}`;\n}\nfunction granolaAutomationArtefactDecisionPath(id, decision) {\n return `${granolaAutomationArtefactPath(id)}/${decision}`;\n}\nfunction granolaAutomationArtefactUpdatePath(id) {\n return `${granolaAutomationArtefactPath(id)}/update`;\n}\nfunction granolaExportJobRerunPath(id) {\n return `${granolaTransportPaths.exportJobs}/${encodeURIComponent(id)}/rerun`;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/checkPrivateRedeclaration.js\nfunction _checkPrivateRedeclaration(e, t) {\n if (t.has(e)) throw new TypeError(\"Cannot initialize the same private elements twice on an object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldInitSpec.js\nfunction _classPrivateFieldInitSpec(e, t, a) {\n _checkPrivateRedeclaration(e, t), t.set(e, a);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/typeof.js\nfunction _typeof(o) {\n \"@babel/helpers - typeof\";\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function(o) {\n return typeof o;\n } : function(o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/defineProperty.js\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/assertClassBrand.js\nfunction _assertClassBrand(e, t, n) {\n if (\"function\" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;\n throw new TypeError(\"Private element is not present on this object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldSet2.js\nfunction _classPrivateFieldSet2(s, a, r) {\n return s.set(_assertClassBrand(s, a), r), r;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldGet2.js\nfunction _classPrivateFieldGet2(s, a) {\n return s.get(_assertClassBrand(s, a));\n}\n//#endregion\n//#region src/server/client.ts\nfunction cloneValue(value) {\n return structuredClone(value);\n}\nfunction normaliseServerUrl(serverUrl) {\n const raw = serverUrl instanceof URL ? serverUrl.href : serverUrl.trim();\n if (!raw) throw new Error(\"server URL is required\");\n const withProtocol = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw) ? raw : `http://${raw}`;\n const parsed = new URL(withProtocol);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") throw new Error(\"server URL must use http or https\");\n parsed.pathname = \"/\";\n parsed.search = \"\";\n parsed.hash = \"\";\n return parsed;\n}\nfunction mergeHeaders(...values) {\n const headers = new Headers();\n for (const value of values) {\n if (!value) continue;\n new Headers(value).forEach((headerValue, headerName) => {\n headers.set(headerName, headerValue);\n });\n }\n return headers;\n}\nasync function responseError(response) {\n let message = `${response.status} ${response.statusText}`.trim();\n try {\n const payload = await response.json();\n if (typeof payload.error === \"string\" && payload.error.trim()) message = payload.error;\n else if (typeof payload.message === \"string\" && payload.message.trim()) message = payload.message;\n } catch {\n const text = (await response.text()).trim();\n if (text) message = text;\n }\n return new Error(message);\n}\nfunction parseSseEvent(payload) {\n const data = payload.replaceAll(\"\\r\\n\", \"\\n\").split(\"\\n\").filter((line) => line.startsWith(\"data:\")).map((line) => line.slice(5).trimStart()).join(\"\\n\");\n if (!data) return;\n return JSON.parse(data);\n}\nvar _closed = /* @__PURE__ */ new WeakMap();\nvar _eventLoop = /* @__PURE__ */ new WeakMap();\nvar _listeners = /* @__PURE__ */ new WeakMap();\nvar _fetchImpl = /* @__PURE__ */ new WeakMap();\nvar _password = /* @__PURE__ */ new WeakMap();\nvar _reconnectDelayMs = /* @__PURE__ */ new WeakMap();\nvar _streamAbortController = /* @__PURE__ */ new WeakMap();\nvar _state = /* @__PURE__ */ new WeakMap();\nvar GranolaServerClient = class GranolaServerClient {\n constructor(info, url, initialState, options = {}) {\n _classPrivateFieldInitSpec(this, _closed, false);\n _classPrivateFieldInitSpec(this, _eventLoop, void 0);\n _classPrivateFieldInitSpec(this, _listeners, /* @__PURE__ */ new Set());\n _classPrivateFieldInitSpec(this, _fetchImpl, void 0);\n _classPrivateFieldInitSpec(this, _password, void 0);\n _classPrivateFieldInitSpec(this, _reconnectDelayMs, void 0);\n _defineProperty(this, \"info\", void 0);\n _classPrivateFieldInitSpec(this, _streamAbortController, void 0);\n _classPrivateFieldInitSpec(this, _state, void 0);\n this.url = url;\n _classPrivateFieldSet2(_fetchImpl, this, options.fetchImpl ?? fetch);\n this.info = cloneValue(info);\n _classPrivateFieldSet2(_password, this, options.password?.trim() || void 0);\n _classPrivateFieldSet2(_reconnectDelayMs, this, options.reconnectDelayMs ?? 1e3);\n _classPrivateFieldSet2(_state, this, cloneValue(initialState));\n }\n static async connect(serverUrl, options = {}) {\n const url = normaliseServerUrl(serverUrl);\n const fetchImpl = options.fetchImpl ?? fetch;\n const infoResponse = await fetchImpl(new URL(granolaTransportPaths.serverInfo, url), { headers: mergeHeaders({\n ...options.password?.trim() ? { \"x-granola-password\": options.password.trim() } : {},\n accept: \"application/json\"\n }) });\n if (!infoResponse.ok) throw await responseError(infoResponse);\n const info = await infoResponse.json();\n if (info.protocolVersion !== 2) throw new Error(`unsupported Granola transport protocol: expected 2, got ${info.protocolVersion}`);\n const response = await fetchImpl(new URL(granolaTransportPaths.state, url), { headers: mergeHeaders({\n ...options.password?.trim() ? { \"x-granola-password\": options.password.trim() } : {},\n accept: \"application/json\"\n }) });\n if (!response.ok) throw await responseError(response);\n const client = new GranolaServerClient(info, url, await response.json(), options);\n client.startEvents();\n return client;\n }\n async close() {\n _classPrivateFieldSet2(_closed, this, true);\n _classPrivateFieldGet2(_streamAbortController, this)?.abort();\n try {\n await _classPrivateFieldGet2(_eventLoop, this);\n } catch {}\n }\n getState() {\n return cloneValue(_classPrivateFieldGet2(_state, this));\n }\n subscribe(listener) {\n _classPrivateFieldGet2(_listeners, this).add(listener);\n return () => {\n _classPrivateFieldGet2(_listeners, this).delete(listener);\n };\n }\n async inspectAuth() {\n return await this.requestJson(granolaTransportPaths.authStatus);\n }\n async listAutomationArtefacts(options = {}) {\n return await this.requestJson(granolaAutomationArtefactsPath(options));\n }\n async getAutomationArtefact(id) {\n return await this.requestJson(granolaAutomationArtefactPath(id));\n }\n async listAutomationRules() {\n return await this.requestJson(granolaTransportPaths.automationRules);\n }\n async listAutomationMatches(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.automationMatches}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.automationMatches;\n return await this.requestJson(path);\n }\n async listAutomationRuns(options = {}) {\n return await this.requestJson(granolaAutomationRunsPath(options));\n }\n async resolveAutomationRun(id, decision, options = {}) {\n return await this.requestJson(granolaAutomationRunDecisionPath(id, decision), {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async resolveAutomationArtefact(id, decision, options = {}) {\n return await this.requestJson(granolaAutomationArtefactDecisionPath(id, decision), {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async updateAutomationArtefact(id, patch) {\n return await this.requestJson(granolaAutomationArtefactUpdatePath(id), {\n body: JSON.stringify(patch),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async rerunAutomationArtefact(id) {\n return await this.requestJson(granolaAutomationArtefactRerunPath(id), { method: \"POST\" });\n }\n async inspectSync() {\n return cloneValue(_classPrivateFieldGet2(_state, this).sync);\n }\n async listSyncEvents(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.syncEvents}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.syncEvents;\n return await this.requestJson(path);\n }\n async loginAuth(options = {}) {\n return await this.requestJson(granolaTransportPaths.authLogin, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async logoutAuth() {\n return await this.requestJson(granolaTransportPaths.authLogout, { method: \"POST\" });\n }\n async refreshAuth() {\n return await this.requestJson(granolaTransportPaths.authRefresh, { method: \"POST\" });\n }\n async switchAuthMode(mode) {\n return await this.requestJson(granolaTransportPaths.authMode, {\n body: JSON.stringify({ mode }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async sync(options = {}) {\n return await this.requestJson(granolaTransportPaths.syncRun, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async listFolders(options = {}) {\n return await this.requestJson(granolaFoldersPath(options));\n }\n async getFolder(id) {\n return await this.requestJson(granolaFolderPath(id));\n }\n async findFolder(query) {\n return await this.requestJson(granolaFolderResolvePath(query));\n }\n async listMeetings(options = {}) {\n return await this.requestJson(granolaMeetingsPath(options));\n }\n async getMeeting(id, options = {}) {\n return await this.requestJson(`${granolaMeetingPath(id)}${options.requireCache ? \"?includeTranscript=true\" : \"\"}`);\n }\n async findMeeting(query, options = {}) {\n return await this.requestJson(granolaMeetingResolvePath(query, { includeTranscript: options.requireCache }));\n }\n async listExportJobs(options = {}) {\n return await this.requestJson(granolaExportJobsPath(options));\n }\n async exportNotes(format = \"markdown\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportNotes, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async exportTranscripts(format = \"text\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportTranscripts, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async rerunExportJob(id) {\n return await this.requestJson(granolaExportJobRerunPath(id), { method: \"POST\" });\n }\n async request(path, init = {}) {\n const response = await _classPrivateFieldGet2(_fetchImpl, this).call(this, new URL(path, this.url), {\n ...init,\n headers: mergeHeaders({\n ..._classPrivateFieldGet2(_password, this) ? { \"x-granola-password\": _classPrivateFieldGet2(_password, this) } : {},\n accept: \"application/json\"\n }, init.headers)\n });\n if (!response.ok) throw await responseError(response);\n return response;\n }\n async requestJson(path, init = {}) {\n return cloneValue(await (await this.request(path, init)).json());\n }\n emit(event) {\n _classPrivateFieldSet2(_state, this, cloneValue(event.state));\n const nextEvent = cloneValue(event);\n for (const listener of _classPrivateFieldGet2(_listeners, this)) listener(nextEvent);\n }\n startEvents() {\n if (_classPrivateFieldGet2(_eventLoop, this)) return;\n _classPrivateFieldSet2(_eventLoop, this, this.runEventsLoop());\n }\n async runEventsLoop() {\n while (!_classPrivateFieldGet2(_closed, this)) {\n const controller = new AbortController();\n _classPrivateFieldSet2(_streamAbortController, this, controller);\n try {\n const response = await this.request(granolaTransportPaths.events, {\n headers: { accept: \"text/event-stream\" },\n signal: controller.signal\n });\n await this.consumeEventStream(response);\n } catch {\n if (_classPrivateFieldGet2(_closed, this) || controller.signal.aborted) break;\n await new Promise((resolve) => {\n setTimeout(resolve, _classPrivateFieldGet2(_reconnectDelayMs, this));\n });\n }\n }\n }\n async consumeEventStream(response) {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"server did not provide an event stream\");\n const decoder = new TextDecoder();\n let buffer = \"\";\n while (!_classPrivateFieldGet2(_closed, this)) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n buffer = buffer.replaceAll(\"\\r\\n\", \"\\n\");\n while (true) {\n const boundary = buffer.indexOf(\"\\n\\n\");\n if (boundary < 0) break;\n const chunk = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n const event = parseSseEvent(chunk);\n if (event) this.emit(event);\n }\n }\n }\n};\nasync function createGranolaServerClient(serverUrl, options = {}) {\n return await GranolaServerClient.connect(serverUrl, options);\n}\n//#endregion\n//#region src/web/client-state.ts\nvar granolaWebWorkspaceStorageKey = \"granola-toolkit.web-workspace\";\nvar maxRecentMeetings = 6;\nvar maxSavedFilters = 6;\nfunction normaliseFilterValue(value) {\n const trimmed = value?.trim();\n return trimmed ? trimmed : void 0;\n}\nfunction normaliseFilters(filters) {\n const selectedFolderId = normaliseFilterValue(filters.selectedFolderId);\n return {\n search: normaliseFilterValue(filters.search),\n selectedFolderId,\n sort: normaliseFilterValue(filters.sort) ?? \"updated-desc\",\n updatedFrom: normaliseFilterValue(filters.updatedFrom),\n updatedTo: normaliseFilterValue(filters.updatedTo)\n };\n}\nfunction filtersKey(filters) {\n return JSON.stringify(normaliseFilters(filters));\n}\nfunction defaultWorkspacePreferences() {\n return {\n recentMeetings: [],\n savedFilters: []\n };\n}\nfunction parseWorkspacePreferences(raw) {\n if (!raw) return defaultWorkspacePreferences();\n try {\n const parsed = JSON.parse(raw);\n return {\n recentMeetings: Array.isArray(parsed?.recentMeetings) ? parsed.recentMeetings.map((entry) => ({\n folderId: normaliseFilterValue(entry?.folderId),\n id: normaliseFilterValue(entry?.id) || \"\",\n title: normaliseFilterValue(entry?.title) || \"\",\n updatedAt: normaliseFilterValue(entry?.updatedAt) || \"\"\n })).filter((entry) => entry.id && entry.title).slice(0, maxRecentMeetings) : [],\n savedFilters: Array.isArray(parsed?.savedFilters) ? parsed.savedFilters.map((preset) => ({\n filters: normaliseFilters(preset?.filters ?? {}),\n id: normaliseFilterValue(preset?.id) || \"\",\n label: normaliseFilterValue(preset?.label) || \"\"\n })).filter((preset) => preset.id && preset.label).slice(0, maxSavedFilters) : []\n };\n } catch {\n return defaultWorkspacePreferences();\n }\n}\nfunction serialiseWorkspacePreferences(preferences) {\n return JSON.stringify({\n recentMeetings: preferences.recentMeetings.slice(0, maxRecentMeetings),\n savedFilters: preferences.savedFilters.slice(0, maxSavedFilters)\n });\n}\nfunction hasActiveFilters(filters) {\n const normalised = normaliseFilters(filters);\n return Boolean(normalised.search || normalised.selectedFolderId || normalised.updatedFrom || normalised.updatedTo || normalised.sort !== \"updated-desc\");\n}\nfunction filterLabel(filters) {\n const summary = currentFilterSummary(filters);\n if (!summary) return \"Current workspace\";\n return summary;\n}\nfunction rememberRecentMeeting(preferences, meeting) {\n const nextEntry = {\n folderId: meeting.folders?.[0]?.id,\n id: meeting.id,\n title: meeting.title?.trim() || meeting.id,\n updatedAt: meeting.updatedAt\n };\n return {\n ...preferences,\n recentMeetings: [nextEntry, ...preferences.recentMeetings.filter((entry) => entry.id !== nextEntry.id)].slice(0, maxRecentMeetings)\n };\n}\nfunction saveWorkspaceFilter(preferences, filters, options = {}) {\n const nextFilters = normaliseFilters(filters);\n if (!hasActiveFilters(nextFilters)) return preferences;\n const key = filtersKey(nextFilters);\n const nextPreset = {\n filters: nextFilters,\n id: preferences.savedFilters.find((preset) => filtersKey(preset.filters) === key)?.id ?? options.idFactory?.() ?? `filter-${preferences.savedFilters.length + 1}`,\n label: filterLabel(filters)\n };\n return {\n ...preferences,\n savedFilters: [nextPreset, ...preferences.savedFilters.filter((preset) => preset.id !== nextPreset.id)].slice(0, maxSavedFilters)\n };\n}\nfunction removeWorkspaceFilter(preferences, id) {\n return {\n ...preferences,\n savedFilters: preferences.savedFilters.filter((preset) => preset.id !== id)\n };\n}\nfunction applyWorkspaceFilter(preset) {\n return {\n search: preset.filters.search ?? \"\",\n selectedFolderId: preset.filters.selectedFolderId ?? null,\n sort: preset.filters.sort ?? \"updated-desc\",\n updatedFrom: preset.filters.updatedFrom ?? \"\",\n updatedTo: preset.filters.updatedTo ?? \"\"\n };\n}\nfunction parseWorkspaceTab(value) {\n switch (value) {\n case \"metadata\":\n case \"raw\":\n case \"transcript\": return value;\n default: return \"notes\";\n }\n}\nfunction startupSelectionFromSearch(search) {\n const params = new URLSearchParams(search);\n return {\n folderId: params.get(\"folder\")?.trim() || \"\",\n meetingId: params.get(\"meeting\")?.trim() || \"\",\n workspaceTab: parseWorkspaceTab(params.get(\"tab\"))\n };\n}\nfunction buildBrowserUrlPath(currentHref, selection) {\n const url = new URL(currentHref);\n if (selection.selectedFolderId) url.searchParams.set(\"folder\", selection.selectedFolderId);\n else url.searchParams.delete(\"folder\");\n if (selection.selectedMeetingId) url.searchParams.set(\"meeting\", selection.selectedMeetingId);\n else url.searchParams.delete(\"meeting\");\n if (parseWorkspaceTab(selection.workspaceTab) !== \"notes\") url.searchParams.set(\"tab\", parseWorkspaceTab(selection.workspaceTab));\n else url.searchParams.delete(\"tab\");\n return `${url.pathname}${url.search}${url.hash}`;\n}\nfunction exportScopeLabel(scope) {\n return scope && scope.mode === \"folder\" ? `Folder: ${scope.folderName || scope.folderId}` : \"Scope: All meetings\";\n}\nfunction currentFilterSummary(filters) {\n const parts = [];\n if (filters.selectedFolderId) {\n const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);\n parts.push(`folder \"${folder ? folder.name : filters.selectedFolderId}\"`);\n }\n if (filters.search) parts.push(`search \"${filters.search}\"`);\n if (filters.updatedFrom) parts.push(`from ${filters.updatedFrom}`);\n if (filters.updatedTo) parts.push(`to ${filters.updatedTo}`);\n if (filters.sort && filters.sort !== \"updated-desc\") parts.push(filters.sort === \"updated-asc\" ? \"oldest first\" : filters.sort === \"title-asc\" ? \"title A-Z\" : \"title Z-A\");\n return parts.join(\", \");\n}\nfunction selectMeetingId(meetings, selectedMeetingId) {\n if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;\n return meetings[0]?.id ?? null;\n}\nfunction nextWorkspaceTab(currentTab, key) {\n const current = parseWorkspaceTab(currentTab);\n switch (key) {\n case \"1\": return \"notes\";\n case \"2\": return \"transcript\";\n case \"3\": return \"metadata\";\n case \"4\": return \"raw\";\n case \"]\":\n switch (current) {\n case \"notes\": return \"transcript\";\n case \"transcript\": return \"metadata\";\n case \"metadata\": return \"raw\";\n case \"raw\": return \"notes\";\n }\n break;\n case \"[\":\n switch (current) {\n case \"notes\": return \"raw\";\n case \"transcript\": return \"notes\";\n case \"metadata\": return \"transcript\";\n case \"raw\": return \"metadata\";\n }\n break;\n default: return;\n }\n}\nfunction describeSyncStatus(sync) {\n if (sync.running) return \"Sync running\";\n if (sync.lastError) return \"Sync needs attention\";\n if (sync.lastCompletedAt) {\n const suffix = sync.summary?.changedCount ? ` · ${sync.summary.changedCount} changes` : \"\";\n return `Synced ${sync.lastCompletedAt.slice(11, 19)}${suffix}`;\n }\n return \"Sync idle\";\n}\nfunction describeAuthStatus(auth) {\n if (!auth) return \"Waiting for auth\";\n if (auth.lastError) return \"Auth needs attention\";\n switch (auth.mode) {\n case \"api-key\": return \"API key active\";\n case \"stored-session\": return \"Stored session active\";\n default: return \"supabase.json active\";\n }\n}\n//#endregion\n//#region src/web-app/components.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$$1 = /* @__PURE__ */ template(`<section class=hero><h1>Granola Toolkit</h1><p>Browser workspace for folders, meetings, notes, transcripts, and export flows on top of one local server instance.</p><input class=search placeholder=\"Search meetings, ids, or tags\"><div class=\"field-row field-row--inline\"><label><span class=field-label>Sort</span><select class=select><option value=updated-desc>Newest first</option><option value=updated-asc>Oldest first</option><option value=title-asc>Title A-Z</option><option value=title-desc>Title Z-A</option></select></label><label><span class=field-label>Updated From</span><input class=field-input type=date></label></div><label class=field-row><span class=field-label>Updated To</span><input class=field-input type=date>`), _tmpl$2 = /* @__PURE__ */ template(`<section class=toolbar><div><p>Meetings are loaded from the shared server state so this view can stay aligned with the terminal UI and sync loop.</p></div><div class=toolbar-form><input class=field-input placeholder=\"Quick open by id or title\"><button class=\"button button--secondary\"type=button>Open`), _tmpl$3 = /* @__PURE__ */ template(`<div class=\"folder-empty folder-empty--error\">`), _tmpl$4 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Folders</h2><p>Pick a folder to scope the meeting browser, or stay on All meetings.</p></div><div class=folder-list>`), _tmpl$5 = /* @__PURE__ */ template(`<button class=folder-row type=button><span class=folder-row__title>All meetings</span><span class=folder-row__meta>Browse the full meeting list.`), _tmpl$6 = /* @__PURE__ */ template(`<div class=folder-empty>No folders found.`), _tmpl$7 = /* @__PURE__ */ template(`<button class=folder-row type=button><span class=folder-row__title></span><span class=folder-row__meta>`), _tmpl$8 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Saved Filters</h2><p>Keep the slices you revisit often close at hand.</p></div><div class=saved-filter-actions><button class=\"button button--secondary\"type=button>Save current filter</button></div><div class=saved-filter-list>`), _tmpl$9 = /* @__PURE__ */ template(`<div class=folder-empty>No saved filters yet.`), _tmpl$0 = /* @__PURE__ */ template(`<div class=saved-filter-card><button class=saved-filter-card__main type=button><span class=folder-row__title></span><span class=folder-row__meta></span></button><button class=saved-filter-card__remove type=button>Remove`), _tmpl$1 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Recent Meetings</h2><p>Jump back into the conversations you opened most recently.</p></div><div class=folder-list>`), _tmpl$10 = /* @__PURE__ */ template(`<div class=folder-empty>No recent meetings yet.`), _tmpl$11 = /* @__PURE__ */ template(`<div class=\"meeting-empty meeting-empty--error\">`), _tmpl$12 = /* @__PURE__ */ template(`<section class=meeting-list>`), _tmpl$13 = /* @__PURE__ */ template(`<div class=meeting-empty>`), _tmpl$14 = /* @__PURE__ */ template(`<button class=meeting-row type=button><span class=meeting-row__title></span><span class=meeting-row__meta></span><span class=meeting-row__meta>`), _tmpl$15 = /* @__PURE__ */ template(`<p>`), _tmpl$16 = /* @__PURE__ */ template(`<section class=detail-head><div><h2>Meeting Workspace</h2></div><div class=state-badge>`), _tmpl$17 = /* @__PURE__ */ template(`<p>Waiting for server state…`), _tmpl$18 = /* @__PURE__ */ template(`<div class=status-grid><div><span class=status-label>Surface</span><strong></strong></div><div><span class=status-label>View</span><strong></strong></div><div><span class=status-label>Auth</span><strong></strong></div><div><span class=status-label>Sync</span><strong></strong></div><div><span class=status-label>Documents</span><strong></strong></div><div><span class=status-label>Folders</span><strong></strong></div><div><span class=status-label>Cache</span><strong></strong></div><div><span class=status-label>Index</span><strong></strong></div><div><span class=status-label>Automation</span><strong>`), _tmpl$19 = /* @__PURE__ */ template(`<section class=security-panel><div class=security-panel__head><h3>Server Access</h3><p>This server is locked with a password. Unlock it to load meetings and live state.</p></div><div class=security-panel__body><input class=field-input placeholder=\"Server password\"type=password><div class=toolbar-actions><button class=\"button button--primary\"type=button>Unlock</button><button class=\"button button--secondary\"type=button>Lock`), _tmpl$20 = /* @__PURE__ */ template(`<section class=auth-panel><div class=auth-panel__head><h3>Auth Session</h3><p>Inspect, refresh, and switch between API key, stored session, and <code>supabase.json</code>.</p></div><div class=auth-panel__body>`), _tmpl$21 = /* @__PURE__ */ template(`<div class=auth-card><div class=auth-card__meta>Auth state unavailable.`), _tmpl$22 = /* @__PURE__ */ template(`<div class=auth-card__meta>Client ID: `), _tmpl$23 = /* @__PURE__ */ template(`<div class=auth-card__meta>Sign-in method: `), _tmpl$24 = /* @__PURE__ */ template(`<div class=auth-card__meta>supabase path: `), _tmpl$25 = /* @__PURE__ */ template(`<div class=\"auth-card__meta auth-card__error\">`), _tmpl$26 = /* @__PURE__ */ template(`<div class=auth-card><div class=status-grid><div><span class=status-label>Active</span><strong></strong></div><div><span class=status-label>API key</span><strong></strong></div><div><span class=status-label>Stored</span><strong></strong></div><div><span class=status-label>supabase.json</span><strong></strong></div><div><span class=status-label>Refresh</span><strong></strong></div></div><div class=auth-card__meta>Store a Granola Personal API key here or use <code>granola auth login --api-key <token></code>.</div><div class=auth-card__actions><input class=input placeholder=grn_... type=password><button class=\"button button--secondary\"type=button>Save API key</button><button class=\"button button--secondary\"type=button>Import desktop session</button><button class=\"button button--secondary\"type=button>Refresh stored session</button><button class=\"button button--secondary\"type=button>Use API key</button><button class=\"button button--secondary\"type=button>Use stored session</button><button class=\"button button--secondary\"type=button>Use supabase.json</button><button class=\"button button--secondary\"type=button>Sign out`), _tmpl$27 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Recent Export Jobs</h3><p>Tracked across CLI and web runs.</p></div><div class=jobs-list>`), _tmpl$28 = /* @__PURE__ */ template(`<div class=job-empty>No export jobs yet.`), _tmpl$29 = /* @__PURE__ */ template(`<div class=job-card__meta>`), _tmpl$30 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Rerun`), _tmpl$31 = /* @__PURE__ */ template(`<article class=job-card><div class=job-card__head><div><div class=job-card__title> export</div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta>Started: </div><div class=job-card__meta>Output: </div><div class=job-card__actions>`), _tmpl$32 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Automation Runs</h3><p>Recent action runs triggered by durable sync events.</p></div><div class=jobs-list>`), _tmpl$33 = /* @__PURE__ */ template(`<div class=job-empty>No automation runs yet.`), _tmpl$34 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Approve`), _tmpl$35 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Reject`), _tmpl$36 = /* @__PURE__ */ template(`<article class=job-card><div class=job-card__head><div><div class=job-card__title></div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta></div><div class=job-card__actions>`), _tmpl$37 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Review Queue</h3><p>Generated note and enrichment candidates waiting for review or follow-up.</p></div><div class=jobs-list>`), _tmpl$38 = /* @__PURE__ */ template(`<div class=job-empty>No automation artefacts yet.`), _tmpl$39 = /* @__PURE__ */ template(`<button class=\"job-card job-card--button\"type=button><div class=job-card__head><div><div class=job-card__title></div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta>`), _tmpl$40 = /* @__PURE__ */ template(`<section class=review-panel><div class=jobs-panel__head><h3>Artefact Review</h3><p>Review generated candidate notes, compare them to the current meeting, then approve, reject, edit, or rerun.`), _tmpl$41 = /* @__PURE__ */ template(`<div class=job-empty>`), _tmpl$42 = /* @__PURE__ */ template(`<div class=review-grid><section class=detail-section><h2>Current Meeting Notes</h2><pre class=detail-pre></pre></section><section class=detail-section><h2>Candidate</h2><label class=field-row><span class=field-label>Title</span><input class=\"field-input field-input--plain\"></label><label class=field-row><span class=field-label>Summary</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><label class=field-row><span class=field-label>Markdown</span><textarea class=review-textarea></textarea></label><label class=field-row><span class=field-label>Review Note</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><div class=job-card__actions><button class=\"button button--secondary\"type=button>Save edits</button><button class=\"button button--secondary\"type=button>Approve</button><button class=\"button button--secondary\"type=button>Reject</button><button class=\"button button--secondary\"type=button>Rerun`), _tmpl$43 = /* @__PURE__ */ template(`<div class=review-body><div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip></div></div><section class=\"detail-section review-history\"><h2>History</h2><div class=jobs-list>`), _tmpl$44 = /* @__PURE__ */ template(`<div class=empty>`), _tmpl$45 = /* @__PURE__ */ template(`<div class=job-card><div class=job-card__head><div class=job-card__title></div><div class=job-card__meta>`), _tmpl$46 = /* @__PURE__ */ template(`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle`), _tmpl$47 = /* @__PURE__ */ template(`<button class=workspace-tab type=button>`), _tmpl$48 = /* @__PURE__ */ template(`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>`), _tmpl$49 = /* @__PURE__ */ template(`<div class=detail-body><div class=workspace-grid><aside class=\"detail-section workspace-sidebar\"><h2>Meeting Metadata</h2><pre class=detail-pre></pre></aside><section class=\"detail-section workspace-main\"><h2></h2><pre class=detail-pre>`);\nfunction authModeLabel(mode) {\n switch (mode) {\n case \"api-key\": return \"API key\";\n case \"stored-session\": return \"Stored session\";\n default: return \"supabase.json\";\n }\n}\nfunction metadataLines(record) {\n return [\n `Title: ${record.meeting.title || record.meeting.id}`,\n `Created: ${record.meeting.createdAt}`,\n `Updated: ${record.meeting.updatedAt}`,\n `Folders: ${record.meeting.folders.length ? record.meeting.folders.map((folder) => folder.name).join(\", \") : \"none\"}`,\n `Tags: ${record.meeting.tags.length ? record.meeting.tags.join(\", \") : \"none\"}`,\n `Transcript loaded: ${record.meeting.transcriptLoaded ? \"yes\" : \"no\"}`\n ].join(\"\\n\");\n}\nfunction workspaceBody(bundle, record, tab) {\n switch (tab) {\n case \"transcript\": return {\n body: record.transcriptText || \"(Transcript unavailable)\",\n title: \"Transcript\"\n };\n case \"metadata\": return {\n body: metadataLines(record),\n title: \"Metadata\"\n };\n case \"raw\": return {\n body: JSON.stringify(bundle || record, null, 2),\n title: \"Raw Bundle\"\n };\n default: return {\n body: record.noteMarkdown || \"(No notes available)\",\n title: \"Notes\"\n };\n }\n}\nfunction scopeLabel(scope) {\n return exportScopeLabel(scope);\n}\nfunction ToolbarFilters(props) {\n return [(() => {\n var _el$ = _tmpl$$1(), _el$4 = _el$.firstChild.nextSibling.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$8 = _el$6.firstChild.nextSibling, _el$1 = _el$6.nextSibling.firstChild.nextSibling, _el$12 = _el$5.nextSibling.firstChild.nextSibling;\n _el$4.$$input = (event) => {\n props.onSearchInput(event.currentTarget.value);\n };\n _el$8.addEventListener(\"change\", (event) => {\n props.onSortChange(event.currentTarget.value);\n });\n _el$1.addEventListener(\"change\", (event) => {\n props.onUpdatedFromChange(event.currentTarget.value);\n });\n _el$12.addEventListener(\"change\", (event) => {\n props.onUpdatedToChange(event.currentTarget.value);\n });\n createRenderEffect(() => _el$4.value = props.search);\n createRenderEffect(() => _el$8.value = props.sort);\n createRenderEffect(() => _el$1.value = props.updatedFrom);\n createRenderEffect(() => _el$12.value = props.updatedTo);\n return _el$;\n })(), (() => {\n var _el$13 = _tmpl$2(), _el$16 = _el$13.firstChild.nextSibling.firstChild, _el$17 = _el$16.nextSibling;\n _el$16.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onQuickOpen();\n }\n };\n _el$16.$$input = (event) => {\n props.onQuickOpenInput(event.currentTarget.value);\n };\n addEventListener(_el$17, \"click\", props.onQuickOpen, true);\n createRenderEffect(() => _el$16.value = props.quickOpen);\n return _el$13;\n })()];\n}\nfunction FolderList(props) {\n return (() => {\n var _el$18 = _tmpl$4(), _el$20 = _el$18.firstChild.nextSibling;\n insert(_el$20, createComponent(Show, {\n get fallback() {\n return [\n (() => {\n var _el$22 = _tmpl$5();\n _el$22.$$click = () => {\n props.onSelect(null);\n };\n createRenderEffect(() => setAttribute(_el$22, \"data-selected\", !props.selectedFolderId ? \"true\" : void 0));\n return _el$22;\n })(),\n createComponent(For, {\n get each() {\n return props.folders;\n },\n children: (folder) => (() => {\n var _el$24 = _tmpl$7(), _el$25 = _el$24.firstChild, _el$26 = _el$25.nextSibling;\n _el$24.$$click = () => {\n props.onSelect(folder.id);\n };\n insert(_el$25, () => (folder.isFavourite ? \"★ \" : \"\") + (folder.name || folder.id));\n insert(_el$26, () => `${folder.documentCount} meetings`);\n createRenderEffect(() => setAttribute(_el$24, \"data-selected\", folder.id === props.selectedFolderId ? \"true\" : void 0));\n return _el$24;\n })()\n }),\n createComponent(Show, {\n get when() {\n return props.folders.length === 0;\n },\n get children() {\n return _tmpl$6();\n }\n })\n ];\n },\n get when() {\n return !props.error;\n },\n get children() {\n var _el$21 = _tmpl$3();\n insert(_el$21, () => props.error);\n return _el$21;\n }\n }));\n return _el$18;\n })();\n}\nfunction SavedFiltersPanel(props) {\n const canSaveCurrent = () => hasActiveFilters({\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n sort: props.sort,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$27 = _tmpl$8(), _el$29 = _el$27.firstChild.nextSibling, _el$30 = _el$29.firstChild, _el$31 = _el$29.nextSibling;\n _el$30.$$click = () => {\n props.onSaveCurrent();\n };\n insert(_el$31, createComponent(Show, {\n get when() {\n return props.savedFilters.length > 0;\n },\n get fallback() {\n return _tmpl$9();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.savedFilters;\n },\n children: (preset) => (() => {\n var _el$33 = _tmpl$0(), _el$34 = _el$33.firstChild, _el$35 = _el$34.firstChild, _el$36 = _el$35.nextSibling, _el$37 = _el$34.nextSibling;\n _el$34.$$click = () => {\n props.onApply(preset);\n };\n insert(_el$35, () => preset.label);\n insert(_el$36, () => currentFilterSummary({\n folders: props.folders,\n ...preset.filters\n }) || \"Saved workspace scope\");\n _el$37.$$click = () => {\n props.onRemove(preset.id);\n };\n return _el$33;\n })()\n });\n }\n }));\n createRenderEffect(() => _el$30.disabled = !canSaveCurrent());\n return _el$27;\n })();\n}\nfunction RecentMeetingsPanel(props) {\n return (() => {\n var _el$38 = _tmpl$1(), _el$40 = _el$38.firstChild.nextSibling;\n insert(_el$40, createComponent(Show, {\n get when() {\n return props.recentMeetings.length > 0;\n },\n get fallback() {\n return _tmpl$10();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.recentMeetings;\n },\n children: (meeting) => (() => {\n var _el$42 = _tmpl$7(), _el$43 = _el$42.firstChild, _el$44 = _el$43.nextSibling;\n _el$42.$$click = () => {\n props.onOpen(meeting);\n };\n insert(_el$43, () => meeting.title);\n insert(_el$44, () => meeting.updatedAt.slice(0, 10));\n return _el$42;\n })()\n });\n }\n }));\n return _el$38;\n })();\n}\nfunction MeetingList(props) {\n const summary = () => currentFilterSummary({\n folders: props.folders,\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$45 = _tmpl$12();\n insert(_el$45, createComponent(Show, {\n get fallback() {\n return createComponent(Show, {\n get fallback() {\n return (() => {\n var _el$47 = _tmpl$13();\n insert(_el$47, (() => {\n var _c$ = memo(() => !!summary());\n return () => _c$() ? `No meetings match ${summary()}.` : props.emptyHint || \"No meetings yet. Try Sync now.\";\n })());\n return _el$47;\n })();\n },\n get when() {\n return props.meetings.length > 0;\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.meetings;\n },\n children: (meeting) => (() => {\n var _el$48 = _tmpl$14(), _el$49 = _el$48.firstChild, _el$50 = _el$49.nextSibling, _el$51 = _el$50.nextSibling;\n _el$48.$$click = () => {\n props.onSelect(meeting.id);\n };\n insert(_el$49, () => meeting.title || meeting.id);\n insert(_el$50, (() => {\n var _c$2 = memo(() => !!meeting.tags.length);\n return () => _c$2() ? meeting.tags.map((tag) => `#${tag}`).join(\" \") : \"untagged\";\n })());\n insert(_el$51, (() => {\n var _c$3 = memo(() => !!meeting.updatedAt);\n return () => _c$3() ? meeting.updatedAt.slice(0, 10) : \"unknown\";\n })());\n createRenderEffect(() => setAttribute(_el$48, \"data-selected\", meeting.id === props.selectedMeetingId ? \"true\" : void 0));\n return _el$48;\n })()\n });\n }\n });\n },\n get when() {\n return props.error;\n },\n get children() {\n var _el$46 = _tmpl$11();\n insert(_el$46, () => props.error);\n return _el$46;\n }\n }));\n return _el$45;\n })();\n}\nfunction AppStatePanel(props) {\n const syncStatus = () => describeSyncStatus(props.appState?.sync ?? {});\n const authStatus = () => describeAuthStatus(props.appState?.auth);\n return (() => {\n var _el$52 = _tmpl$16(), _el$53 = _el$52.firstChild;\n _el$53.firstChild;\n var _el$56 = _el$53.nextSibling;\n insert(_el$53, createComponent(Show, {\n get fallback() {\n return _tmpl$17();\n },\n get when() {\n return props.appState;\n },\n children: (appState) => (() => {\n var _el$58 = _tmpl$18(), _el$59 = _el$58.firstChild, _el$61 = _el$59.firstChild.nextSibling, _el$62 = _el$59.nextSibling, _el$64 = _el$62.firstChild.nextSibling, _el$65 = _el$62.nextSibling, _el$67 = _el$65.firstChild.nextSibling, _el$68 = _el$65.nextSibling, _el$70 = _el$68.firstChild.nextSibling, _el$71 = _el$68.nextSibling, _el$73 = _el$71.firstChild.nextSibling, _el$74 = _el$71.nextSibling, _el$76 = _el$74.firstChild.nextSibling, _el$77 = _el$74.nextSibling, _el$79 = _el$77.firstChild.nextSibling, _el$80 = _el$77.nextSibling, _el$82 = _el$80.firstChild.nextSibling, _el$85 = _el$80.nextSibling.firstChild.nextSibling;\n insert(_el$61, () => appState().ui.surface);\n insert(_el$64, () => appState().ui.view);\n insert(_el$67, authStatus);\n insert(_el$70, syncStatus);\n insert(_el$73, (() => {\n var _c$4 = memo(() => !!appState().documents.loaded);\n return () => _c$4() ? String(appState().documents.count) : \"not loaded\";\n })());\n insert(_el$76, (() => {\n var _c$5 = memo(() => !!appState().folders.loaded);\n return () => _c$5() ? String(appState().folders.count) : \"not loaded\";\n })());\n insert(_el$79, (() => {\n var _c$6 = memo(() => !!appState().cache.loaded);\n return () => _c$6() ? `${appState().cache.transcriptCount} transcript sets` : appState().cache.configured ? \"configured\" : \"not configured\";\n })());\n insert(_el$82, (() => {\n var _c$7 = memo(() => !!appState().index.loaded);\n return () => _c$7() ? `${appState().index.meetingCount} meetings` : appState().index.available ? \"available\" : \"not built\";\n })());\n insert(_el$85, () => `${appState().automation.runCount} runs / ${appState().automation.pendingRunCount} pending runs / ${appState().automation.pendingArtefactCount} pending artefacts`);\n return _el$58;\n })()\n }), null);\n insert(_el$53, createComponent(Show, {\n get when() {\n return props.appState?.auth.lastError;\n },\n get children() {\n var _el$55 = _tmpl$15();\n insert(_el$55, () => props.appState?.auth.lastError);\n return _el$55;\n }\n }), null);\n insert(_el$56, () => props.statusLabel);\n createRenderEffect(() => setAttribute(_el$56, \"data-tone\", props.statusTone));\n return _el$52;\n })();\n}\nfunction SecurityPanel(props) {\n return createComponent(Show, {\n get when() {\n return props.visible;\n },\n get children() {\n var _el$86 = _tmpl$19(), _el$89 = _el$86.firstChild.nextSibling.firstChild, _el$91 = _el$89.nextSibling.firstChild, _el$92 = _el$91.nextSibling;\n _el$89.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onUnlock();\n }\n };\n _el$89.$$input = (event) => {\n props.onPasswordChange(event.currentTarget.value);\n };\n addEventListener(_el$91, \"click\", props.onUnlock, true);\n addEventListener(_el$92, \"click\", props.onLock, true);\n createRenderEffect(() => _el$89.value = props.password);\n return _el$86;\n }\n });\n}\nfunction AuthPanel(props) {\n return (() => {\n var _el$93 = _tmpl$20(), _el$95 = _el$93.firstChild.nextSibling;\n insert(_el$95, createComponent(Show, {\n get fallback() {\n return _tmpl$21();\n },\n get when() {\n return props.auth;\n },\n children: (auth) => (() => {\n var _el$97 = _tmpl$26(), _el$98 = _el$97.firstChild, _el$99 = _el$98.firstChild, _el$101 = _el$99.firstChild.nextSibling, _el$102 = _el$99.nextSibling, _el$104 = _el$102.firstChild.nextSibling, _el$105 = _el$102.nextSibling, _el$107 = _el$105.firstChild.nextSibling, _el$108 = _el$105.nextSibling, _el$110 = _el$108.firstChild.nextSibling, _el$113 = _el$108.nextSibling.firstChild.nextSibling, _el$121 = _el$98.nextSibling, _el$123 = _el$121.nextSibling.firstChild, _el$124 = _el$123.nextSibling, _el$125 = _el$124.nextSibling, _el$126 = _el$125.nextSibling, _el$127 = _el$126.nextSibling, _el$128 = _el$127.nextSibling, _el$129 = _el$128.nextSibling, _el$130 = _el$129.nextSibling;\n insert(_el$101, () => authModeLabel(auth().mode));\n insert(_el$104, () => auth().apiKeyAvailable ? \"available\" : \"missing\");\n insert(_el$107, () => auth().storedSessionAvailable ? \"available\" : \"missing\");\n insert(_el$110, () => auth().supabaseAvailable ? \"available\" : \"missing\");\n insert(_el$113, () => auth().refreshAvailable ? \"available\" : \"missing\");\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().clientId;\n },\n get children() {\n var _el$114 = _tmpl$22();\n _el$114.firstChild;\n insert(_el$114, () => auth().clientId, null);\n return _el$114;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().signInMethod;\n },\n get children() {\n var _el$116 = _tmpl$23();\n _el$116.firstChild;\n insert(_el$116, () => auth().signInMethod, null);\n return _el$116;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().supabasePath;\n },\n get children() {\n var _el$118 = _tmpl$24();\n _el$118.firstChild;\n insert(_el$118, () => auth().supabasePath, null);\n return _el$118;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().lastError;\n },\n get children() {\n var _el$120 = _tmpl$25();\n insert(_el$120, () => auth().lastError);\n return _el$120;\n }\n }), _el$121);\n _el$123.$$input = (event) => {\n props.onApiKeyDraftChange(event.currentTarget.value);\n };\n addEventListener(_el$124, \"click\", props.onSaveApiKey, true);\n addEventListener(_el$125, \"click\", props.onImportDesktopSession, true);\n addEventListener(_el$126, \"click\", props.onRefresh, true);\n _el$127.$$click = () => {\n props.onSwitchMode(\"api-key\");\n };\n _el$128.$$click = () => {\n props.onSwitchMode(\"stored-session\");\n };\n _el$129.$$click = () => {\n props.onSwitchMode(\"supabase-file\");\n };\n addEventListener(_el$130, \"click\", props.onLogout, true);\n createRenderEffect((_p$) => {\n var _v$ = !auth().supabaseAvailable, _v$2 = !auth().storedSessionAvailable || !auth().refreshAvailable, _v$3 = !auth().apiKeyAvailable || auth().mode === \"api-key\", _v$4 = !auth().storedSessionAvailable || auth().mode === \"stored-session\", _v$5 = !auth().supabaseAvailable || auth().mode === \"supabase-file\", _v$6 = !auth().apiKeyAvailable && !auth().storedSessionAvailable;\n _v$ !== _p$.e && (_el$125.disabled = _p$.e = _v$);\n _v$2 !== _p$.t && (_el$126.disabled = _p$.t = _v$2);\n _v$3 !== _p$.a && (_el$127.disabled = _p$.a = _v$3);\n _v$4 !== _p$.o && (_el$128.disabled = _p$.o = _v$4);\n _v$5 !== _p$.i && (_el$129.disabled = _p$.i = _v$5);\n _v$6 !== _p$.n && (_el$130.disabled = _p$.n = _v$6);\n return _p$;\n }, {\n e: void 0,\n t: void 0,\n a: void 0,\n o: void 0,\n i: void 0,\n n: void 0\n });\n createRenderEffect(() => _el$123.value = props.apiKeyDraft);\n return _el$97;\n })()\n }));\n return _el$93;\n })();\n}\nfunction ExportJobsPanel(props) {\n return (() => {\n var _el$131 = _tmpl$27(), _el$133 = _el$131.firstChild.nextSibling;\n insert(_el$133, createComponent(Show, {\n get when() {\n return props.jobs.length > 0;\n },\n get fallback() {\n return _tmpl$28();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.jobs.slice(0, 6);\n },\n children: (job) => (() => {\n var _el$135 = _tmpl$31(), _el$136 = _el$135.firstChild, _el$137 = _el$136.firstChild, _el$138 = _el$137.firstChild, _el$139 = _el$138.firstChild, _el$140 = _el$138.nextSibling, _el$141 = _el$137.nextSibling, _el$142 = _el$136.nextSibling, _el$143 = _el$142.nextSibling;\n _el$143.firstChild;\n var _el$145 = _el$143.nextSibling;\n _el$145.firstChild;\n var _el$148 = _el$145.nextSibling;\n insert(_el$138, () => job.kind, _el$139);\n insert(_el$140, () => job.id);\n insert(_el$141, () => job.status);\n insert(_el$142, () => `Format: ${job.format} • ${scopeLabel(job.scope)} • ${job.itemCount > 0 ? `${job.completedCount}/${job.itemCount} items` : \"0 items\"} • Written: ${job.written}`);\n insert(_el$143, () => job.startedAt.slice(0, 19), null);\n insert(_el$145, () => job.outputDir, null);\n insert(_el$135, createComponent(Show, {\n get when() {\n return job.error;\n },\n get children() {\n var _el$147 = _tmpl$29();\n insert(_el$147, () => job.error);\n return _el$147;\n }\n }), _el$148);\n insert(_el$148, createComponent(Show, {\n get when() {\n return job.status !== \"running\";\n },\n get children() {\n var _el$149 = _tmpl$30();\n _el$149.$$click = () => {\n props.onRerun(job.id);\n };\n return _el$149;\n }\n }));\n createRenderEffect(() => setAttribute(_el$141, \"data-status\", job.status));\n return _el$135;\n })()\n });\n }\n }));\n return _el$131;\n })();\n}\nfunction AutomationRunsPanel(props) {\n return (() => {\n var _el$150 = _tmpl$32(), _el$152 = _el$150.firstChild.nextSibling;\n insert(_el$152, createComponent(Show, {\n get when() {\n return props.runs.length > 0;\n },\n get fallback() {\n return _tmpl$33();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.runs.slice(0, 6);\n },\n children: (run) => (() => {\n var _el$154 = _tmpl$36(), _el$155 = _el$154.firstChild, _el$156 = _el$155.firstChild, _el$157 = _el$156.firstChild, _el$158 = _el$157.nextSibling, _el$159 = _el$156.nextSibling, _el$160 = _el$155.nextSibling, _el$161 = _el$160.nextSibling, _el$165 = _el$161.nextSibling;\n insert(_el$157, () => run.actionName);\n insert(_el$158, () => `${run.ruleName} • ${run.id}`);\n insert(_el$159, () => run.status);\n insert(_el$160, () => `${run.title} • ${run.eventKind}`);\n insert(_el$161, () => `Started: ${run.startedAt.slice(0, 19)}`);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.prompt;\n },\n get children() {\n var _el$162 = _tmpl$29();\n insert(_el$162, () => run.prompt);\n return _el$162;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.result;\n },\n get children() {\n var _el$163 = _tmpl$29();\n insert(_el$163, () => run.result);\n return _el$163;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.error;\n },\n get children() {\n var _el$164 = _tmpl$29();\n insert(_el$164, () => run.error);\n return _el$164;\n }\n }), _el$165);\n insert(_el$165, createComponent(Show, {\n get when() {\n return run.status === \"pending\";\n },\n get children() {\n return [(() => {\n var _el$166 = _tmpl$34();\n _el$166.$$click = () => {\n props.onApprove(run.id);\n };\n return _el$166;\n })(), (() => {\n var _el$167 = _tmpl$35();\n _el$167.$$click = () => {\n props.onReject(run.id);\n };\n return _el$167;\n })()];\n }\n }));\n createRenderEffect(() => setAttribute(_el$159, \"data-status\", run.status));\n return _el$154;\n })()\n });\n }\n }));\n return _el$150;\n })();\n}\nfunction AutomationArtefactsPanel(props) {\n return (() => {\n var _el$168 = _tmpl$37(), _el$170 = _el$168.firstChild.nextSibling;\n insert(_el$170, createComponent(Show, {\n get when() {\n return props.artefacts.length > 0;\n },\n get fallback() {\n return _tmpl$38();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.artefacts.slice(0, 10);\n },\n children: (artefact) => (() => {\n var _el$172 = _tmpl$39(), _el$173 = _el$172.firstChild, _el$174 = _el$173.firstChild, _el$175 = _el$174.firstChild, _el$176 = _el$175.nextSibling, _el$177 = _el$174.nextSibling, _el$178 = _el$173.nextSibling, _el$180 = _el$178.nextSibling;\n _el$172.$$click = () => {\n props.onSelect(artefact.id);\n };\n insert(_el$175, () => artefact.structured.title);\n insert(_el$176, () => `${artefact.kind} • ${artefact.ruleName}`);\n insert(_el$177, () => artefact.status);\n insert(_el$178, () => artefact.meetingId);\n insert(_el$172, createComponent(Show, {\n get when() {\n return artefact.structured.summary;\n },\n get children() {\n var _el$179 = _tmpl$29();\n insert(_el$179, () => artefact.structured.summary);\n return _el$179;\n }\n }), _el$180);\n insert(_el$180, () => `Updated: ${artefact.updatedAt.slice(0, 19)}`);\n createRenderEffect((_p$) => {\n var _v$7 = artefact.id === props.selectedArtefactId ? \"true\" : void 0, _v$8 = artefact.status;\n _v$7 !== _p$.e && setAttribute(_el$172, \"data-selected\", _p$.e = _v$7);\n _v$8 !== _p$.t && setAttribute(_el$177, \"data-status\", _p$.t = _v$8);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n return _el$172;\n })()\n });\n }\n }));\n return _el$168;\n })();\n}\nfunction ArtefactReviewPanel(props) {\n return (() => {\n var _el$181 = _tmpl$40();\n _el$181.firstChild;\n insert(_el$181, createComponent(Show, {\n get when() {\n return props.artefact;\n },\n get fallback() {\n return (() => {\n var _el$183 = _tmpl$41();\n insert(_el$183, () => props.error || \"Select an automation artefact to review it.\");\n return _el$183;\n })();\n },\n children: (artefact) => (() => {\n var _el$184 = _tmpl$43(), _el$185 = _el$184.firstChild, _el$186 = _el$185.firstChild, _el$187 = _el$186.nextSibling, _el$188 = _el$187.nextSibling, _el$189 = _el$188.nextSibling, _el$213 = _el$185.nextSibling, _el$215 = _el$213.firstChild.nextSibling;\n insert(_el$186, () => `Status: ${artefact().status}`);\n insert(_el$187, () => `Kind: ${artefact().kind}`);\n insert(_el$188, () => `Meeting: ${artefact().meetingId}`);\n insert(_el$189, () => `Provider: ${artefact().provider}/${artefact().model}`);\n insert(_el$184, createComponent(Show, {\n get when() {\n return !props.error;\n },\n get fallback() {\n return (() => {\n var _el$216 = _tmpl$44();\n insert(_el$216, () => props.error);\n return _el$216;\n })();\n },\n get children() {\n var _el$190 = _tmpl$42(), _el$191 = _el$190.firstChild, _el$193 = _el$191.firstChild.nextSibling, _el$196 = _el$191.nextSibling.firstChild.nextSibling, _el$198 = _el$196.firstChild.nextSibling, _el$199 = _el$196.nextSibling, _el$201 = _el$199.firstChild.nextSibling, _el$202 = _el$199.nextSibling, _el$204 = _el$202.firstChild.nextSibling, _el$205 = _el$202.nextSibling, _el$207 = _el$205.firstChild.nextSibling, _el$209 = _el$205.nextSibling.firstChild, _el$210 = _el$209.nextSibling, _el$211 = _el$210.nextSibling, _el$212 = _el$211.nextSibling;\n insert(_el$193, () => props.bundle?.meeting.noteMarkdown || \"(No existing meeting notes)\");\n _el$198.$$input = (event) => {\n props.onDraftTitleChange(event.currentTarget.value);\n };\n _el$201.$$input = (event) => {\n props.onDraftSummaryChange(event.currentTarget.value);\n };\n insert(_el$201, () => props.draftSummary);\n _el$204.$$input = (event) => {\n props.onDraftMarkdownChange(event.currentTarget.value);\n };\n insert(_el$204, () => props.draftMarkdown);\n _el$207.$$input = (event) => {\n props.onReviewNoteChange(event.currentTarget.value);\n };\n insert(_el$207, () => props.reviewNote);\n addEventListener(_el$209, \"click\", props.onSave, true);\n addEventListener(_el$210, \"click\", props.onApprove, true);\n addEventListener(_el$211, \"click\", props.onReject, true);\n addEventListener(_el$212, \"click\", props.onRerun, true);\n createRenderEffect((_p$) => {\n var _v$9 = artefact().status === \"superseded\", _v$0 = artefact().status === \"superseded\";\n _v$9 !== _p$.e && (_el$210.disabled = _p$.e = _v$9);\n _v$0 !== _p$.t && (_el$211.disabled = _p$.t = _v$0);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n createRenderEffect(() => _el$198.value = props.draftTitle);\n return _el$190;\n }\n }), _el$213);\n insert(_el$215, createComponent(For, {\n get each() {\n return artefact().history.slice().reverse();\n },\n children: (entry) => (() => {\n var _el$217 = _tmpl$45(), _el$219 = _el$217.firstChild.firstChild, _el$220 = _el$219.nextSibling;\n insert(_el$219, () => entry.action);\n insert(_el$220, () => entry.at.slice(0, 19));\n insert(_el$217, createComponent(Show, {\n get when() {\n return entry.note;\n },\n get children() {\n var _el$221 = _tmpl$29();\n insert(_el$221, () => entry.note);\n return _el$221;\n }\n }), null);\n return _el$217;\n })()\n }));\n return _el$184;\n })()\n }), null);\n return _el$181;\n })();\n}\nfunction Workspace(props) {\n const parsedTab = () => parseWorkspaceTab(props.tab);\n const details = () => {\n if (!props.selectedMeeting) return null;\n return workspaceBody(props.bundle, props.selectedMeeting, parsedTab());\n };\n return [(() => {\n var _el$222 = _tmpl$46(), _el$223 = _el$222.firstChild;\n insert(_el$222, createComponent(For, {\n each: [\n \"notes\",\n \"transcript\",\n \"metadata\",\n \"raw\"\n ],\n children: (tab) => (() => {\n var _el$224 = _tmpl$47();\n _el$224.$$click = () => {\n props.onSelectTab(tab);\n };\n insert(_el$224, tab === \"notes\" ? \"Notes\" : tab === \"transcript\" ? \"Transcript\" : tab === \"metadata\" ? \"Metadata\" : \"Raw\");\n createRenderEffect(() => setAttribute(_el$224, \"data-selected\", parsedTab() === tab ? \"true\" : void 0));\n return _el$224;\n })()\n }), _el$223);\n return _el$222;\n })(), createComponent(Show, {\n get when() {\n return props.selectedMeeting;\n },\n get fallback() {\n return (() => {\n var _el$225 = _tmpl$44();\n insert(_el$225, () => props.detailError || \"Select a meeting to inspect its notes and transcript.\");\n return _el$225;\n })();\n },\n children: (meeting) => [(() => {\n var _el$226 = _tmpl$48(), _el$227 = _el$226.firstChild, _el$228 = _el$227.nextSibling, _el$229 = _el$228.nextSibling;\n insert(_el$227, () => `ID: ${meeting().meeting.id}`);\n insert(_el$228, () => `Source: ${meeting().meeting.noteContentSource}`);\n insert(_el$229, () => `Transcript: ${meeting().meeting.transcriptSegmentCount} segments`);\n return _el$226;\n })(), createComponent(Show, {\n get when() {\n return !props.detailError;\n },\n get fallback() {\n return (() => {\n var _el$238 = _tmpl$44();\n insert(_el$238, () => props.detailError);\n return _el$238;\n })();\n },\n get children() {\n var _el$230 = _tmpl$49(), _el$232 = _el$230.firstChild.firstChild, _el$234 = _el$232.firstChild.nextSibling, _el$236 = _el$232.nextSibling.firstChild, _el$237 = _el$236.nextSibling;\n insert(_el$234, () => metadataLines(meeting()));\n insert(_el$236, () => details()?.title);\n insert(_el$237, () => details()?.body);\n return _el$230;\n }\n })]\n })];\n}\ndelegateEvents([\n \"input\",\n \"keydown\",\n \"click\"\n]);\n//#endregion\n//#region src/web-app/App.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$ = /* @__PURE__ */ template(`<div class=shell><aside class=\"pane sidebar\"></aside><main class=\"pane detail\"><section class=toolbar><div class=toolbar-actions><button class=\"button button--primary\"type=button>Sync now</button><button class=\"button button--secondary\"type=button>Clear Filters</button><button class=\"button button--secondary\"type=button>Export Notes</button><button class=\"button button--secondary\"type=button>Export Transcripts</button></div><p>Solid-powered web workspace on top of the same local server, sync loop, and shared app contracts.`);\nfunction browserConfig() {\n return { passwordRequired: Boolean(window.__GRANOLA_SERVER__?.passwordRequired) };\n}\nasync function requestJson(path, init) {\n const response = await fetch(path, init);\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const error = typeof payload.error === \"string\" && payload.error.trim() ? payload.error : response.statusText || \"Request failed\";\n throw new Error(error);\n }\n return payload;\n}\nfunction App() {\n const startup = startupSelectionFromSearch(window.location.search);\n const initialPreferences = parseWorkspacePreferences(window.localStorage.getItem(granolaWebWorkspaceStorageKey));\n const [state, setState] = createStore({\n apiKeyDraft: \"\",\n automationArtefactDraftMarkdown: \"\",\n automationArtefactDraftSummary: \"\",\n automationArtefactDraftTitle: \"\",\n automationArtefactError: \"\",\n automationArtefacts: [],\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n listError: \"\",\n meetingSource: \"live\",\n meetings: [],\n quickOpen: \"\",\n recentMeetings: initialPreferences.recentMeetings,\n savedFilters: initialPreferences.savedFilters,\n search: \"\",\n selectedAutomationArtefactId: null,\n selectedFolderId: startup.folderId || null,\n selectedMeetingBundle: null,\n selectedMeetingId: startup.meetingId || null,\n selectedMeeting: null,\n reviewNote: \"\",\n serverLocked: browserConfig().passwordRequired,\n serverPassword: \"\",\n sort: \"updated-desc\",\n statusLabel: browserConfig().passwordRequired ? \"Server locked\" : \"Connecting…\",\n statusTone: browserConfig().passwordRequired ? \"error\" : \"idle\",\n updatedFrom: \"\",\n updatedTo: \"\",\n workspaceTab: parseWorkspaceTab(startup.workspaceTab)\n });\n let client = null;\n let unsubscribe;\n const setStatus = (label, tone = \"idle\") => {\n setState({\n statusLabel: label,\n statusTone: tone\n });\n };\n const updatePreferences = (updater) => {\n const next = updater({\n recentMeetings: state.recentMeetings,\n savedFilters: state.savedFilters\n });\n window.localStorage.setItem(granolaWebWorkspaceStorageKey, serialiseWorkspacePreferences(next));\n setState(\"recentMeetings\", next.recentMeetings);\n setState(\"savedFilters\", next.savedFilters);\n };\n const mergeAuthState = async (authState) => {\n if (!client) return;\n const nextState = client.getState();\n if (authState) {\n setState(\"appState\", {\n ...nextState,\n auth: authState\n });\n return;\n }\n try {\n setState(\"appState\", {\n ...nextState,\n auth: await client.inspectAuth()\n });\n } catch {\n setState(\"appState\", nextState);\n }\n };\n const detachClient = async () => {\n unsubscribe?.();\n unsubscribe = void 0;\n if (client) {\n await client.close().catch(() => void 0);\n client = null;\n }\n };\n const attachClient = async () => {\n await detachClient();\n client = await createGranolaServerClient(window.location.origin);\n setState(\"appState\", client.getState());\n unsubscribe = client.subscribe((event) => {\n setState(\"appState\", event.state);\n });\n await mergeAuthState();\n };\n const loadFolders = async (refresh = false) => {\n if (!client) return;\n try {\n setState(\"folderError\", \"\");\n const result = await client.listFolders({\n forceRefresh: refresh,\n limit: 100\n });\n setState(\"folders\", result.folders);\n if (state.selectedFolderId && !result.folders.some((folder) => folder.id === state.selectedFolderId)) setState(\"selectedFolderId\", null);\n } catch (error) {\n setState(\"folderError\", error instanceof Error ? error.message : String(error));\n setState(\"folders\", []);\n setState(\"selectedFolderId\", null);\n }\n };\n const loadAutomationRuns = async () => {\n if (!client) return;\n try {\n setState(\"automationRuns\", (await client.listAutomationRuns({ limit: 20 })).runs);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const syncSelectedArtefact = (artefacts, options = {}) => {\n const preferred = (options.preferredId ? artefacts.find((candidate) => candidate.id === options.preferredId) : void 0) ?? (options.preferredMeetingId ? artefacts.find((candidate) => candidate.meetingId === options.preferredMeetingId && candidate.status === \"generated\") : void 0) ?? artefacts.find((candidate) => candidate.status === \"generated\") ?? artefacts[0];\n setState(\"selectedAutomationArtefactId\", preferred?.id ?? null);\n setState(\"automationArtefactDraftTitle\", preferred?.structured.title ?? \"\");\n setState(\"automationArtefactDraftSummary\", preferred?.structured.summary ?? \"\");\n setState(\"automationArtefactDraftMarkdown\", preferred?.structured.markdown ?? \"\");\n setState(\"reviewNote\", \"\");\n };\n const loadAutomationArtefacts = async (options = {}) => {\n if (!client) return;\n try {\n setState(\"automationArtefactError\", \"\");\n const result = await client.listAutomationArtefacts({ limit: 30 });\n setState(\"automationArtefacts\", result.artefacts);\n syncSelectedArtefact(result.artefacts, {\n preferredId: options.preferredId ?? state.selectedAutomationArtefactId,\n preferredMeetingId: options.preferredMeetingId ?? state.selectedMeetingId\n });\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setState(\"automationArtefacts\", []);\n syncSelectedArtefact([]);\n }\n };\n const loadMeeting = async (meetingId) => {\n if (!client) return;\n setState(\"selectedMeetingId\", meetingId);\n try {\n setState(\"detailError\", \"\");\n const bundle = await client.getMeeting(meetingId);\n setState(\"selectedMeetingBundle\", bundle);\n setState(\"selectedMeeting\", bundle.meeting);\n updatePreferences((preferences) => rememberRecentMeeting(preferences, bundle.meeting.meeting));\n await loadAutomationArtefacts({\n preferredId: state.selectedAutomationArtefactId,\n preferredMeetingId: bundle.document.id\n });\n } catch (error) {\n setState(\"selectedMeetingBundle\", null);\n setState(\"selectedMeeting\", null);\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadMeetings = async (options = {}) => {\n if (!client) return;\n try {\n setState(\"listError\", \"\");\n const result = await client.listMeetings({\n folderId: state.selectedFolderId || void 0,\n forceRefresh: options.refresh,\n limit: 100,\n search: state.search || void 0,\n sort: state.sort,\n updatedFrom: state.updatedFrom || void 0,\n updatedTo: state.updatedTo || void 0\n });\n const preferredMeetingId = options.preferredMeetingId ?? state.selectedMeetingId;\n const nextMeetingId = selectMeetingId(result.meetings, preferredMeetingId);\n setState(\"meetings\", result.meetings);\n setState(\"meetingSource\", result.source);\n setState(\"selectedMeetingId\", nextMeetingId);\n if (nextMeetingId) await loadMeeting(nextMeetingId);\n else {\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", \"\");\n await loadAutomationArtefacts({\n preferredId: state.selectedAutomationArtefactId,\n preferredMeetingId: null\n });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n setState(\"listError\", message);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", message);\n }\n };\n const refreshAll = async (forceRefresh = false) => {\n if (!client) await attachClient();\n setStatus(forceRefresh ? \"Syncing…\" : \"Refreshing…\", \"busy\");\n if (forceRefresh) await client?.sync({\n forceRefresh: true,\n foreground: true\n });\n await Promise.all([\n loadFolders(forceRefresh),\n loadAutomationRuns(),\n loadAutomationArtefacts(),\n mergeAuthState()\n ]);\n await loadMeetings({ refresh: forceRefresh });\n setState(\"serverLocked\", false);\n setStatus(forceRefresh ? \"Sync complete\" : state.meetingSource === \"index\" ? \"Loaded from index\" : \"Connected\", \"ok\");\n };\n const connectAndRefresh = async (forceRefresh = false) => {\n try {\n await refreshAll(forceRefresh);\n } catch (error) {\n setStatus(\"Connection failed\", \"error\");\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const quickOpenMeeting = async () => {\n if (!client) return;\n const query = state.quickOpen.trim();\n if (!query) {\n setStatus(\"Enter a title or id\", \"error\");\n return;\n }\n setStatus(\"Opening meeting…\", \"busy\");\n try {\n const bundle = await client.findMeeting(query);\n setState(\"selectedFolderId\", bundle.meeting.meeting.folders[0]?.id || null);\n setState(\"search\", \"\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n await loadMeetings({ preferredMeetingId: bundle.document.id });\n setStatus(\"Connected\", \"ok\");\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Quick open failed\", \"error\");\n }\n };\n const saveApiKey = async () => {\n if (!client) return;\n if (!state.apiKeyDraft.trim()) {\n setStatus(\"Enter a Granola API key\", \"error\");\n return;\n }\n setStatus(\"Saving API key…\", \"busy\");\n try {\n const auth = await client.loginAuth({ apiKey: state.apiKeyDraft.trim() });\n setState(\"apiKeyDraft\", \"\");\n await mergeAuthState(auth);\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"API key save failed\", \"error\");\n }\n };\n const importDesktopSession = async () => {\n if (!client) return;\n setStatus(\"Importing desktop session…\", \"busy\");\n try {\n await mergeAuthState(await client.loginAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Auth import failed\", \"error\");\n }\n };\n const refreshAuth = async () => {\n if (!client) return;\n setStatus(\"Refreshing session…\", \"busy\");\n try {\n await mergeAuthState(await client.refreshAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Refresh failed\", \"error\");\n }\n };\n const switchAuthMode = async (mode) => {\n if (!client) return;\n setStatus(\"Switching auth source…\", \"busy\");\n try {\n await mergeAuthState(await client.switchAuthMode(mode));\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Switch failed\", \"error\");\n }\n };\n const logout = async () => {\n if (!client) return;\n setStatus(\"Signing out…\", \"busy\");\n try {\n await mergeAuthState(await client.logoutAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Sign out failed\", \"error\");\n }\n };\n const exportNotes = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder notes…\" : \"Exporting notes…\", \"busy\");\n try {\n await client.exportNotes(\"markdown\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const clearFilters = async () => {\n setState(\"search\", \"\");\n setState(\"sort\", \"updated-desc\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n setState(\"selectedFolderId\", null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(\"Filters cleared\", \"ok\");\n };\n const saveCurrentFilter = () => {\n updatePreferences((preferences) => saveWorkspaceFilter(preferences, {\n folders: state.folders,\n search: state.search,\n selectedFolderId: state.selectedFolderId,\n sort: state.sort,\n updatedFrom: state.updatedFrom,\n updatedTo: state.updatedTo\n }, { idFactory: () => `filter-${Date.now()}` }));\n setStatus(\"Saved filter\", \"ok\");\n };\n const applySavedFilterPreset = async (id) => {\n const preset = state.savedFilters.find((candidate) => candidate.id === id);\n if (!preset) return;\n const nextFilters = applyWorkspaceFilter(preset);\n setState(\"search\", nextFilters.search);\n setState(\"selectedFolderId\", nextFilters.selectedFolderId);\n setState(\"sort\", nextFilters.sort);\n setState(\"updatedFrom\", nextFilters.updatedFrom);\n setState(\"updatedTo\", nextFilters.updatedTo);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(`Applied ${preset.label}`, \"ok\");\n };\n const removeSavedFilterPreset = (id) => {\n updatePreferences((preferences) => removeWorkspaceFilter(preferences, id));\n setStatus(\"Removed saved filter\", \"ok\");\n };\n const openRecentMeeting = async (meetingId, folderId) => {\n if (folderId !== void 0) {\n setState(\"selectedFolderId\", folderId || null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings({ preferredMeetingId: meetingId });\n return;\n }\n await loadMeeting(meetingId);\n };\n const meetingEmptyHint = () => {\n if (!state.appState) return \"Connect to the local server to load meetings.\";\n if (state.appState.auth.lastError) return \"Resolve auth first, then sync again.\";\n if (!state.appState.documents.loaded && !state.appState.sync.lastCompletedAt) return \"Run Sync now to populate your local meeting index.\";\n return \"Try a different folder or filter, or sync again.\";\n };\n const exportTranscripts = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder transcripts…\" : \"Exporting transcripts…\", \"busy\");\n try {\n await client.exportTranscripts(\"text\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const rerunJob = async (jobId) => {\n if (!client) return;\n setStatus(\"Rerunning export…\", \"busy\");\n try {\n await client.rerunExportJob(jobId);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Rerun failed\", \"error\");\n }\n };\n const selectedAutomationArtefact = () => state.automationArtefacts.find((artefact) => artefact.id === state.selectedAutomationArtefactId) || null;\n const resolveAutomationRun = async (id, decision) => {\n if (!client) return;\n setStatus(decision === \"approve\" ? \"Approving automation…\" : \"Rejecting automation…\", \"busy\");\n try {\n await client.resolveAutomationRun(id, decision);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Automation decision failed\", \"error\");\n }\n };\n const selectAutomationArtefact = async (id) => {\n if (!client) return;\n try {\n const artefact = state.automationArtefacts.find((candidate) => candidate.id === id) ?? await client.getAutomationArtefact(id);\n setState(\"selectedAutomationArtefactId\", artefact.id);\n setState(\"automationArtefactDraftTitle\", artefact.structured.title);\n setState(\"automationArtefactDraftSummary\", artefact.structured.summary ?? \"\");\n setState(\"automationArtefactDraftMarkdown\", artefact.structured.markdown);\n setState(\"reviewNote\", \"\");\n if (artefact.meetingId !== state.selectedMeetingId) await loadMeeting(artefact.meetingId);\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unable to open artefact\", \"error\");\n }\n };\n const saveAutomationArtefact = async () => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(\"Saving artefact edits…\", \"busy\");\n try {\n const artefact = await client.updateAutomationArtefact(state.selectedAutomationArtefactId, {\n markdown: state.automationArtefactDraftMarkdown,\n note: state.reviewNote || void 0,\n summary: state.automationArtefactDraftSummary,\n title: state.automationArtefactDraftTitle\n });\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(\"Artefact updated\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact save failed\", \"error\");\n }\n };\n const resolveAutomationArtefact = async (decision) => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(decision === \"approve\" ? \"Approving artefact…\" : \"Rejecting artefact…\", \"busy\");\n try {\n const artefact = await client.resolveAutomationArtefact(state.selectedAutomationArtefactId, decision, { note: state.reviewNote || void 0 });\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(decision === \"approve\" ? \"Artefact approved\" : \"Artefact rejected\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact decision failed\", \"error\");\n }\n };\n const rerunAutomationArtefact = async () => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(\"Rerunning artefact pipeline…\", \"busy\");\n try {\n const artefact = await client.rerunAutomationArtefact(state.selectedAutomationArtefactId);\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(\"Artefact rerun complete\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact rerun failed\", \"error\");\n }\n };\n const unlockServer = async () => {\n if (!state.serverPassword.trim()) {\n setStatus(\"Enter the server password\", \"error\");\n return;\n }\n setStatus(\"Unlocking server…\", \"busy\");\n try {\n await requestJson(\"/auth/unlock\", {\n body: JSON.stringify({ password: state.serverPassword }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n setState(\"serverPassword\", \"\");\n setState(\"serverLocked\", false);\n await connectAndRefresh(true);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unlock failed\", \"error\");\n }\n };\n const lockServer = async () => {\n try {\n await requestJson(\"/auth/lock\", { method: \"POST\" });\n } catch {}\n await detachClient();\n setState({\n appState: null,\n automationArtefactDraftMarkdown: \"\",\n automationArtefactDraftSummary: \"\",\n automationArtefactDraftTitle: \"\",\n automationArtefactError: \"\",\n automationArtefacts: [],\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n listError: \"\",\n meetings: [],\n reviewNote: \"\",\n selectedAutomationArtefactId: null,\n selectedFolderId: null,\n selectedMeeting: null,\n selectedMeetingBundle: null,\n selectedMeetingId: null,\n serverLocked: true,\n serverPassword: \"\"\n });\n setStatus(\"Server locked\", \"error\");\n };\n createEffect(() => {\n const nextPath = buildBrowserUrlPath(window.location.href, {\n selectedFolderId: state.selectedFolderId,\n selectedMeetingId: state.selectedMeetingId,\n workspaceTab: state.workspaceTab\n });\n if (nextPath !== `${window.location.pathname}${window.location.search}${window.location.hash}`) history.replaceState(null, \"\", nextPath);\n });\n createEffect(() => {\n if (!state.appState?.automation.loaded || !client) return;\n loadAutomationRuns();\n loadAutomationArtefacts();\n });\n onMount(() => {\n const onKeyDown = (event) => {\n const target = event.target;\n if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return;\n const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);\n if (nextTab) setState(\"workspaceTab\", nextTab);\n };\n document.addEventListener(\"keydown\", onKeyDown);\n onCleanup(() => {\n document.removeEventListener(\"keydown\", onKeyDown);\n });\n if (!state.serverLocked) connectAndRefresh();\n });\n onCleanup(() => {\n detachClient();\n });\n return (() => {\n var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$6 = _el$4.firstChild.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling;\n insert(_el$2, createComponent(ToolbarFilters, {\n onQuickOpen: () => {\n quickOpenMeeting();\n },\n onQuickOpenInput: (value) => {\n setState(\"quickOpen\", value);\n },\n onSearchInput: (value) => {\n setState(\"search\", value.trim());\n loadMeetings();\n },\n onSortChange: (value) => {\n setState(\"sort\", value);\n loadMeetings();\n },\n onUpdatedFromChange: (value) => {\n setState(\"updatedFrom\", value);\n loadMeetings();\n },\n onUpdatedToChange: (value) => {\n setState(\"updatedTo\", value);\n loadMeetings();\n },\n get quickOpen() {\n return state.quickOpen;\n },\n get search() {\n return state.search;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(SavedFiltersPanel, {\n get folders() {\n return state.folders;\n },\n onApply: (preset) => {\n applySavedFilterPreset(preset.id);\n },\n onRemove: removeSavedFilterPreset,\n onSaveCurrent: saveCurrentFilter,\n get savedFilters() {\n return state.savedFilters;\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(RecentMeetingsPanel, {\n onOpen: (meeting) => {\n openRecentMeeting(meeting.id, meeting.folderId);\n },\n get recentMeetings() {\n return state.recentMeetings;\n }\n }), null);\n insert(_el$2, createComponent(FolderList, {\n get error() {\n return state.folderError;\n },\n get folders() {\n return state.folders;\n },\n onSelect: (folderId) => {\n setState(\"selectedFolderId\", folderId);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n loadMeetings();\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n }\n }), null);\n insert(_el$2, createComponent(MeetingList, {\n get error() {\n return state.listError;\n },\n get emptyHint() {\n return meetingEmptyHint();\n },\n get folders() {\n return state.folders;\n },\n get meetings() {\n return state.meetings;\n },\n onSelect: (meetingId) => {\n loadMeeting(meetingId);\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get selectedMeetingId() {\n return state.selectedMeetingId;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$3, createComponent(AppStatePanel, {\n get appState() {\n return state.appState;\n },\n get statusLabel() {\n return state.statusLabel;\n },\n get statusTone() {\n return state.statusTone;\n }\n }), _el$4);\n _el$6.$$click = () => {\n connectAndRefresh(true);\n };\n _el$7.$$click = () => {\n clearFilters();\n };\n _el$8.$$click = () => {\n exportNotes();\n };\n _el$9.$$click = () => {\n exportTranscripts();\n };\n insert(_el$3, createComponent(SecurityPanel, {\n onLock: () => {\n lockServer();\n },\n onPasswordChange: (value) => {\n setState(\"serverPassword\", value);\n },\n onUnlock: () => {\n unlockServer();\n },\n get password() {\n return state.serverPassword;\n },\n get visible() {\n return state.serverLocked;\n }\n }), null);\n insert(_el$3, createComponent(AuthPanel, {\n get apiKeyDraft() {\n return state.apiKeyDraft;\n },\n get auth() {\n return state.appState?.auth;\n },\n onApiKeyDraftChange: (value) => {\n setState(\"apiKeyDraft\", value);\n },\n onImportDesktopSession: () => {\n importDesktopSession();\n },\n onLogout: () => {\n logout();\n },\n onRefresh: () => {\n refreshAuth();\n },\n onSaveApiKey: () => {\n saveApiKey();\n },\n onSwitchMode: (mode) => {\n switchAuthMode(mode);\n }\n }), null);\n insert(_el$3, createComponent(ExportJobsPanel, {\n get jobs() {\n return state.appState?.exports.jobs || [];\n },\n onRerun: (jobId) => {\n rerunJob(jobId);\n }\n }), null);\n insert(_el$3, createComponent(AutomationRunsPanel, {\n onApprove: (runId) => {\n resolveAutomationRun(runId, \"approve\");\n },\n onReject: (runId) => {\n resolveAutomationRun(runId, \"reject\");\n },\n get runs() {\n return state.automationRuns;\n }\n }), null);\n insert(_el$3, createComponent(AutomationArtefactsPanel, {\n get artefacts() {\n return state.automationArtefacts;\n },\n onSelect: (artefactId) => {\n selectAutomationArtefact(artefactId);\n },\n get selectedArtefactId() {\n return state.selectedAutomationArtefactId;\n }\n }), null);\n insert(_el$3, createComponent(ArtefactReviewPanel, {\n get artefact() {\n return selectedAutomationArtefact();\n },\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get draftMarkdown() {\n return state.automationArtefactDraftMarkdown;\n },\n get draftSummary() {\n return state.automationArtefactDraftSummary;\n },\n get draftTitle() {\n return state.automationArtefactDraftTitle;\n },\n get error() {\n return state.automationArtefactError;\n },\n onApprove: () => {\n resolveAutomationArtefact(\"approve\");\n },\n onDraftMarkdownChange: (value) => {\n setState(\"automationArtefactDraftMarkdown\", value);\n },\n onDraftSummaryChange: (value) => {\n setState(\"automationArtefactDraftSummary\", value);\n },\n onDraftTitleChange: (value) => {\n setState(\"automationArtefactDraftTitle\", value);\n },\n onReject: () => {\n resolveAutomationArtefact(\"reject\");\n },\n onRerun: () => {\n rerunAutomationArtefact();\n },\n onReviewNoteChange: (value) => {\n setState(\"reviewNote\", value);\n },\n onSave: () => {\n saveAutomationArtefact();\n },\n get reviewNote() {\n return state.reviewNote;\n }\n }), null);\n insert(_el$3, createComponent(Workspace, {\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get detailError() {\n return state.detailError;\n },\n onSelectTab: (tab) => {\n setState(\"workspaceTab\", tab);\n },\n get selectedMeeting() {\n return state.selectedMeeting;\n },\n get tab() {\n return state.workspaceTab;\n }\n }), null);\n return _el$;\n })();\n}\ndelegateEvents([\"click\"]);\n//#endregion\n//#region src/web-app/main.tsx\n/** @jsxImportSource solid-js */\nvar root = document.getElementById(\"granola-web-root\");\nif (!root) throw new Error(\"Granola web root element not found\");\nrender(() => createComponent(App, {}), root);\n//#endregion\n";
|
|
7009
7774
|
//#endregion
|
|
7010
7775
|
//#region src/web/assets.ts
|
|
7011
7776
|
const granolaWebAssetPaths = {
|
|
@@ -7085,6 +7850,26 @@ function parseAutomationRunStatus(value) {
|
|
|
7085
7850
|
default: throw new Error("invalid automation status: expected completed, failed, pending, or skipped");
|
|
7086
7851
|
}
|
|
7087
7852
|
}
|
|
7853
|
+
function parseAutomationArtefactKind(value) {
|
|
7854
|
+
switch (value) {
|
|
7855
|
+
case null:
|
|
7856
|
+
case "": return;
|
|
7857
|
+
case "enrichment":
|
|
7858
|
+
case "notes": return value;
|
|
7859
|
+
default: throw new Error("invalid automation artefact kind: expected enrichment or notes");
|
|
7860
|
+
}
|
|
7861
|
+
}
|
|
7862
|
+
function parseAutomationArtefactStatus(value) {
|
|
7863
|
+
switch (value) {
|
|
7864
|
+
case null:
|
|
7865
|
+
case "": return;
|
|
7866
|
+
case "approved":
|
|
7867
|
+
case "generated":
|
|
7868
|
+
case "rejected":
|
|
7869
|
+
case "superseded": return value;
|
|
7870
|
+
default: throw new Error("invalid automation artefact status: expected approved, generated, rejected, or superseded");
|
|
7871
|
+
}
|
|
7872
|
+
}
|
|
7088
7873
|
function folderIdFromBody(value) {
|
|
7089
7874
|
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
7090
7875
|
}
|
|
@@ -7340,6 +8125,33 @@ async function startGranolaServer(app, options = {}) {
|
|
|
7340
8125
|
sendJson(response, await app.listAutomationMatches({ limit: parseInteger(url.searchParams.get("limit")) }), { headers: originHeaders });
|
|
7341
8126
|
return;
|
|
7342
8127
|
}
|
|
8128
|
+
if (method === "GET" && path === granolaTransportPaths.automationArtefacts) {
|
|
8129
|
+
sendJson(response, await app.listAutomationArtefacts({
|
|
8130
|
+
kind: parseAutomationArtefactKind(url.searchParams.get("kind")),
|
|
8131
|
+
limit: parseInteger(url.searchParams.get("limit")),
|
|
8132
|
+
meetingId: url.searchParams.get("meetingId")?.trim() || void 0,
|
|
8133
|
+
status: parseAutomationArtefactStatus(url.searchParams.get("status"))
|
|
8134
|
+
}), { headers: originHeaders });
|
|
8135
|
+
return;
|
|
8136
|
+
}
|
|
8137
|
+
if (method === "POST" && path.endsWith("/update") && path.startsWith(`${granolaTransportPaths.automationArtefacts}/`)) {
|
|
8138
|
+
const id = decodeURIComponent(path.slice(`${granolaTransportPaths.automationArtefacts}/`.length, -7));
|
|
8139
|
+
const body = await readJsonBody(request);
|
|
8140
|
+
sendJson(response, await app.updateAutomationArtefact(id, {
|
|
8141
|
+
markdown: typeof body.markdown === "string" ? body.markdown : void 0,
|
|
8142
|
+
note: typeof body.note === "string" ? body.note : void 0,
|
|
8143
|
+
summary: typeof body.summary === "string" ? body.summary : void 0,
|
|
8144
|
+
title: typeof body.title === "string" ? body.title : void 0
|
|
8145
|
+
}), { headers: originHeaders });
|
|
8146
|
+
return;
|
|
8147
|
+
}
|
|
8148
|
+
if (method === "POST" && (path.endsWith("/approve") || path.endsWith("/reject")) && path.startsWith(`${granolaTransportPaths.automationArtefacts}/`)) {
|
|
8149
|
+
const decision = path.endsWith("/approve") ? "approve" : "reject";
|
|
8150
|
+
const id = decodeURIComponent(path.slice(`${granolaTransportPaths.automationArtefacts}/`.length, -`/${decision}`.length));
|
|
8151
|
+
const body = await readJsonBody(request);
|
|
8152
|
+
sendJson(response, await app.resolveAutomationArtefact(id, decision, { note: typeof body.note === "string" ? body.note : void 0 }), { headers: originHeaders });
|
|
8153
|
+
return;
|
|
8154
|
+
}
|
|
7343
8155
|
if (method === "GET" && path === granolaTransportPaths.automationRuns) {
|
|
7344
8156
|
sendJson(response, await app.listAutomationRuns({
|
|
7345
8157
|
limit: parseInteger(url.searchParams.get("limit")),
|
|
@@ -7347,6 +8159,16 @@ async function startGranolaServer(app, options = {}) {
|
|
|
7347
8159
|
}), { headers: originHeaders });
|
|
7348
8160
|
return;
|
|
7349
8161
|
}
|
|
8162
|
+
if (method === "POST" && path.endsWith("/rerun") && path.startsWith(`${granolaTransportPaths.automationArtefacts}/`)) {
|
|
8163
|
+
const id = decodeURIComponent(path.slice(`${granolaTransportPaths.automationArtefacts}/`.length, -6));
|
|
8164
|
+
sendJson(response, await app.rerunAutomationArtefact(id), { headers: originHeaders });
|
|
8165
|
+
return;
|
|
8166
|
+
}
|
|
8167
|
+
if (method === "GET" && path.startsWith(`${granolaTransportPaths.automationArtefacts}/`) && !path.slice(`${granolaTransportPaths.automationArtefacts}/`.length).includes("/")) {
|
|
8168
|
+
const id = decodeURIComponent(path.slice(`${granolaTransportPaths.automationArtefacts}/`.length));
|
|
8169
|
+
sendJson(response, await app.getAutomationArtefact(id), { headers: originHeaders });
|
|
8170
|
+
return;
|
|
8171
|
+
}
|
|
7350
8172
|
if (method === "POST" && (path.endsWith("/approve") || path.endsWith("/reject")) && path.startsWith(`${granolaTransportPaths.automationRuns}/`)) {
|
|
7351
8173
|
const decision = path.endsWith("/approve") ? "approve" : "reject";
|
|
7352
8174
|
const id = decodeURIComponent(path.slice(`${granolaTransportPaths.automationRuns}/`.length, -`/${decision}`.length));
|