granola-toolkit 0.54.0 → 0.56.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 +498 -16
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Input, ProcessTerminal, TUI, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
|
|
3
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
-
import { appendFile, mkdir, mkdtemp, readFile, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
|
-
import { dirname, join, resolve } from "node:path";
|
|
6
3
|
import { existsSync } from "node:fs";
|
|
4
|
+
import { appendFile, mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
7
5
|
import { homedir, platform, tmpdir } from "node:os";
|
|
6
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
8
7
|
import { NodeHtmlMarkdown } from "node-html-markdown";
|
|
8
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
9
9
|
import { execFile, spawn } from "node:child_process";
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import { createServer } from "node:http";
|
|
@@ -226,6 +226,9 @@ var GranolaServerClient = class GranolaServerClient {
|
|
|
226
226
|
async listAutomationArtefacts(options = {}) {
|
|
227
227
|
return await this.requestJson(granolaAutomationArtefactsPath(options));
|
|
228
228
|
}
|
|
229
|
+
async evaluateAutomationCases(_cases, _options) {
|
|
230
|
+
throw new Error("automation evaluate is not supported over attached servers yet");
|
|
231
|
+
}
|
|
229
232
|
async listProcessingIssues(options = {}) {
|
|
230
233
|
return await this.requestJson(granolaProcessingIssuesPath(options));
|
|
231
234
|
}
|
|
@@ -555,7 +558,189 @@ async function readUtf8(pathname) {
|
|
|
555
558
|
return await readFile(pathname, "utf8");
|
|
556
559
|
}
|
|
557
560
|
function transcriptSpeakerLabel(segment) {
|
|
558
|
-
|
|
561
|
+
const source = segment.source.trim();
|
|
562
|
+
if (!source || source === "unknown") return "Unknown";
|
|
563
|
+
if (source === "microphone") return "You";
|
|
564
|
+
if (source === "system") return "System";
|
|
565
|
+
return source;
|
|
566
|
+
}
|
|
567
|
+
//#endregion
|
|
568
|
+
//#region src/meeting-roles.ts
|
|
569
|
+
function normaliseKey(value) {
|
|
570
|
+
return (value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
571
|
+
}
|
|
572
|
+
function collapseSpaces(value) {
|
|
573
|
+
return value.replace(/\s+/g, " ").trim();
|
|
574
|
+
}
|
|
575
|
+
function personLabel(person) {
|
|
576
|
+
return collapseSpaces(person.name || person.email || "Unknown participant");
|
|
577
|
+
}
|
|
578
|
+
function personId(role, person) {
|
|
579
|
+
return `${role}:${sanitiseFilename((person.email?.trim() || person.name?.trim() || `unknown-${role}`).toLowerCase(), role)}`;
|
|
580
|
+
}
|
|
581
|
+
function personToParticipant(role, person) {
|
|
582
|
+
return {
|
|
583
|
+
companyName: person.companyName?.trim() || void 0,
|
|
584
|
+
email: person.email?.trim() || void 0,
|
|
585
|
+
id: personId(role, person),
|
|
586
|
+
label: personLabel(person),
|
|
587
|
+
role,
|
|
588
|
+
title: person.title?.trim() || void 0
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function uniqueParticipants(people) {
|
|
592
|
+
const participants = [];
|
|
593
|
+
const seen = /* @__PURE__ */ new Set();
|
|
594
|
+
const add = (role, person) => {
|
|
595
|
+
if (!person) return;
|
|
596
|
+
const participant = personToParticipant(role, person);
|
|
597
|
+
const key = participant.email?.toLowerCase() || normaliseKey(participant.label);
|
|
598
|
+
if (!key || seen.has(key)) return;
|
|
599
|
+
seen.add(key);
|
|
600
|
+
participants.push(participant);
|
|
601
|
+
};
|
|
602
|
+
add("creator", people?.creator);
|
|
603
|
+
for (const attendee of people?.attendees ?? []) add("attendee", attendee);
|
|
604
|
+
return participants.sort((left, right) => compareStrings(left.label, right.label));
|
|
605
|
+
}
|
|
606
|
+
function participantMatchKeys(participant) {
|
|
607
|
+
const values = /* @__PURE__ */ new Set();
|
|
608
|
+
const label = normaliseKey(participant.label);
|
|
609
|
+
if (label) {
|
|
610
|
+
values.add(label);
|
|
611
|
+
const parts = label.split(" ").filter(Boolean);
|
|
612
|
+
if (parts[0]) values.add(parts[0]);
|
|
613
|
+
}
|
|
614
|
+
const email = participant.email?.trim().toLowerCase();
|
|
615
|
+
if (email) {
|
|
616
|
+
values.add(email);
|
|
617
|
+
const [local] = email.split("@");
|
|
618
|
+
if (local) values.add(normaliseKey(local));
|
|
619
|
+
}
|
|
620
|
+
return values;
|
|
621
|
+
}
|
|
622
|
+
function matchParticipant(label, participants) {
|
|
623
|
+
const key = normaliseKey(label);
|
|
624
|
+
if (!key) return;
|
|
625
|
+
const exact = participants.filter((participant) => participantMatchKeys(participant).has(key));
|
|
626
|
+
if (exact.length === 1) return exact[0];
|
|
627
|
+
const fuzzy = participants.filter((participant) => {
|
|
628
|
+
const participantKey = normaliseKey(participant.label);
|
|
629
|
+
return participantKey.includes(key) || key.includes(participantKey);
|
|
630
|
+
});
|
|
631
|
+
if (fuzzy.length === 1) return fuzzy[0];
|
|
632
|
+
}
|
|
633
|
+
function wordCount(text) {
|
|
634
|
+
return text.trim().match(/\b[\p{L}\p{N}'’-]+\b/gu)?.length ?? 0;
|
|
635
|
+
}
|
|
636
|
+
function speakerId(label) {
|
|
637
|
+
return `speaker:${sanitiseFilename(label.toLowerCase(), "speaker")}`;
|
|
638
|
+
}
|
|
639
|
+
function buildTranscriptSpeakers(segments, people) {
|
|
640
|
+
const participants = uniqueParticipants(people);
|
|
641
|
+
const creator = participants.find((participant) => participant.role === "creator");
|
|
642
|
+
const speakers = /* @__PURE__ */ new Map();
|
|
643
|
+
for (const segment of segments) {
|
|
644
|
+
const label = transcriptSpeakerLabel(segment);
|
|
645
|
+
const existing = speakers.get(label);
|
|
646
|
+
const matchedParticipant = label === "You" ? creator : label === "System" ? void 0 : matchParticipant(label, participants);
|
|
647
|
+
const role = label === "You" ? "self" : label === "System" ? "system" : matchedParticipant?.role ?? "unknown";
|
|
648
|
+
if (!existing) {
|
|
649
|
+
speakers.set(label, {
|
|
650
|
+
firstTimestamp: segment.startTimestamp,
|
|
651
|
+
id: speakerId(label),
|
|
652
|
+
label,
|
|
653
|
+
lastTimestamp: segment.endTimestamp,
|
|
654
|
+
matchedParticipantEmail: matchedParticipant?.email,
|
|
655
|
+
matchedParticipantId: matchedParticipant?.id,
|
|
656
|
+
matchedParticipantLabel: matchedParticipant?.label,
|
|
657
|
+
role,
|
|
658
|
+
segmentCount: 1,
|
|
659
|
+
source: segment.source,
|
|
660
|
+
wordCount: wordCount(segment.text)
|
|
661
|
+
});
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
existing.segmentCount += 1;
|
|
665
|
+
existing.wordCount += wordCount(segment.text);
|
|
666
|
+
if (segment.startTimestamp < existing.firstTimestamp) existing.firstTimestamp = segment.startTimestamp;
|
|
667
|
+
if (segment.endTimestamp > existing.lastTimestamp) existing.lastTimestamp = segment.endTimestamp;
|
|
668
|
+
if (!existing.matchedParticipantId && matchedParticipant) {
|
|
669
|
+
existing.matchedParticipantEmail = matchedParticipant.email;
|
|
670
|
+
existing.matchedParticipantId = matchedParticipant.id;
|
|
671
|
+
existing.matchedParticipantLabel = matchedParticipant.label;
|
|
672
|
+
existing.role = role;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
return [...speakers.values()].sort((left, right) => {
|
|
676
|
+
return compareStrings(left.firstTimestamp, right.firstTimestamp) || compareStrings(left.label, right.label);
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function buildMeetingRoleHelpers(people, speakers) {
|
|
680
|
+
const participants = uniqueParticipants(people);
|
|
681
|
+
const creator = participants.find((participant) => participant.role === "creator");
|
|
682
|
+
const ownerCandidates = [];
|
|
683
|
+
const seen = /* @__PURE__ */ new Set();
|
|
684
|
+
const addOwnerCandidate = (candidate) => {
|
|
685
|
+
const key = candidate.email?.toLowerCase() || normaliseKey(candidate.label);
|
|
686
|
+
if (!key || seen.has(key)) return;
|
|
687
|
+
seen.add(key);
|
|
688
|
+
ownerCandidates.push(candidate);
|
|
689
|
+
};
|
|
690
|
+
for (const speaker of speakers) {
|
|
691
|
+
if (speaker.role === "system") continue;
|
|
692
|
+
const matchedParticipant = participants.find((participant) => participant.id === speaker.matchedParticipantId) || (speaker.role === "self" ? creator : void 0) || matchParticipant(speaker.label, participants);
|
|
693
|
+
addOwnerCandidate({
|
|
694
|
+
email: matchedParticipant?.email ?? speaker.matchedParticipantEmail,
|
|
695
|
+
id: matchedParticipant?.id ?? speaker.matchedParticipantId ?? speaker.id,
|
|
696
|
+
label: matchedParticipant?.label ?? speaker.matchedParticipantLabel ?? speaker.label,
|
|
697
|
+
role: speaker.role,
|
|
698
|
+
source: "speaker"
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
if (ownerCandidates.length === 0) for (const participant of participants) addOwnerCandidate({
|
|
702
|
+
email: participant.email,
|
|
703
|
+
id: participant.id,
|
|
704
|
+
label: participant.label,
|
|
705
|
+
role: participant.role,
|
|
706
|
+
source: "participant"
|
|
707
|
+
});
|
|
708
|
+
return {
|
|
709
|
+
ownerCandidates: ownerCandidates.sort((left, right) => compareStrings(left.label, right.label)),
|
|
710
|
+
participants,
|
|
711
|
+
speakers: speakers.map((speaker) => ({ ...speaker }))
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
function resolveMeetingOwnerCandidate(owner, roleHelpers) {
|
|
715
|
+
const trimmed = owner?.trim();
|
|
716
|
+
if (!trimmed) return {};
|
|
717
|
+
const candidates = roleHelpers?.ownerCandidates ?? [];
|
|
718
|
+
if (candidates.length === 0) return { owner: trimmed };
|
|
719
|
+
const ownerKey = normaliseKey(trimmed);
|
|
720
|
+
const selfCandidate = candidates.find((candidate) => candidate.role === "self");
|
|
721
|
+
if ([
|
|
722
|
+
"i",
|
|
723
|
+
"me",
|
|
724
|
+
"myself",
|
|
725
|
+
"you"
|
|
726
|
+
].includes(ownerKey) && selfCandidate) return {
|
|
727
|
+
owner: selfCandidate.label,
|
|
728
|
+
ownerEmail: selfCandidate.email,
|
|
729
|
+
ownerOriginal: trimmed,
|
|
730
|
+
ownerRole: selfCandidate.role
|
|
731
|
+
};
|
|
732
|
+
const matches = candidates.filter((candidate) => {
|
|
733
|
+
const labelKey = normaliseKey(candidate.label);
|
|
734
|
+
const emailKey = candidate.email?.toLowerCase();
|
|
735
|
+
return labelKey === ownerKey || labelKey.includes(ownerKey) || ownerKey.includes(labelKey) || emailKey === ownerKey || emailKey?.startsWith(`${ownerKey}@`);
|
|
736
|
+
});
|
|
737
|
+
if (matches.length === 1) return {
|
|
738
|
+
owner: matches[0].label,
|
|
739
|
+
ownerEmail: matches[0].email,
|
|
740
|
+
ownerOriginal: trimmed === matches[0].label ? void 0 : trimmed,
|
|
741
|
+
ownerRole: matches[0].role
|
|
742
|
+
};
|
|
743
|
+
return { owner: trimmed };
|
|
559
744
|
}
|
|
560
745
|
//#endregion
|
|
561
746
|
//#region src/export-state.ts
|
|
@@ -1025,6 +1210,7 @@ function buildTranscriptExport(document, segments, rawSegments = segments) {
|
|
|
1025
1210
|
segments: rawSegments
|
|
1026
1211
|
},
|
|
1027
1212
|
segments: renderedSegments,
|
|
1213
|
+
speakers: buildTranscriptSpeakers(segments),
|
|
1028
1214
|
title: document.title,
|
|
1029
1215
|
updatedAt: document.updatedAt
|
|
1030
1216
|
};
|
|
@@ -1035,6 +1221,7 @@ function renderTranscriptExport(transcript, format = "text") {
|
|
|
1035
1221
|
createdAt: transcript.createdAt,
|
|
1036
1222
|
id: transcript.id,
|
|
1037
1223
|
segments: transcript.segments,
|
|
1224
|
+
speakers: transcript.speakers,
|
|
1038
1225
|
title: transcript.title,
|
|
1039
1226
|
updatedAt: transcript.updatedAt
|
|
1040
1227
|
});
|
|
@@ -1043,6 +1230,7 @@ function renderTranscriptExport(transcript, format = "text") {
|
|
|
1043
1230
|
createdAt: transcript.createdAt,
|
|
1044
1231
|
id: transcript.id,
|
|
1045
1232
|
segments: transcript.segments,
|
|
1233
|
+
speakers: transcript.speakers,
|
|
1046
1234
|
title: transcript.title,
|
|
1047
1235
|
updatedAt: transcript.updatedAt
|
|
1048
1236
|
});
|
|
@@ -1059,6 +1247,7 @@ function formatTranscriptText(transcript) {
|
|
|
1059
1247
|
transcript.createdAt ? `Created: ${transcript.createdAt}` : "",
|
|
1060
1248
|
transcript.updatedAt ? `Updated: ${transcript.updatedAt}` : "",
|
|
1061
1249
|
`Segments: ${transcript.segments.length}`,
|
|
1250
|
+
transcript.speakers.length > 0 ? `Speakers: ${transcript.speakers.map((speaker) => speaker.label).join(", ")}` : "",
|
|
1062
1251
|
"=".repeat(80),
|
|
1063
1252
|
""
|
|
1064
1253
|
].filter(Boolean);
|
|
@@ -1165,6 +1354,7 @@ function serialiseTranscript(transcript) {
|
|
|
1165
1354
|
createdAt: transcript.createdAt,
|
|
1166
1355
|
id: transcript.id,
|
|
1167
1356
|
segments: transcript.segments.map((segment) => ({ ...segment })),
|
|
1357
|
+
speakers: transcript.speakers.map((speaker) => ({ ...speaker })),
|
|
1168
1358
|
title: transcript.title,
|
|
1169
1359
|
updatedAt: transcript.updatedAt
|
|
1170
1360
|
};
|
|
@@ -1203,6 +1393,9 @@ function buildMeetingTranscript(document, cacheData) {
|
|
|
1203
1393
|
transcriptText: renderTranscriptExport(transcript, "text")
|
|
1204
1394
|
};
|
|
1205
1395
|
}
|
|
1396
|
+
function buildRoleHelpers(document, transcript) {
|
|
1397
|
+
return buildMeetingRoleHelpers(document.people, transcript?.speakers ?? []);
|
|
1398
|
+
}
|
|
1206
1399
|
function matchesMeetingSearch(document, search) {
|
|
1207
1400
|
const query = search.trim().toLowerCase();
|
|
1208
1401
|
if (!query) return true;
|
|
@@ -1288,6 +1481,7 @@ function buildMeetingSummary(document, cacheData, folders = []) {
|
|
|
1288
1481
|
function buildMeetingRecord(document, cacheData, folders = []) {
|
|
1289
1482
|
const note = buildNoteExport(document);
|
|
1290
1483
|
const transcript = buildMeetingTranscript(document, cacheData);
|
|
1484
|
+
const roleHelpers = buildRoleHelpers(document, transcript.transcript);
|
|
1291
1485
|
return {
|
|
1292
1486
|
meeting: {
|
|
1293
1487
|
createdAt: document.createdAt,
|
|
@@ -1302,6 +1496,7 @@ function buildMeetingRecord(document, cacheData, folders = []) {
|
|
|
1302
1496
|
},
|
|
1303
1497
|
note: serialiseNote(note),
|
|
1304
1498
|
noteMarkdown: renderNoteExport(note, "markdown"),
|
|
1499
|
+
roleHelpers,
|
|
1305
1500
|
transcript: transcript.transcript,
|
|
1306
1501
|
transcriptText: transcript.transcriptText
|
|
1307
1502
|
};
|
|
@@ -1376,6 +1571,8 @@ function renderMeetingView(record, format = "text") {
|
|
|
1376
1571
|
const tags = record.meeting.tags.length > 0 ? record.meeting.tags.join(", ") : "(none)";
|
|
1377
1572
|
const folders = meetingFolders(record.meeting).length > 0 ? meetingFolders(record.meeting).map((folder) => folder.name).join(", ") : "(none)";
|
|
1378
1573
|
const transcriptStatus = !record.meeting.transcriptLoaded ? "cache not loaded" : record.meeting.transcriptSegmentCount === 0 ? "no transcript segments" : `${record.meeting.transcriptSegmentCount} segment(s)`;
|
|
1574
|
+
const ownerCandidates = record.roleHelpers.ownerCandidates.length > 0 ? record.roleHelpers.ownerCandidates.map((candidate) => candidate.label).join(", ") : "(none)";
|
|
1575
|
+
const speakers = record.roleHelpers.speakers.length > 0 ? record.roleHelpers.speakers.map((speaker) => speaker.label).join(", ") : "(none)";
|
|
1379
1576
|
return `${[
|
|
1380
1577
|
`# ${record.meeting.title || record.meeting.id}`,
|
|
1381
1578
|
"",
|
|
@@ -1386,6 +1583,8 @@ function renderMeetingView(record, format = "text") {
|
|
|
1386
1583
|
`Folders: ${folders}`,
|
|
1387
1584
|
`Note source: ${record.meeting.noteContentSource}`,
|
|
1388
1585
|
`Transcript: ${transcriptStatus}`,
|
|
1586
|
+
`Owner candidates: ${ownerCandidates}`,
|
|
1587
|
+
`Speakers: ${speakers}`,
|
|
1389
1588
|
"",
|
|
1390
1589
|
"## Notes",
|
|
1391
1590
|
"",
|
|
@@ -1525,7 +1724,9 @@ function renderGranolaTuiMeetingTab(bundle, tab) {
|
|
|
1525
1724
|
`Tags: ${summary.tags.length > 0 ? summary.tags.join(", ") : "none"}`,
|
|
1526
1725
|
`Notes source: ${summary.noteContentSource}`,
|
|
1527
1726
|
`Transcript loaded: ${summary.transcriptLoaded ? "yes" : "no"}`,
|
|
1528
|
-
`Transcript segments: ${summary.transcriptSegmentCount}
|
|
1727
|
+
`Transcript segments: ${summary.transcriptSegmentCount}`,
|
|
1728
|
+
`Owner candidates: ${bundle.meeting.roleHelpers.ownerCandidates.length > 0 ? bundle.meeting.roleHelpers.ownerCandidates.map((candidate) => candidate.label).join(", ") : "none"}`,
|
|
1729
|
+
`Speakers: ${bundle.meeting.roleHelpers.speakers.length > 0 ? bundle.meeting.roleHelpers.speakers.map((speaker) => `${speaker.label} (${speaker.segmentCount})`).join(", ") : "none"}`
|
|
1529
1730
|
].join("\n");
|
|
1530
1731
|
case "raw": return JSON.stringify(bundle, null, 2);
|
|
1531
1732
|
case "transcript": {
|
|
@@ -4018,13 +4219,22 @@ function buildAutomationDeliveryPayload(context) {
|
|
|
4018
4219
|
} : void 0,
|
|
4019
4220
|
artefact: context.artefact ? {
|
|
4020
4221
|
actionId: context.artefact.actionId,
|
|
4222
|
+
actionItems: context.artefact.structured.actionItems.map((item) => ({ ...item })),
|
|
4223
|
+
decisions: [...context.artefact.structured.decisions],
|
|
4224
|
+
followUps: [...context.artefact.structured.followUps],
|
|
4021
4225
|
id: context.artefact.id,
|
|
4226
|
+
highlights: [...context.artefact.structured.highlights],
|
|
4022
4227
|
kind: context.artefact.kind,
|
|
4023
4228
|
markdown: context.artefact.structured.markdown,
|
|
4024
4229
|
metadata: context.artefact.structured.metadata,
|
|
4025
4230
|
model: context.artefact.model,
|
|
4231
|
+
participantSummaries: context.artefact.structured.participantSummaries?.map((summary) => ({
|
|
4232
|
+
...summary,
|
|
4233
|
+
actionItems: [...summary.actionItems]
|
|
4234
|
+
})),
|
|
4026
4235
|
prompt: context.artefact.prompt,
|
|
4027
4236
|
provider: context.artefact.provider,
|
|
4237
|
+
sections: context.artefact.structured.sections.map((section) => ({ ...section })),
|
|
4028
4238
|
status: context.artefact.status,
|
|
4029
4239
|
summary: context.artefact.structured.summary,
|
|
4030
4240
|
title: context.artefact.structured.title
|
|
@@ -4742,6 +4952,37 @@ function parseCalendarEvent(value) {
|
|
|
4742
4952
|
url: url || void 0
|
|
4743
4953
|
};
|
|
4744
4954
|
}
|
|
4955
|
+
function parsePerson(value) {
|
|
4956
|
+
const record = asRecord(value);
|
|
4957
|
+
if (!record) return;
|
|
4958
|
+
const details = asRecord(record.details);
|
|
4959
|
+
const person = asRecord(details?.person);
|
|
4960
|
+
const nameRecord = asRecord(person?.name);
|
|
4961
|
+
const company = asRecord(details?.company);
|
|
4962
|
+
const employment = asRecord(person?.employment);
|
|
4963
|
+
const name = stringValue(record.name) || stringValue(nameRecord?.fullName) || stringValue(nameRecord?.displayName);
|
|
4964
|
+
const email = stringValue(record.email);
|
|
4965
|
+
const companyName = stringValue(company?.name) || stringValue(employment?.name);
|
|
4966
|
+
const title = stringValue(employment?.title);
|
|
4967
|
+
if (!name && !email && !companyName && !title) return;
|
|
4968
|
+
return {
|
|
4969
|
+
companyName: companyName || void 0,
|
|
4970
|
+
email: email || void 0,
|
|
4971
|
+
name: name || void 0,
|
|
4972
|
+
title: title || void 0
|
|
4973
|
+
};
|
|
4974
|
+
}
|
|
4975
|
+
function parsePeople(value) {
|
|
4976
|
+
const record = asRecord(value);
|
|
4977
|
+
if (!record) return;
|
|
4978
|
+
const creator = parsePerson(record.creator);
|
|
4979
|
+
const attendees = Array.isArray(record.attendees) ? record.attendees.map(parsePerson).filter((person) => Boolean(person)) : [];
|
|
4980
|
+
if (!creator && attendees.length === 0) return;
|
|
4981
|
+
return {
|
|
4982
|
+
attendees,
|
|
4983
|
+
creator
|
|
4984
|
+
};
|
|
4985
|
+
}
|
|
4745
4986
|
function parseDocument(value) {
|
|
4746
4987
|
const record = asRecord(value);
|
|
4747
4988
|
if (!record) throw new Error("document payload is not an object");
|
|
@@ -4753,6 +4994,7 @@ function parseDocument(value) {
|
|
|
4753
4994
|
lastViewedPanel: parseLastViewedPanel(record.last_viewed_panel),
|
|
4754
4995
|
notes: parseProseMirrorDoc(record.notes),
|
|
4755
4996
|
notesPlain: stringValue(record.notes_plain),
|
|
4997
|
+
people: parsePeople(record.people),
|
|
4756
4998
|
tags: stringArray$2(record.tags),
|
|
4757
4999
|
title: stringValue(record.title),
|
|
4758
5000
|
updatedAt: stringValue(record.updated_at)
|
|
@@ -4810,6 +5052,7 @@ function parsePublicNote(value) {
|
|
|
4810
5052
|
folderMemberships: Array.isArray(record.folder_membership) ? record.folder_membership.map(parseFolderMembership).filter((membership) => Boolean(membership)) : [],
|
|
4811
5053
|
id,
|
|
4812
5054
|
notesPlain: summaryText,
|
|
5055
|
+
people: parsePeople(record.people),
|
|
4813
5056
|
tags: [],
|
|
4814
5057
|
title: stringValue(record.title),
|
|
4815
5058
|
transcriptSegments: Array.isArray(record.transcript) ? record.transcript.map((segment, index) => parsePublicTranscriptSegment(id, segment, index)).filter((segment) => Boolean(segment)) : [],
|
|
@@ -5948,20 +6191,46 @@ function markdownSections(markdown) {
|
|
|
5948
6191
|
function normaliseStrings(value) {
|
|
5949
6192
|
return stringArray$2(value).map((item) => item.trim()).filter(Boolean);
|
|
5950
6193
|
}
|
|
5951
|
-
function normaliseActionItems(value) {
|
|
6194
|
+
function normaliseActionItems(value, roleHelpers) {
|
|
5952
6195
|
if (!Array.isArray(value)) return [];
|
|
5953
6196
|
return value.map((item) => {
|
|
5954
6197
|
const record = asRecord(item);
|
|
5955
6198
|
if (!record) return;
|
|
5956
6199
|
const title = stringValue(record.title).trim();
|
|
5957
6200
|
if (!title) return;
|
|
6201
|
+
const owner = resolveMeetingOwnerCandidate(stringValue(record.owner), roleHelpers);
|
|
6202
|
+
const ownerEmail = stringValue(record.ownerEmail).trim() || owner.ownerEmail;
|
|
6203
|
+
const ownerOriginal = stringValue(record.ownerOriginal).trim() || owner.ownerOriginal;
|
|
6204
|
+
const ownerRoleValue = stringValue(record.ownerRole).trim();
|
|
6205
|
+
const ownerRole = ownerRoleValue === "attendee" || ownerRoleValue === "creator" || ownerRoleValue === "self" || ownerRoleValue === "system" || ownerRoleValue === "unknown" ? ownerRoleValue : owner.ownerRole;
|
|
5958
6206
|
return {
|
|
5959
6207
|
dueDate: stringValue(record.dueDate).trim() || void 0,
|
|
5960
|
-
owner:
|
|
6208
|
+
owner: owner.owner,
|
|
6209
|
+
ownerEmail: ownerEmail || void 0,
|
|
6210
|
+
ownerOriginal: ownerOriginal || void 0,
|
|
6211
|
+
ownerRole,
|
|
5961
6212
|
title
|
|
5962
6213
|
};
|
|
5963
6214
|
}).filter((item) => Boolean(item));
|
|
5964
6215
|
}
|
|
6216
|
+
function normaliseParticipantSummaries(value) {
|
|
6217
|
+
if (!Array.isArray(value)) return;
|
|
6218
|
+
const summaries = value.map((item) => {
|
|
6219
|
+
const record = asRecord(item);
|
|
6220
|
+
if (!record) return;
|
|
6221
|
+
const speaker = stringValue(record.speaker).trim();
|
|
6222
|
+
const summary = stringValue(record.summary).trim();
|
|
6223
|
+
if (!speaker || !summary) return;
|
|
6224
|
+
const role = stringValue(record.role).trim();
|
|
6225
|
+
return {
|
|
6226
|
+
actionItems: normaliseStrings(record.actionItems),
|
|
6227
|
+
role: role === "attendee" || role === "creator" || role === "self" || role === "system" || role === "unknown" ? role : void 0,
|
|
6228
|
+
speaker,
|
|
6229
|
+
summary
|
|
6230
|
+
};
|
|
6231
|
+
}).filter(Boolean);
|
|
6232
|
+
return summaries.length > 0 ? summaries : void 0;
|
|
6233
|
+
}
|
|
5965
6234
|
function normaliseSections(value) {
|
|
5966
6235
|
if (!Array.isArray(value)) return [];
|
|
5967
6236
|
return value.map((item) => {
|
|
@@ -5988,7 +6257,8 @@ function buildPipelineInstructions(kind, instructions) {
|
|
|
5988
6257
|
return [
|
|
5989
6258
|
instructions.trim(),
|
|
5990
6259
|
task,
|
|
5991
|
-
"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\":{}}",
|
|
6260
|
+
"Return JSON only. Use this exact shape: {\"title\":\"string\",\"summary\":\"string\",\"markdown\":\"string\",\"sections\":[{\"title\":\"string\",\"body\":\"string\"}],\"actionItems\":[{\"title\":\"string\",\"owner\":\"string\",\"ownerEmail\":\"string\",\"ownerRole\":\"string\",\"dueDate\":\"string\"}],\"participantSummaries\":[{\"speaker\":\"string\",\"role\":\"string\",\"summary\":\"string\",\"actionItems\":[\"string\"]}],\"decisions\":[\"string\"],\"followUps\":[\"string\"],\"highlights\":[\"string\"],\"metadata\":{}}",
|
|
6261
|
+
"When meeting role helpers are provided, prefer canonical owner names and emails from those candidates instead of vague owners like 'you' or first-name fragments.",
|
|
5992
6262
|
"Keep arrays empty instead of omitting them. markdown must contain the full Markdown result."
|
|
5993
6263
|
].filter(Boolean).join("\n\n");
|
|
5994
6264
|
}
|
|
@@ -6001,12 +6271,13 @@ function parsePipelineOutput(options) {
|
|
|
6001
6271
|
if (markdown) return {
|
|
6002
6272
|
parseMode: "json",
|
|
6003
6273
|
structured: {
|
|
6004
|
-
actionItems: normaliseActionItems(payload.actionItems),
|
|
6274
|
+
actionItems: normaliseActionItems(payload.actionItems, options.roleHelpers),
|
|
6005
6275
|
decisions: normaliseStrings(payload.decisions),
|
|
6006
6276
|
followUps: normaliseStrings(payload.followUps),
|
|
6007
6277
|
highlights: normaliseStrings(payload.highlights),
|
|
6008
6278
|
markdown,
|
|
6009
6279
|
metadata: asRecord(payload.metadata),
|
|
6280
|
+
participantSummaries: normaliseParticipantSummaries(payload.participantSummaries),
|
|
6010
6281
|
sections: normaliseSections(payload.sections),
|
|
6011
6282
|
summary: stringValue(payload.summary).trim() || firstParagraph(markdown),
|
|
6012
6283
|
title
|
|
@@ -6023,6 +6294,7 @@ function parsePipelineOutput(options) {
|
|
|
6023
6294
|
highlights: [],
|
|
6024
6295
|
markdown,
|
|
6025
6296
|
metadata: { fallbackKind: options.kind },
|
|
6297
|
+
participantSummaries: void 0,
|
|
6026
6298
|
sections: markdownSections(markdown),
|
|
6027
6299
|
summary: firstParagraph(markdown),
|
|
6028
6300
|
title: options.kind === "notes" ? `${options.meetingTitle} Generated Notes` : `${options.meetingTitle} Enrichment`
|
|
@@ -6081,9 +6353,9 @@ function combinePromptSections(...values) {
|
|
|
6081
6353
|
return sections.length > 0 ? sections.join("\n\n") : void 0;
|
|
6082
6354
|
}
|
|
6083
6355
|
function meetingTranscriptText(bundle) {
|
|
6084
|
-
const segments = bundle.
|
|
6356
|
+
const segments = bundle.meeting.transcript?.segments;
|
|
6085
6357
|
if (!segments?.length) return;
|
|
6086
|
-
return segments.slice().sort((left, right) => left.startTimestamp.localeCompare(right.startTimestamp)).map((segment) => segment.text.trim()).filter(Boolean).join("\n");
|
|
6358
|
+
return segments.slice().sort((left, right) => left.startTimestamp.localeCompare(right.startTimestamp)).map((segment) => `${segment.speaker}: ${segment.text.trim()}`).filter(Boolean).join("\n");
|
|
6087
6359
|
}
|
|
6088
6360
|
function buildAutomationArtefactId(runId, kind) {
|
|
6089
6361
|
return `${kind}:${runId}`;
|
|
@@ -6105,6 +6377,7 @@ function buildAutomationAgentPrompt(match, rule, instructions, bundle) {
|
|
|
6105
6377
|
meeting: bundle ? {
|
|
6106
6378
|
id: bundle.document.id,
|
|
6107
6379
|
notesPlain: bundle.document.notesPlain,
|
|
6380
|
+
roleHelpers: bundle.meeting.roleHelpers,
|
|
6108
6381
|
tags: [...bundle.document.tags],
|
|
6109
6382
|
title: bundle.document.title,
|
|
6110
6383
|
updatedAt: bundle.document.updatedAt
|
|
@@ -6392,6 +6665,10 @@ var GranolaApp = class {
|
|
|
6392
6665
|
followUps: [...artefact.structured.followUps],
|
|
6393
6666
|
highlights: [...artefact.structured.highlights],
|
|
6394
6667
|
metadata: artefact.structured.metadata ? structuredClone(artefact.structured.metadata) : void 0,
|
|
6668
|
+
participantSummaries: artefact.structured.participantSummaries?.map((summary) => ({
|
|
6669
|
+
...summary,
|
|
6670
|
+
actionItems: [...summary.actionItems]
|
|
6671
|
+
})),
|
|
6395
6672
|
sections: artefact.structured.sections.map((section) => ({ ...section }))
|
|
6396
6673
|
}
|
|
6397
6674
|
};
|
|
@@ -6764,6 +7041,102 @@ var GranolaApp = class {
|
|
|
6764
7041
|
this.setUiState({ view: "idle" });
|
|
6765
7042
|
return { artefacts: artefacts.map((artefact) => this.cloneAutomationArtefact(artefact)) };
|
|
6766
7043
|
}
|
|
7044
|
+
async evaluateAutomationCases(cases, options = {}) {
|
|
7045
|
+
const generatedAt = this.nowIso();
|
|
7046
|
+
const kind = options.kind ?? "notes";
|
|
7047
|
+
const harnesses = this.deps.agentHarnessStore ? await this.deps.agentHarnessStore.readHarnesses() : [];
|
|
7048
|
+
const runner = this.deps.agentRunner ?? createDefaultAutomationAgentRunner(this.config);
|
|
7049
|
+
const results = [];
|
|
7050
|
+
for (const evaluationCase of cases) {
|
|
7051
|
+
const match = {
|
|
7052
|
+
eventId: `evaluate:${evaluationCase.id}`,
|
|
7053
|
+
eventKind: "transcript.ready",
|
|
7054
|
+
folders: evaluationCase.bundle.meeting.meeting.folders.map((folder) => ({ ...folder })),
|
|
7055
|
+
id: `evaluate:${evaluationCase.id}`,
|
|
7056
|
+
matchedAt: generatedAt,
|
|
7057
|
+
meetingId: evaluationCase.bundle.document.id,
|
|
7058
|
+
ruleId: "automation-evaluation",
|
|
7059
|
+
ruleName: "Automation evaluation",
|
|
7060
|
+
tags: [...evaluationCase.bundle.meeting.meeting.tags],
|
|
7061
|
+
title: evaluationCase.title,
|
|
7062
|
+
transcriptLoaded: evaluationCase.bundle.meeting.meeting.transcriptLoaded
|
|
7063
|
+
};
|
|
7064
|
+
const resolvedHarnesses = (options.harnessIds && options.harnessIds.length > 0 ? options.harnessIds.map((harnessId) => resolveAgentHarness(harnesses, {
|
|
7065
|
+
bundle: evaluationCase.bundle,
|
|
7066
|
+
match
|
|
7067
|
+
}, harnessId)) : matchAgentHarnesses(harnesses, {
|
|
7068
|
+
bundle: evaluationCase.bundle,
|
|
7069
|
+
match
|
|
7070
|
+
})).filter((harness) => Boolean(harness));
|
|
7071
|
+
if (resolvedHarnesses.length === 0) {
|
|
7072
|
+
results.push({
|
|
7073
|
+
caseId: evaluationCase.id,
|
|
7074
|
+
caseTitle: evaluationCase.title,
|
|
7075
|
+
error: "No harness matched this fixture case.",
|
|
7076
|
+
prompt: "",
|
|
7077
|
+
status: "failed"
|
|
7078
|
+
});
|
|
7079
|
+
continue;
|
|
7080
|
+
}
|
|
7081
|
+
for (const harness of resolvedHarnesses) {
|
|
7082
|
+
const action = {
|
|
7083
|
+
dryRun: options.dryRun,
|
|
7084
|
+
harnessId: harness.id,
|
|
7085
|
+
id: `evaluate:${harness.id}`,
|
|
7086
|
+
kind: "agent",
|
|
7087
|
+
model: options.model,
|
|
7088
|
+
pipeline: { kind },
|
|
7089
|
+
provider: options.provider
|
|
7090
|
+
};
|
|
7091
|
+
const rule = {
|
|
7092
|
+
id: `evaluate:${harness.id}`,
|
|
7093
|
+
name: `Evaluate ${harness.name}`,
|
|
7094
|
+
when: {}
|
|
7095
|
+
};
|
|
7096
|
+
try {
|
|
7097
|
+
const attempt = await this.buildAutomationAgentAttempt(match, rule, action, evaluationCase.bundle, harness);
|
|
7098
|
+
const result = await runner.run(attempt.request);
|
|
7099
|
+
const parsed = parsePipelineOutput({
|
|
7100
|
+
kind,
|
|
7101
|
+
meetingTitle: evaluationCase.title,
|
|
7102
|
+
rawOutput: result.output ?? "",
|
|
7103
|
+
roleHelpers: evaluationCase.bundle.meeting.roleHelpers
|
|
7104
|
+
});
|
|
7105
|
+
results.push({
|
|
7106
|
+
caseId: evaluationCase.id,
|
|
7107
|
+
caseTitle: evaluationCase.title,
|
|
7108
|
+
harnessId: harness.id,
|
|
7109
|
+
harnessName: harness.name,
|
|
7110
|
+
model: result.model,
|
|
7111
|
+
parseMode: parsed.parseMode,
|
|
7112
|
+
prompt: result.prompt,
|
|
7113
|
+
provider: result.provider,
|
|
7114
|
+
rawOutput: result.output ?? "",
|
|
7115
|
+
status: "completed",
|
|
7116
|
+
structured: parsed.structured
|
|
7117
|
+
});
|
|
7118
|
+
} catch (error) {
|
|
7119
|
+
results.push({
|
|
7120
|
+
caseId: evaluationCase.id,
|
|
7121
|
+
caseTitle: evaluationCase.title,
|
|
7122
|
+
error: error instanceof Error ? error.message : String(error),
|
|
7123
|
+
harnessId: harness.id,
|
|
7124
|
+
harnessName: harness.name,
|
|
7125
|
+
model: options.model ?? harness.model,
|
|
7126
|
+
prompt: "",
|
|
7127
|
+
provider: options.provider ?? harness.provider,
|
|
7128
|
+
status: "failed"
|
|
7129
|
+
});
|
|
7130
|
+
}
|
|
7131
|
+
}
|
|
7132
|
+
}
|
|
7133
|
+
this.setUiState({ view: "idle" });
|
|
7134
|
+
return {
|
|
7135
|
+
generatedAt,
|
|
7136
|
+
kind,
|
|
7137
|
+
results
|
|
7138
|
+
};
|
|
7139
|
+
}
|
|
6767
7140
|
async getAutomationArtefact(id) {
|
|
6768
7141
|
const artefact = await this.readAutomationArtefactById(id);
|
|
6769
7142
|
if (!artefact) throw new Error(`automation artefact not found: ${id}`);
|
|
@@ -7346,7 +7719,8 @@ var GranolaApp = class {
|
|
|
7346
7719
|
const parsed = parsePipelineOutput({
|
|
7347
7720
|
kind: action.pipeline.kind,
|
|
7348
7721
|
meetingTitle: match.title,
|
|
7349
|
-
rawOutput: result.output ?? ""
|
|
7722
|
+
rawOutput: result.output ?? "",
|
|
7723
|
+
roleHelpers: bundle?.meeting.roleHelpers
|
|
7350
7724
|
});
|
|
7351
7725
|
const createdAt = this.nowIso();
|
|
7352
7726
|
const artefact = {
|
|
@@ -7991,6 +8365,54 @@ async function loadConfig(options) {
|
|
|
7991
8365
|
};
|
|
7992
8366
|
}
|
|
7993
8367
|
//#endregion
|
|
8368
|
+
//#region src/evaluations.ts
|
|
8369
|
+
function parseBundle(value) {
|
|
8370
|
+
const record = asRecord(value);
|
|
8371
|
+
const document = asRecord(record?.document);
|
|
8372
|
+
const meeting = asRecord(record?.meeting);
|
|
8373
|
+
if (!document || !meeting) return;
|
|
8374
|
+
return value;
|
|
8375
|
+
}
|
|
8376
|
+
function caseTitle(bundle, fallbackId) {
|
|
8377
|
+
return bundle.meeting.meeting.title?.trim() || bundle.document.title?.trim() || bundle.meeting.meeting.id || bundle.document.id || fallbackId;
|
|
8378
|
+
}
|
|
8379
|
+
function parseCase(value, fallbackId) {
|
|
8380
|
+
const record = asRecord(value);
|
|
8381
|
+
if (!record) return;
|
|
8382
|
+
const bundle = parseBundle(record.bundle) ?? parseBundle(record);
|
|
8383
|
+
if (!bundle) return;
|
|
8384
|
+
const id = stringValue(record.id).trim() || bundle.document.id || bundle.meeting.meeting.id || fallbackId;
|
|
8385
|
+
return {
|
|
8386
|
+
bundle,
|
|
8387
|
+
id,
|
|
8388
|
+
title: stringValue(record.title).trim() || caseTitle(bundle, id)
|
|
8389
|
+
};
|
|
8390
|
+
}
|
|
8391
|
+
function parseCasesFromText(contents, sourceLabel) {
|
|
8392
|
+
const parsed = parseJsonString(contents);
|
|
8393
|
+
if (!parsed) throw new Error(`failed to parse evaluation JSON: ${sourceLabel}`);
|
|
8394
|
+
if (Array.isArray(parsed)) return parsed.map((value, index) => parseCase(value, `${sourceLabel}:${index + 1}`)).filter((value) => Boolean(value));
|
|
8395
|
+
const record = asRecord(parsed);
|
|
8396
|
+
if (!record) return [];
|
|
8397
|
+
if (Array.isArray(record.cases)) return record.cases.map((value, index) => parseCase(value, `${sourceLabel}:${index + 1}`)).filter((value) => Boolean(value));
|
|
8398
|
+
const single = parseCase(record, sourceLabel);
|
|
8399
|
+
return single ? [single] : [];
|
|
8400
|
+
}
|
|
8401
|
+
async function readAutomationEvaluationCases(fixturePath) {
|
|
8402
|
+
const resolved = resolve(fixturePath);
|
|
8403
|
+
if ((await stat(resolved)).isDirectory()) {
|
|
8404
|
+
const entries = (await readdir(resolved)).filter((entry) => extname(entry).toLowerCase() === ".json").sort();
|
|
8405
|
+
const cases = [];
|
|
8406
|
+
for (const entry of entries) {
|
|
8407
|
+
const sourceLabel = basename(entry, ".json");
|
|
8408
|
+
const contents = await readFile(join(resolved, entry), "utf8");
|
|
8409
|
+
cases.push(...parseCasesFromText(contents, sourceLabel));
|
|
8410
|
+
}
|
|
8411
|
+
return cases;
|
|
8412
|
+
}
|
|
8413
|
+
return parseCasesFromText(await readFile(resolved, "utf8"), basename(resolved, ".json"));
|
|
8414
|
+
}
|
|
8415
|
+
//#endregion
|
|
7994
8416
|
//#region src/commands/shared.ts
|
|
7995
8417
|
function debug(enabled, ...values) {
|
|
7996
8418
|
if (enabled) console.error("[debug]", ...values);
|
|
@@ -8060,7 +8482,7 @@ function automationHelp() {
|
|
|
8060
8482
|
return `Granola automation
|
|
8061
8483
|
|
|
8062
8484
|
Usage:
|
|
8063
|
-
granola automation <rules|matches|runs|artefacts|health|recover|approve|reject|approve-artefact|reject-artefact|rerun> [options]
|
|
8485
|
+
granola automation <rules|matches|runs|artefacts|health|evaluate|recover|approve|reject|approve-artefact|reject-artefact|rerun> [options]
|
|
8064
8486
|
|
|
8065
8487
|
Subcommands:
|
|
8066
8488
|
rules List configured automation rules
|
|
@@ -8068,6 +8490,7 @@ Subcommands:
|
|
|
8068
8490
|
runs Show recent automation action runs
|
|
8069
8491
|
artefacts Show generated note and enrichment artefacts
|
|
8070
8492
|
health Show processing-health issues and recovery candidates
|
|
8493
|
+
evaluate Run fixture-backed harness evaluations
|
|
8071
8494
|
recover <issue-id> Recover a processing-health issue
|
|
8072
8495
|
approve <id> Approve a pending ask-user action run
|
|
8073
8496
|
reject <id> Reject a pending ask-user action run
|
|
@@ -8081,6 +8504,10 @@ Options:
|
|
|
8081
8504
|
--format <value> text, json, yaml (default: text)
|
|
8082
8505
|
--limit <n> Number of matches to show (default: 20)
|
|
8083
8506
|
--kind <value> notes or enrichment
|
|
8507
|
+
--fixture <path> Evaluation fixture file or directory
|
|
8508
|
+
--harness <value> Harness id or comma-separated ids for evaluation
|
|
8509
|
+
--provider <value> codex, openai, openrouter
|
|
8510
|
+
--model <value> Override the harness model for evaluation
|
|
8084
8511
|
--meeting <id> Filter artefacts to one meeting id
|
|
8085
8512
|
--severity <value> error or warning
|
|
8086
8513
|
--status <value> completed, failed, pending, skipped
|
|
@@ -8148,6 +8575,21 @@ function parseArtefactKind(value) {
|
|
|
8148
8575
|
default: throw new Error("invalid automation artefact kind: expected notes or enrichment");
|
|
8149
8576
|
}
|
|
8150
8577
|
}
|
|
8578
|
+
function parseHarnessIds(value) {
|
|
8579
|
+
if (value === void 0) return;
|
|
8580
|
+
if (typeof value !== "string") throw new Error("invalid harness list: expected a comma-separated string");
|
|
8581
|
+
const ids = value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
8582
|
+
return ids.length > 0 ? ids : void 0;
|
|
8583
|
+
}
|
|
8584
|
+
function parseProvider(value) {
|
|
8585
|
+
switch (value) {
|
|
8586
|
+
case void 0: return;
|
|
8587
|
+
case "codex":
|
|
8588
|
+
case "openai":
|
|
8589
|
+
case "openrouter": return value;
|
|
8590
|
+
default: throw new Error("invalid provider: expected codex, openai, or openrouter");
|
|
8591
|
+
}
|
|
8592
|
+
}
|
|
8151
8593
|
function parseArtefactStatus(value) {
|
|
8152
8594
|
switch (value) {
|
|
8153
8595
|
case void 0: return;
|
|
@@ -8166,6 +8608,30 @@ function renderArtefacts(artefacts, format) {
|
|
|
8166
8608
|
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(" - ")}`;
|
|
8167
8609
|
})].join("\n")}\n`;
|
|
8168
8610
|
}
|
|
8611
|
+
function renderEvaluations(result, format) {
|
|
8612
|
+
if (format === "json") return toJson(result);
|
|
8613
|
+
if (format === "yaml") return toYaml(result);
|
|
8614
|
+
if (result.results.length === 0) return "No evaluation results\n";
|
|
8615
|
+
const header = "CASE HARNESS STATUS PROVIDER/MODEL SUMMARY";
|
|
8616
|
+
const lines = result.results.map((entry) => {
|
|
8617
|
+
const harness = (entry.harnessName || entry.harnessId || "-").padEnd(20).slice(0, 20);
|
|
8618
|
+
const status = entry.status.padEnd(11).slice(0, 11);
|
|
8619
|
+
const providerModel = `${entry.provider || "-"}${entry.model ? `/${entry.model}` : ""}`.padEnd(30).slice(0, 30);
|
|
8620
|
+
const summary = entry.status === "failed" ? entry.error || "Evaluation failed" : [
|
|
8621
|
+
entry.structured?.title,
|
|
8622
|
+
entry.structured?.summary,
|
|
8623
|
+
entry.structured?.actionItems.length ? `${entry.structured.actionItems.length} action item(s)` : ""
|
|
8624
|
+
].filter(Boolean).join(" - ");
|
|
8625
|
+
return `${entry.caseTitle.padEnd(20).slice(0, 20)} ${harness} ${status} ${providerModel} ${summary}`;
|
|
8626
|
+
});
|
|
8627
|
+
return [
|
|
8628
|
+
`Evaluated ${result.results.length} run(s) across ${new Set(result.results.map((entry) => entry.caseId)).size} case(s)`,
|
|
8629
|
+
`Kind: ${result.kind}`,
|
|
8630
|
+
"",
|
|
8631
|
+
header,
|
|
8632
|
+
...lines
|
|
8633
|
+
].join("\n") + "\n";
|
|
8634
|
+
}
|
|
8169
8635
|
function parseSeverity(value) {
|
|
8170
8636
|
switch (value) {
|
|
8171
8637
|
case void 0: return;
|
|
@@ -8193,12 +8659,16 @@ function renderRuns(runs, format) {
|
|
|
8193
8659
|
const automationCommand = {
|
|
8194
8660
|
description: "Inspect automation rules and rule matches",
|
|
8195
8661
|
flags: {
|
|
8662
|
+
fixture: { type: "string" },
|
|
8196
8663
|
format: { type: "string" },
|
|
8664
|
+
harness: { type: "string" },
|
|
8197
8665
|
help: { type: "boolean" },
|
|
8198
8666
|
kind: { type: "string" },
|
|
8199
8667
|
limit: { type: "string" },
|
|
8200
8668
|
meeting: { type: "string" },
|
|
8669
|
+
model: { type: "string" },
|
|
8201
8670
|
note: { type: "string" },
|
|
8671
|
+
provider: { type: "string" },
|
|
8202
8672
|
severity: { type: "string" },
|
|
8203
8673
|
status: { type: "string" },
|
|
8204
8674
|
timeout: { type: "string" }
|
|
@@ -8253,6 +8723,18 @@ const automationCommand = {
|
|
|
8253
8723
|
console.log(renderProcessingIssues(result.issues, format).trimEnd());
|
|
8254
8724
|
return 0;
|
|
8255
8725
|
}
|
|
8726
|
+
case "evaluate": {
|
|
8727
|
+
const fixturePath = typeof commandFlags.fixture === "string" ? commandFlags.fixture.trim() : "";
|
|
8728
|
+
if (!fixturePath) throw new Error("automation evaluate requires --fixture <path>");
|
|
8729
|
+
const result = await app.evaluateAutomationCases(await readAutomationEvaluationCases(fixturePath), {
|
|
8730
|
+
harnessIds: parseHarnessIds(commandFlags.harness),
|
|
8731
|
+
kind: parseArtefactKind(commandFlags.kind) ?? "notes",
|
|
8732
|
+
model: typeof commandFlags.model === "string" ? commandFlags.model.trim() : void 0,
|
|
8733
|
+
provider: parseProvider(commandFlags.provider)
|
|
8734
|
+
});
|
|
8735
|
+
console.log(renderEvaluations(result, format).trimEnd());
|
|
8736
|
+
return 0;
|
|
8737
|
+
}
|
|
8256
8738
|
case "recover": {
|
|
8257
8739
|
const id = commandArgs[1]?.trim();
|
|
8258
8740
|
if (!id) throw new Error("missing processing issue id for recover");
|
|
@@ -8286,7 +8768,7 @@ const automationCommand = {
|
|
|
8286
8768
|
case void 0:
|
|
8287
8769
|
console.log(automationHelp());
|
|
8288
8770
|
return 1;
|
|
8289
|
-
default: throw new Error("invalid automation command: expected rules, matches, runs, artefacts, health, recover, approve, reject, approve-artefact, reject-artefact, or rerun");
|
|
8771
|
+
default: throw new Error("invalid automation command: expected rules, matches, runs, artefacts, health, evaluate, recover, approve, reject, approve-artefact, reject-artefact, or rerun");
|
|
8290
8772
|
}
|
|
8291
8773
|
}
|
|
8292
8774
|
};
|
|
@@ -8636,8 +9118,8 @@ async function openExternalUrl(url, options = {}) {
|
|
|
8636
9118
|
}
|
|
8637
9119
|
//#endregion
|
|
8638
9120
|
//#region src/web/generated.ts
|
|
8639
|
-
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=\"error\"] {\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=\"warning\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\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*/";
|
|
8640
|
-
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 processingIssues: \"/processing/issues\",\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 granolaProcessingIssuesPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.processingIssues, {\n limit: options.limit,\n meetingId: options.meetingId,\n severity: options.severity\n });\n}\nfunction granolaProcessingIssueRecoverPath(id) {\n return `${granolaTransportPaths.processingIssues}/${encodeURIComponent(id)}/recover`;\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 listProcessingIssues(options = {}) {\n return await this.requestJson(granolaProcessingIssuesPath(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 recoverProcessingIssue(id) {\n return await this.requestJson(granolaProcessingIssueRecoverPath(id), { method: \"POST\" });\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>Processing Health</h3><p>Catch stale syncs, missing transcripts, and failed or outdated note pipelines.</p></div><div class=jobs-list>`), _tmpl$38 = /* @__PURE__ */ template(`<div class=job-empty>No processing issues detected.`), _tmpl$39 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Open Meeting`), _tmpl$40 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Recover`), _tmpl$41 = /* @__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$42 = /* @__PURE__ */ template(`<div class=job-empty>No automation artefacts yet.`), _tmpl$43 = /* @__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$44 = /* @__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$45 = /* @__PURE__ */ template(`<div class=job-empty>`), _tmpl$46 = /* @__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$47 = /* @__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$48 = /* @__PURE__ */ template(`<div class=empty>`), _tmpl$49 = /* @__PURE__ */ template(`<div class=job-card><div class=job-card__head><div class=job-card__title></div><div class=job-card__meta>`), _tmpl$50 = /* @__PURE__ */ template(`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle`), _tmpl$51 = /* @__PURE__ */ template(`<button class=workspace-tab type=button>`), _tmpl$52 = /* @__PURE__ */ template(`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>`), _tmpl$53 = /* @__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 ProcessingIssuesPanel(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.issues.length > 0;\n },\n get fallback() {\n return _tmpl$38();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.issues.slice(0, 8);\n },\n children: (issue) => (() => {\n var _el$172 = _tmpl$36(), _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$179 = _el$178.nextSibling, _el$180 = _el$179.nextSibling;\n insert(_el$175, () => issue.title);\n insert(_el$176, () => issue.id);\n insert(_el$177, () => issue.severity);\n insert(_el$178, () => issue.kind);\n insert(_el$179, () => issue.detail);\n insert(_el$180, createComponent(Show, {\n get when() {\n return issue.meetingId;\n },\n get children() {\n var _el$181 = _tmpl$39();\n _el$181.$$click = () => {\n props.onOpenMeeting(issue.meetingId);\n };\n return _el$181;\n }\n }), null);\n insert(_el$180, createComponent(Show, {\n get when() {\n return issue.recoverable;\n },\n get children() {\n var _el$182 = _tmpl$40();\n _el$182.$$click = () => {\n props.onRecover(issue.id);\n };\n return _el$182;\n }\n }), null);\n createRenderEffect(() => setAttribute(_el$177, \"data-status\", issue.severity));\n return _el$172;\n })()\n });\n }\n }));\n return _el$168;\n })();\n}\nfunction AutomationArtefactsPanel(props) {\n return (() => {\n var _el$183 = _tmpl$41(), _el$185 = _el$183.firstChild.nextSibling;\n insert(_el$185, createComponent(Show, {\n get when() {\n return props.artefacts.length > 0;\n },\n get fallback() {\n return _tmpl$42();\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$187 = _tmpl$43(), _el$188 = _el$187.firstChild, _el$189 = _el$188.firstChild, _el$190 = _el$189.firstChild, _el$191 = _el$190.nextSibling, _el$192 = _el$189.nextSibling, _el$193 = _el$188.nextSibling, _el$195 = _el$193.nextSibling;\n _el$187.$$click = () => {\n props.onSelect(artefact.id);\n };\n insert(_el$190, () => artefact.structured.title);\n insert(_el$191, () => `${artefact.kind} • ${artefact.ruleName}`);\n insert(_el$192, () => artefact.status);\n insert(_el$193, () => artefact.meetingId);\n insert(_el$187, createComponent(Show, {\n get when() {\n return artefact.structured.summary;\n },\n get children() {\n var _el$194 = _tmpl$29();\n insert(_el$194, () => artefact.structured.summary);\n return _el$194;\n }\n }), _el$195);\n insert(_el$195, () => `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$187, \"data-selected\", _p$.e = _v$7);\n _v$8 !== _p$.t && setAttribute(_el$192, \"data-status\", _p$.t = _v$8);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n return _el$187;\n })()\n });\n }\n }));\n return _el$183;\n })();\n}\nfunction ArtefactReviewPanel(props) {\n return (() => {\n var _el$196 = _tmpl$44();\n _el$196.firstChild;\n insert(_el$196, createComponent(Show, {\n get when() {\n return props.artefact;\n },\n get fallback() {\n return (() => {\n var _el$198 = _tmpl$45();\n insert(_el$198, () => props.error || \"Select an automation artefact to review it.\");\n return _el$198;\n })();\n },\n children: (artefact) => (() => {\n var _el$199 = _tmpl$47(), _el$200 = _el$199.firstChild, _el$201 = _el$200.firstChild, _el$202 = _el$201.nextSibling, _el$203 = _el$202.nextSibling, _el$204 = _el$203.nextSibling, _el$228 = _el$200.nextSibling, _el$230 = _el$228.firstChild.nextSibling;\n insert(_el$201, () => `Status: ${artefact().status}`);\n insert(_el$202, () => `Kind: ${artefact().kind}`);\n insert(_el$203, () => `Meeting: ${artefact().meetingId}`);\n insert(_el$204, () => `Provider: ${artefact().provider}/${artefact().model}`);\n insert(_el$199, createComponent(Show, {\n get when() {\n return !props.error;\n },\n get fallback() {\n return (() => {\n var _el$231 = _tmpl$48();\n insert(_el$231, () => props.error);\n return _el$231;\n })();\n },\n get children() {\n var _el$205 = _tmpl$46(), _el$206 = _el$205.firstChild, _el$208 = _el$206.firstChild.nextSibling, _el$211 = _el$206.nextSibling.firstChild.nextSibling, _el$213 = _el$211.firstChild.nextSibling, _el$214 = _el$211.nextSibling, _el$216 = _el$214.firstChild.nextSibling, _el$217 = _el$214.nextSibling, _el$219 = _el$217.firstChild.nextSibling, _el$220 = _el$217.nextSibling, _el$222 = _el$220.firstChild.nextSibling, _el$224 = _el$220.nextSibling.firstChild, _el$225 = _el$224.nextSibling, _el$226 = _el$225.nextSibling, _el$227 = _el$226.nextSibling;\n insert(_el$208, () => props.bundle?.meeting.noteMarkdown || \"(No existing meeting notes)\");\n _el$213.$$input = (event) => {\n props.onDraftTitleChange(event.currentTarget.value);\n };\n _el$216.$$input = (event) => {\n props.onDraftSummaryChange(event.currentTarget.value);\n };\n insert(_el$216, () => props.draftSummary);\n _el$219.$$input = (event) => {\n props.onDraftMarkdownChange(event.currentTarget.value);\n };\n insert(_el$219, () => props.draftMarkdown);\n _el$222.$$input = (event) => {\n props.onReviewNoteChange(event.currentTarget.value);\n };\n insert(_el$222, () => props.reviewNote);\n addEventListener(_el$224, \"click\", props.onSave, true);\n addEventListener(_el$225, \"click\", props.onApprove, true);\n addEventListener(_el$226, \"click\", props.onReject, true);\n addEventListener(_el$227, \"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$225.disabled = _p$.e = _v$9);\n _v$0 !== _p$.t && (_el$226.disabled = _p$.t = _v$0);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n createRenderEffect(() => _el$213.value = props.draftTitle);\n return _el$205;\n }\n }), _el$228);\n insert(_el$230, createComponent(For, {\n get each() {\n return artefact().history.slice().reverse();\n },\n children: (entry) => (() => {\n var _el$232 = _tmpl$49(), _el$234 = _el$232.firstChild.firstChild, _el$235 = _el$234.nextSibling;\n insert(_el$234, () => entry.action);\n insert(_el$235, () => entry.at.slice(0, 19));\n insert(_el$232, createComponent(Show, {\n get when() {\n return entry.note;\n },\n get children() {\n var _el$236 = _tmpl$29();\n insert(_el$236, () => entry.note);\n return _el$236;\n }\n }), null);\n return _el$232;\n })()\n }));\n return _el$199;\n })()\n }), null);\n return _el$196;\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$237 = _tmpl$50(), _el$238 = _el$237.firstChild;\n insert(_el$237, createComponent(For, {\n each: [\n \"notes\",\n \"transcript\",\n \"metadata\",\n \"raw\"\n ],\n children: (tab) => (() => {\n var _el$239 = _tmpl$51();\n _el$239.$$click = () => {\n props.onSelectTab(tab);\n };\n insert(_el$239, tab === \"notes\" ? \"Notes\" : tab === \"transcript\" ? \"Transcript\" : tab === \"metadata\" ? \"Metadata\" : \"Raw\");\n createRenderEffect(() => setAttribute(_el$239, \"data-selected\", parsedTab() === tab ? \"true\" : void 0));\n return _el$239;\n })()\n }), _el$238);\n return _el$237;\n })(), createComponent(Show, {\n get when() {\n return props.selectedMeeting;\n },\n get fallback() {\n return (() => {\n var _el$240 = _tmpl$48();\n insert(_el$240, () => props.detailError || \"Select a meeting to inspect its notes and transcript.\");\n return _el$240;\n })();\n },\n children: (meeting) => [(() => {\n var _el$241 = _tmpl$52(), _el$242 = _el$241.firstChild, _el$243 = _el$242.nextSibling, _el$244 = _el$243.nextSibling;\n insert(_el$242, () => `ID: ${meeting().meeting.id}`);\n insert(_el$243, () => `Source: ${meeting().meeting.noteContentSource}`);\n insert(_el$244, () => `Transcript: ${meeting().meeting.transcriptSegmentCount} segments`);\n return _el$241;\n })(), createComponent(Show, {\n get when() {\n return !props.detailError;\n },\n get fallback() {\n return (() => {\n var _el$253 = _tmpl$48();\n insert(_el$253, () => props.detailError);\n return _el$253;\n })();\n },\n get children() {\n var _el$245 = _tmpl$53(), _el$247 = _el$245.firstChild.firstChild, _el$249 = _el$247.firstChild.nextSibling, _el$251 = _el$247.nextSibling.firstChild, _el$252 = _el$251.nextSibling;\n insert(_el$249, () => metadataLines(meeting()));\n insert(_el$251, () => details()?.title);\n insert(_el$252, () => details()?.body);\n return _el$245;\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 processingIssueError: \"\",\n processingIssues: [],\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 loadProcessingIssues = async () => {\n if (!client) return;\n try {\n setState(\"processingIssueError\", \"\");\n setState(\"processingIssues\", (await client.listProcessingIssues({ limit: 20 })).issues);\n } catch (error) {\n setState(\"processingIssueError\", error instanceof Error ? error.message : String(error));\n setState(\"processingIssues\", []);\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 loadProcessingIssues(),\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 recoverProcessingIssue = async (id) => {\n if (!client) return;\n setStatus(\"Recovering processing issue…\", \"busy\");\n try {\n const result = await client.recoverProcessingIssue(id);\n await refreshAll();\n setStatus(result.runCount > 0 ? `Recovered ${result.issue.kind} and re-ran ${result.runCount} pipeline${result.runCount === 1 ? \"\" : \"s\"}` : `Recovered ${result.issue.kind}`, \"ok\");\n } catch (error) {\n setState(\"processingIssueError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Recovery 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 processingIssueError: \"\",\n processingIssues: [],\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 loadProcessingIssues();\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(ProcessingIssuesPanel, {\n get issues() {\n return state.processingIssues;\n },\n onOpenMeeting: (meetingId) => {\n loadMeeting(meetingId);\n },\n onRecover: (issueId) => {\n recoverProcessingIssue(issueId);\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";
|
|
9121
|
+
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=\"error\"] {\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=\"warning\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\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-section h3 {\n margin: 18px 0 10px;\n font-size: 0.95rem;\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.detail-list {\n margin: 0;\n padding-left: 18px;\n display: grid;\n gap: 10px;\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*/";
|
|
9122
|
+
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 processingIssues: \"/processing/issues\",\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 granolaProcessingIssuesPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.processingIssues, {\n limit: options.limit,\n meetingId: options.meetingId,\n severity: options.severity\n });\n}\nfunction granolaProcessingIssueRecoverPath(id) {\n return `${granolaTransportPaths.processingIssues}/${encodeURIComponent(id)}/recover`;\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 evaluateAutomationCases(_cases, _options) {\n throw new Error(\"automation evaluate is not supported over attached servers yet\");\n }\n async listProcessingIssues(options = {}) {\n return await this.requestJson(granolaProcessingIssuesPath(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 recoverProcessingIssue(id) {\n return await this.requestJson(granolaProcessingIssueRecoverPath(id), { method: \"POST\" });\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>Processing Health</h3><p>Catch stale syncs, missing transcripts, and failed or outdated note pipelines.</p></div><div class=jobs-list>`), _tmpl$38 = /* @__PURE__ */ template(`<div class=job-empty>No processing issues detected.`), _tmpl$39 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Open Meeting`), _tmpl$40 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Recover`), _tmpl$41 = /* @__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$42 = /* @__PURE__ */ template(`<div class=job-empty>No automation artefacts yet.`), _tmpl$43 = /* @__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$44 = /* @__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$45 = /* @__PURE__ */ template(`<div class=job-empty>`), _tmpl$46 = /* @__PURE__ */ template(`<div class=detail-section><h3>Action Items</h3><ul class=detail-list>`), _tmpl$47 = /* @__PURE__ */ template(`<div class=detail-section><h3>Participant Summaries</h3><ul class=detail-list>`), _tmpl$48 = /* @__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$49 = /* @__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$50 = /* @__PURE__ */ template(`<div class=empty>`), _tmpl$51 = /* @__PURE__ */ template(`<span>`), _tmpl$52 = /* @__PURE__ */ template(`<li><strong>`), _tmpl$53 = /* @__PURE__ */ template(`<li><strong></strong><div>`), _tmpl$54 = /* @__PURE__ */ template(`<div class=job-card><div class=job-card__head><div class=job-card__title></div><div class=job-card__meta>`), _tmpl$55 = /* @__PURE__ */ template(`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle`), _tmpl$56 = /* @__PURE__ */ template(`<button class=workspace-tab type=button>`), _tmpl$57 = /* @__PURE__ */ template(`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>`), _tmpl$58 = /* @__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 `Owner candidates: ${record.roleHelpers.ownerCandidates.length ? record.roleHelpers.ownerCandidates.map((candidate) => candidate.label).join(\", \") : \"none\"}`,\n `Speakers: ${record.roleHelpers.speakers.length ? record.roleHelpers.speakers.map((speaker) => `${speaker.label} (${speaker.segmentCount})`).join(\", \") : \"none\"}`\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 ProcessingIssuesPanel(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.issues.length > 0;\n },\n get fallback() {\n return _tmpl$38();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.issues.slice(0, 8);\n },\n children: (issue) => (() => {\n var _el$172 = _tmpl$36(), _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$179 = _el$178.nextSibling, _el$180 = _el$179.nextSibling;\n insert(_el$175, () => issue.title);\n insert(_el$176, () => issue.id);\n insert(_el$177, () => issue.severity);\n insert(_el$178, () => issue.kind);\n insert(_el$179, () => issue.detail);\n insert(_el$180, createComponent(Show, {\n get when() {\n return issue.meetingId;\n },\n get children() {\n var _el$181 = _tmpl$39();\n _el$181.$$click = () => {\n props.onOpenMeeting(issue.meetingId);\n };\n return _el$181;\n }\n }), null);\n insert(_el$180, createComponent(Show, {\n get when() {\n return issue.recoverable;\n },\n get children() {\n var _el$182 = _tmpl$40();\n _el$182.$$click = () => {\n props.onRecover(issue.id);\n };\n return _el$182;\n }\n }), null);\n createRenderEffect(() => setAttribute(_el$177, \"data-status\", issue.severity));\n return _el$172;\n })()\n });\n }\n }));\n return _el$168;\n })();\n}\nfunction AutomationArtefactsPanel(props) {\n return (() => {\n var _el$183 = _tmpl$41(), _el$185 = _el$183.firstChild.nextSibling;\n insert(_el$185, createComponent(Show, {\n get when() {\n return props.artefacts.length > 0;\n },\n get fallback() {\n return _tmpl$42();\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$187 = _tmpl$43(), _el$188 = _el$187.firstChild, _el$189 = _el$188.firstChild, _el$190 = _el$189.firstChild, _el$191 = _el$190.nextSibling, _el$192 = _el$189.nextSibling, _el$193 = _el$188.nextSibling, _el$195 = _el$193.nextSibling;\n _el$187.$$click = () => {\n props.onSelect(artefact.id);\n };\n insert(_el$190, () => artefact.structured.title);\n insert(_el$191, () => `${artefact.kind} • ${artefact.ruleName}`);\n insert(_el$192, () => artefact.status);\n insert(_el$193, () => artefact.meetingId);\n insert(_el$187, createComponent(Show, {\n get when() {\n return artefact.structured.summary;\n },\n get children() {\n var _el$194 = _tmpl$29();\n insert(_el$194, () => artefact.structured.summary);\n return _el$194;\n }\n }), _el$195);\n insert(_el$195, () => `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$187, \"data-selected\", _p$.e = _v$7);\n _v$8 !== _p$.t && setAttribute(_el$192, \"data-status\", _p$.t = _v$8);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n return _el$187;\n })()\n });\n }\n }));\n return _el$183;\n })();\n}\nfunction ArtefactReviewPanel(props) {\n return (() => {\n var _el$196 = _tmpl$44();\n _el$196.firstChild;\n insert(_el$196, createComponent(Show, {\n get when() {\n return props.artefact;\n },\n get fallback() {\n return (() => {\n var _el$198 = _tmpl$45();\n insert(_el$198, () => props.error || \"Select an automation artefact to review it.\");\n return _el$198;\n })();\n },\n children: (artefact) => (() => {\n var _el$199 = _tmpl$49(), _el$200 = _el$199.firstChild, _el$201 = _el$200.firstChild, _el$202 = _el$201.nextSibling, _el$203 = _el$202.nextSibling, _el$204 = _el$203.nextSibling, _el$234 = _el$200.nextSibling, _el$236 = _el$234.firstChild.nextSibling;\n insert(_el$201, () => `Status: ${artefact().status}`);\n insert(_el$202, () => `Kind: ${artefact().kind}`);\n insert(_el$203, () => `Meeting: ${artefact().meetingId}`);\n insert(_el$204, () => `Provider: ${artefact().provider}/${artefact().model}`);\n insert(_el$199, createComponent(Show, {\n get when() {\n return !props.error;\n },\n get fallback() {\n return (() => {\n var _el$237 = _tmpl$50();\n insert(_el$237, () => props.error);\n return _el$237;\n })();\n },\n get children() {\n var _el$205 = _tmpl$48(), _el$206 = _el$205.firstChild, _el$208 = _el$206.firstChild.nextSibling, _el$209 = _el$206.nextSibling, _el$211 = _el$209.firstChild.nextSibling, _el$213 = _el$211.firstChild.nextSibling, _el$214 = _el$211.nextSibling, _el$216 = _el$214.firstChild.nextSibling, _el$217 = _el$214.nextSibling, _el$219 = _el$217.firstChild.nextSibling, _el$220 = _el$217.nextSibling, _el$222 = _el$220.firstChild.nextSibling, _el$224 = _el$220.nextSibling.firstChild, _el$225 = _el$224.nextSibling, _el$226 = _el$225.nextSibling, _el$227 = _el$226.nextSibling;\n insert(_el$208, () => props.bundle?.meeting.noteMarkdown || \"(No existing meeting notes)\");\n _el$213.$$input = (event) => {\n props.onDraftTitleChange(event.currentTarget.value);\n };\n _el$216.$$input = (event) => {\n props.onDraftSummaryChange(event.currentTarget.value);\n };\n insert(_el$216, () => props.draftSummary);\n _el$219.$$input = (event) => {\n props.onDraftMarkdownChange(event.currentTarget.value);\n };\n insert(_el$219, () => props.draftMarkdown);\n _el$222.$$input = (event) => {\n props.onReviewNoteChange(event.currentTarget.value);\n };\n insert(_el$222, () => props.reviewNote);\n addEventListener(_el$224, \"click\", props.onSave, true);\n addEventListener(_el$225, \"click\", props.onApprove, true);\n addEventListener(_el$226, \"click\", props.onReject, true);\n addEventListener(_el$227, \"click\", props.onRerun, true);\n insert(_el$209, createComponent(Show, {\n get when() {\n return artefact().structured.actionItems.length > 0;\n },\n get children() {\n var _el$228 = _tmpl$46(), _el$230 = _el$228.firstChild.nextSibling;\n insert(_el$230, createComponent(For, {\n get each() {\n return artefact().structured.actionItems;\n },\n children: (item) => (() => {\n var _el$238 = _tmpl$52(), _el$239 = _el$238.firstChild;\n insert(_el$239, () => item.title);\n insert(_el$238, createComponent(Show, {\n get when() {\n return item.owner;\n },\n get children() {\n var _el$240 = _tmpl$51();\n insert(_el$240, () => ` • ${item.owner}`);\n return _el$240;\n }\n }), null);\n insert(_el$238, createComponent(Show, {\n get when() {\n return item.dueDate;\n },\n get children() {\n var _el$241 = _tmpl$51();\n insert(_el$241, () => ` • due ${item.dueDate}`);\n return _el$241;\n }\n }), null);\n return _el$238;\n })()\n }));\n return _el$228;\n }\n }), null);\n insert(_el$209, createComponent(Show, {\n get when() {\n return (artefact().structured.participantSummaries?.length ?? 0) > 0;\n },\n get children() {\n var _el$231 = _tmpl$47(), _el$233 = _el$231.firstChild.nextSibling;\n insert(_el$233, createComponent(For, {\n get each() {\n return artefact().structured.participantSummaries;\n },\n children: (summary) => (() => {\n var _el$242 = _tmpl$53(), _el$243 = _el$242.firstChild, _el$245 = _el$243.nextSibling;\n insert(_el$243, () => summary.speaker);\n insert(_el$242, createComponent(Show, {\n get when() {\n return summary.role;\n },\n get children() {\n var _el$244 = _tmpl$51();\n insert(_el$244, () => ` • ${summary.role}`);\n return _el$244;\n }\n }), _el$245);\n insert(_el$245, () => summary.summary);\n return _el$242;\n })()\n }));\n return _el$231;\n }\n }), null);\n createRenderEffect((_p$) => {\n var _v$9 = artefact().status === \"superseded\", _v$0 = artefact().status === \"superseded\";\n _v$9 !== _p$.e && (_el$225.disabled = _p$.e = _v$9);\n _v$0 !== _p$.t && (_el$226.disabled = _p$.t = _v$0);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n createRenderEffect(() => _el$213.value = props.draftTitle);\n return _el$205;\n }\n }), _el$234);\n insert(_el$236, createComponent(For, {\n get each() {\n return artefact().history.slice().reverse();\n },\n children: (entry) => (() => {\n var _el$246 = _tmpl$54(), _el$248 = _el$246.firstChild.firstChild, _el$249 = _el$248.nextSibling;\n insert(_el$248, () => entry.action);\n insert(_el$249, () => entry.at.slice(0, 19));\n insert(_el$246, createComponent(Show, {\n get when() {\n return entry.note;\n },\n get children() {\n var _el$250 = _tmpl$29();\n insert(_el$250, () => entry.note);\n return _el$250;\n }\n }), null);\n return _el$246;\n })()\n }));\n return _el$199;\n })()\n }), null);\n return _el$196;\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$251 = _tmpl$55(), _el$252 = _el$251.firstChild;\n insert(_el$251, createComponent(For, {\n each: [\n \"notes\",\n \"transcript\",\n \"metadata\",\n \"raw\"\n ],\n children: (tab) => (() => {\n var _el$253 = _tmpl$56();\n _el$253.$$click = () => {\n props.onSelectTab(tab);\n };\n insert(_el$253, tab === \"notes\" ? \"Notes\" : tab === \"transcript\" ? \"Transcript\" : tab === \"metadata\" ? \"Metadata\" : \"Raw\");\n createRenderEffect(() => setAttribute(_el$253, \"data-selected\", parsedTab() === tab ? \"true\" : void 0));\n return _el$253;\n })()\n }), _el$252);\n return _el$251;\n })(), createComponent(Show, {\n get when() {\n return props.selectedMeeting;\n },\n get fallback() {\n return (() => {\n var _el$254 = _tmpl$50();\n insert(_el$254, () => props.detailError || \"Select a meeting to inspect its notes and transcript.\");\n return _el$254;\n })();\n },\n children: (meeting) => [(() => {\n var _el$255 = _tmpl$57(), _el$256 = _el$255.firstChild, _el$257 = _el$256.nextSibling, _el$258 = _el$257.nextSibling;\n insert(_el$256, () => `ID: ${meeting().meeting.id}`);\n insert(_el$257, () => `Source: ${meeting().meeting.noteContentSource}`);\n insert(_el$258, () => `Transcript: ${meeting().meeting.transcriptSegmentCount} segments`);\n return _el$255;\n })(), createComponent(Show, {\n get when() {\n return !props.detailError;\n },\n get fallback() {\n return (() => {\n var _el$267 = _tmpl$50();\n insert(_el$267, () => props.detailError);\n return _el$267;\n })();\n },\n get children() {\n var _el$259 = _tmpl$58(), _el$261 = _el$259.firstChild.firstChild, _el$263 = _el$261.firstChild.nextSibling, _el$265 = _el$261.nextSibling.firstChild, _el$266 = _el$265.nextSibling;\n insert(_el$263, () => metadataLines(meeting()));\n insert(_el$265, () => details()?.title);\n insert(_el$266, () => details()?.body);\n return _el$259;\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 processingIssueError: \"\",\n processingIssues: [],\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 loadProcessingIssues = async () => {\n if (!client) return;\n try {\n setState(\"processingIssueError\", \"\");\n setState(\"processingIssues\", (await client.listProcessingIssues({ limit: 20 })).issues);\n } catch (error) {\n setState(\"processingIssueError\", error instanceof Error ? error.message : String(error));\n setState(\"processingIssues\", []);\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 loadProcessingIssues(),\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 recoverProcessingIssue = async (id) => {\n if (!client) return;\n setStatus(\"Recovering processing issue…\", \"busy\");\n try {\n const result = await client.recoverProcessingIssue(id);\n await refreshAll();\n setStatus(result.runCount > 0 ? `Recovered ${result.issue.kind} and re-ran ${result.runCount} pipeline${result.runCount === 1 ? \"\" : \"s\"}` : `Recovered ${result.issue.kind}`, \"ok\");\n } catch (error) {\n setState(\"processingIssueError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Recovery 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 processingIssueError: \"\",\n processingIssues: [],\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 loadProcessingIssues();\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(ProcessingIssuesPanel, {\n get issues() {\n return state.processingIssues;\n },\n onOpenMeeting: (meetingId) => {\n loadMeeting(meetingId);\n },\n onRecover: (issueId) => {\n recoverProcessingIssue(issueId);\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";
|
|
8641
9123
|
//#endregion
|
|
8642
9124
|
//#region src/web/assets.ts
|
|
8643
9125
|
const granolaWebAssetPaths = {
|