granola-toolkit 0.47.0 → 0.49.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.
Files changed (2) hide show
  1. package/dist/cli.js +815 -29
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -19,6 +19,7 @@ const granolaTransportPaths = {
19
19
  authStatus: "/auth/status",
20
20
  authUnlock: "/auth/unlock",
21
21
  automationMatches: "/automation/matches",
22
+ automationArtefacts: "/automation/artefacts",
22
23
  automationRules: "/automation/rules",
23
24
  automationRuns: "/automation/runs",
24
25
  events: "/events",
@@ -86,9 +87,20 @@ function granolaAutomationRunsPath(options = {}) {
86
87
  status: options.status
87
88
  });
88
89
  }
90
+ function granolaAutomationArtefactsPath(options = {}) {
91
+ return appendSearchParams(granolaTransportPaths.automationArtefacts, {
92
+ kind: options.kind,
93
+ limit: options.limit,
94
+ meetingId: options.meetingId,
95
+ status: options.status
96
+ });
97
+ }
89
98
  function granolaAutomationRunDecisionPath(id, decision) {
90
99
  return `${granolaTransportPaths.automationRuns}/${encodeURIComponent(id)}/${decision}`;
91
100
  }
101
+ function granolaAutomationArtefactRerunPath(id) {
102
+ return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}/rerun`;
103
+ }
92
104
  function granolaExportJobRerunPath(id) {
93
105
  return `${granolaTransportPaths.exportJobs}/${encodeURIComponent(id)}/rerun`;
94
106
  }
@@ -191,6 +203,9 @@ var GranolaServerClient = class GranolaServerClient {
191
203
  async inspectAuth() {
192
204
  return await this.requestJson(granolaTransportPaths.authStatus);
193
205
  }
206
+ async listAutomationArtefacts(options = {}) {
207
+ return await this.requestJson(granolaAutomationArtefactsPath(options));
208
+ }
194
209
  async listAutomationRules() {
195
210
  return await this.requestJson(granolaTransportPaths.automationRules);
196
211
  }
@@ -208,6 +223,9 @@ var GranolaServerClient = class GranolaServerClient {
208
223
  method: "POST"
209
224
  });
210
225
  }
226
+ async rerunAutomationArtefact(id) {
227
+ return await this.requestJson(granolaAutomationArtefactRerunPath(id), { method: "POST" });
228
+ }
211
229
  async inspectSync() {
212
230
  return cloneValue(this.#state.sync);
213
231
  }
@@ -449,7 +467,7 @@ function asRecord(value) {
449
467
  function stringValue(value) {
450
468
  return typeof value === "string" ? value : "";
451
469
  }
452
- function stringArray$1(value) {
470
+ function stringArray$2(value) {
453
471
  if (!Array.isArray(value)) return [];
454
472
  return value.filter((item) => typeof item === "string");
455
473
  }
@@ -2612,6 +2630,8 @@ function defaultGranolaToolkitPersistenceLayout(options = {}) {
2612
2630
  const targetPlatform = options.platform ?? platform();
2613
2631
  const dataDirectory = defaultGranolaToolkitDataDirectory(targetPlatform, options.homeDirectory ?? homedir());
2614
2632
  return {
2633
+ agentHarnessesFile: join(dataDirectory, "agent-harnesses.json"),
2634
+ automationArtefactsFile: join(dataDirectory, "automation-artefacts.json"),
2615
2635
  automationMatchesFile: join(dataDirectory, "automation-matches.jsonl"),
2616
2636
  automationRulesFile: join(dataDirectory, "automation-rules.json"),
2617
2637
  automationRunsFile: join(dataDirectory, "automation-runs.jsonl"),
@@ -2627,6 +2647,333 @@ function defaultGranolaToolkitPersistenceLayout(options = {}) {
2627
2647
  };
2628
2648
  }
2629
2649
  //#endregion
2650
+ //#region src/agent-harnesses.ts
2651
+ function stringArray$1(value) {
2652
+ if (!Array.isArray(value)) return;
2653
+ const values = value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
2654
+ return values.length > 0 ? [...new Set(values)] : void 0;
2655
+ }
2656
+ function parseMatch(value) {
2657
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
2658
+ const record = value;
2659
+ return {
2660
+ calendarEventIds: stringArray$1(record.calendarEventIds),
2661
+ eventKinds: stringArray$1(record.eventKinds),
2662
+ folderIds: stringArray$1(record.folderIds),
2663
+ folderNames: stringArray$1(record.folderNames),
2664
+ meetingIds: stringArray$1(record.meetingIds),
2665
+ recurringEventIds: stringArray$1(record.recurringEventIds),
2666
+ tags: stringArray$1(record.tags),
2667
+ titleIncludes: stringArray$1(record.titleIncludes),
2668
+ titleMatches: typeof record.titleMatches === "string" && record.titleMatches.trim() ? record.titleMatches.trim() : void 0,
2669
+ transcriptLoaded: typeof record.transcriptLoaded === "boolean" ? record.transcriptLoaded : void 0
2670
+ };
2671
+ }
2672
+ function parseHarness(value) {
2673
+ if (!value || typeof value !== "object" || Array.isArray(value)) return;
2674
+ const record = value;
2675
+ const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : void 0;
2676
+ const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : void 0;
2677
+ if (!id || !name) return;
2678
+ const provider = record.provider === "codex" || record.provider === "openai" || record.provider === "openrouter" ? record.provider : void 0;
2679
+ const prompt = typeof record.prompt === "string" && record.prompt.trim() ? record.prompt.trim() : void 0;
2680
+ const promptFile = typeof record.promptFile === "string" && record.promptFile.trim() ? record.promptFile.trim() : void 0;
2681
+ if (!prompt && !promptFile) return;
2682
+ return {
2683
+ cwd: typeof record.cwd === "string" && record.cwd.trim() ? record.cwd.trim() : void 0,
2684
+ fallbackHarnessIds: stringArray$1(record.fallbackHarnessIds),
2685
+ id,
2686
+ match: parseMatch(record.match),
2687
+ model: typeof record.model === "string" && record.model.trim() ? record.model.trim() : void 0,
2688
+ name,
2689
+ priority: typeof record.priority === "number" && Number.isFinite(record.priority) ? record.priority : typeof record.priority === "string" && /^-?\d+$/.test(record.priority) ? Number(record.priority) : void 0,
2690
+ prompt,
2691
+ promptFile,
2692
+ provider,
2693
+ systemPrompt: typeof record.systemPrompt === "string" && record.systemPrompt.trim() ? record.systemPrompt.trim() : void 0,
2694
+ systemPromptFile: typeof record.systemPromptFile === "string" && record.systemPromptFile.trim() ? record.systemPromptFile.trim() : void 0
2695
+ };
2696
+ }
2697
+ function cloneHarness(harness) {
2698
+ return {
2699
+ ...harness,
2700
+ fallbackHarnessIds: harness.fallbackHarnessIds ? [...harness.fallbackHarnessIds] : void 0,
2701
+ match: harness.match ? {
2702
+ ...harness.match,
2703
+ calendarEventIds: harness.match.calendarEventIds ? [...harness.match.calendarEventIds] : void 0,
2704
+ eventKinds: harness.match.eventKinds ? [...harness.match.eventKinds] : void 0,
2705
+ folderIds: harness.match.folderIds ? [...harness.match.folderIds] : void 0,
2706
+ folderNames: harness.match.folderNames ? [...harness.match.folderNames] : void 0,
2707
+ meetingIds: harness.match.meetingIds ? [...harness.match.meetingIds] : void 0,
2708
+ recurringEventIds: harness.match.recurringEventIds ? [...harness.match.recurringEventIds] : void 0,
2709
+ tags: harness.match.tags ? [...harness.match.tags] : void 0,
2710
+ titleIncludes: harness.match.titleIncludes ? [...harness.match.titleIncludes] : void 0
2711
+ } : void 0
2712
+ };
2713
+ }
2714
+ function includesIgnoreCase$1(candidate, values) {
2715
+ const lowerCandidate = candidate.toLowerCase();
2716
+ return values.some((value) => lowerCandidate.includes(value.toLowerCase()));
2717
+ }
2718
+ function harnessSpecificity(match) {
2719
+ if (!match) return 0;
2720
+ return [
2721
+ match.calendarEventIds?.length ?? 0,
2722
+ match.eventKinds?.length ?? 0,
2723
+ match.folderIds?.length ?? 0,
2724
+ match.folderNames?.length ?? 0,
2725
+ match.meetingIds?.length ?? 0,
2726
+ match.recurringEventIds?.length ?? 0,
2727
+ match.tags?.length ?? 0,
2728
+ match.titleIncludes?.length ?? 0,
2729
+ match.titleMatches ? 1 : 0,
2730
+ match.transcriptLoaded != null ? 1 : 0
2731
+ ].reduce((total, count) => total + count, 0);
2732
+ }
2733
+ function matchesHarness(harness, context) {
2734
+ const match = harness.match;
2735
+ if (!match) return true;
2736
+ if (match.eventKinds?.length && !match.eventKinds.includes(context.match.eventKind)) return false;
2737
+ if (match.meetingIds?.length && !match.meetingIds.includes(context.match.meetingId)) return false;
2738
+ if (match.folderIds?.length && !context.match.folders.some((folder) => match.folderIds?.includes(folder.id))) return false;
2739
+ if (match.folderNames?.length && !context.match.folders.some((folder) => match.folderNames?.includes(folder.name))) return false;
2740
+ if (match.tags?.length && !context.match.tags.some((tag) => match.tags?.includes(tag))) return false;
2741
+ if (match.transcriptLoaded != null && context.match.transcriptLoaded !== match.transcriptLoaded) return false;
2742
+ if (match.titleIncludes?.length && !includesIgnoreCase$1(context.match.title, match.titleIncludes)) return false;
2743
+ if (match.titleMatches) try {
2744
+ if (!new RegExp(match.titleMatches, "i").test(context.match.title)) return false;
2745
+ } catch {
2746
+ return false;
2747
+ }
2748
+ const calendarEventId = context.bundle?.document.calendarEvent?.id;
2749
+ if (match.calendarEventIds?.length && !calendarEventId) return false;
2750
+ if (match.calendarEventIds?.length && !match.calendarEventIds.includes(calendarEventId)) return false;
2751
+ const recurringEventId = context.bundle?.document.calendarEvent?.recurringEventId;
2752
+ if (match.recurringEventIds?.length && !recurringEventId) return false;
2753
+ if (match.recurringEventIds?.length && !match.recurringEventIds.includes(recurringEventId)) return false;
2754
+ return true;
2755
+ }
2756
+ function matchAgentHarnesses(harnesses, context) {
2757
+ return harnesses.filter((harness) => matchesHarness(harness, context)).slice().sort((left, right) => {
2758
+ const priority = (right.priority ?? 0) - (left.priority ?? 0);
2759
+ if (priority !== 0) return priority;
2760
+ return harnessSpecificity(right.match) - harnessSpecificity(left.match);
2761
+ }).map(cloneHarness);
2762
+ }
2763
+ function resolveAgentHarness(harnesses, context, harnessId) {
2764
+ if (harnessId?.trim()) {
2765
+ const harness = harnesses.find((candidate) => candidate.id === harnessId.trim());
2766
+ if (!harness) throw new Error(`agent harness not found: ${harnessId.trim()}`);
2767
+ return cloneHarness(harness);
2768
+ }
2769
+ return matchAgentHarnesses(harnesses, context)[0];
2770
+ }
2771
+ var FileAgentHarnessStore = class {
2772
+ constructor(filePath = defaultAgentHarnessesFilePath()) {
2773
+ this.filePath = filePath;
2774
+ }
2775
+ async readHarnesses() {
2776
+ try {
2777
+ const parsed = parseJsonString(await readFile(this.filePath, "utf8"));
2778
+ return (Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.harnesses) ? parsed.harnesses : []).map((harness) => parseHarness(harness)).filter((harness) => Boolean(harness)).map(cloneHarness);
2779
+ } catch {
2780
+ return [];
2781
+ }
2782
+ }
2783
+ };
2784
+ function defaultAgentHarnessesFilePath() {
2785
+ return defaultGranolaToolkitPersistenceLayout().agentHarnessesFile;
2786
+ }
2787
+ function createDefaultAgentHarnessStore(filePath) {
2788
+ return new FileAgentHarnessStore(filePath);
2789
+ }
2790
+ //#endregion
2791
+ //#region src/automation-artefacts.ts
2792
+ const AUTOMATION_ARTEFACTS_VERSION = 1;
2793
+ const MAX_AUTOMATION_ARTEFACTS = 200;
2794
+ function cloneAttempt(attempt) {
2795
+ return { ...attempt };
2796
+ }
2797
+ function cloneSection(section) {
2798
+ return { ...section };
2799
+ }
2800
+ function cloneActionItem(actionItem) {
2801
+ return { ...actionItem };
2802
+ }
2803
+ function cloneStructured(structured) {
2804
+ return {
2805
+ ...structured,
2806
+ actionItems: structured.actionItems.map((item) => cloneActionItem(item)),
2807
+ decisions: [...structured.decisions],
2808
+ followUps: [...structured.followUps],
2809
+ highlights: [...structured.highlights],
2810
+ metadata: structured.metadata ? structuredClone(structured.metadata) : void 0,
2811
+ sections: structured.sections.map((section) => cloneSection(section))
2812
+ };
2813
+ }
2814
+ function cloneArtefact(artefact) {
2815
+ return {
2816
+ ...artefact,
2817
+ attempts: artefact.attempts.map((attempt) => cloneAttempt(attempt)),
2818
+ structured: cloneStructured(artefact.structured)
2819
+ };
2820
+ }
2821
+ function normaliseSection(value) {
2822
+ const record = asRecord(value);
2823
+ if (!record) return;
2824
+ const title = stringValue(record.title).trim();
2825
+ const body = stringValue(record.body).trim();
2826
+ if (!title || !body) return;
2827
+ return {
2828
+ body,
2829
+ title
2830
+ };
2831
+ }
2832
+ function normaliseActionItem(value) {
2833
+ const record = asRecord(value);
2834
+ if (!record) return;
2835
+ const title = stringValue(record.title).trim();
2836
+ if (!title) return;
2837
+ return {
2838
+ dueDate: stringValue(record.dueDate).trim() || void 0,
2839
+ owner: stringValue(record.owner).trim() || void 0,
2840
+ title
2841
+ };
2842
+ }
2843
+ function normaliseAttempt(value) {
2844
+ const record = asRecord(value);
2845
+ if (!record) return;
2846
+ const provider = stringValue(record.provider).trim();
2847
+ const model = stringValue(record.model).trim();
2848
+ const harnessId = stringValue(record.harnessId).trim();
2849
+ const error = stringValue(record.error).trim();
2850
+ if (!provider && !model && !harnessId && !error) return;
2851
+ return {
2852
+ error: error || void 0,
2853
+ harnessId: harnessId || void 0,
2854
+ model: model || void 0,
2855
+ provider: provider === "codex" || provider === "openai" || provider === "openrouter" ? provider : void 0
2856
+ };
2857
+ }
2858
+ function normaliseStructured(value) {
2859
+ const record = asRecord(value);
2860
+ if (!record) return;
2861
+ const title = stringValue(record.title).trim();
2862
+ const markdown = stringValue(record.markdown).trim();
2863
+ if (!title || !markdown) return;
2864
+ return {
2865
+ actionItems: Array.isArray(record.actionItems) ? record.actionItems.map((item) => normaliseActionItem(item)).filter((item) => Boolean(item)) : [],
2866
+ decisions: stringArray$2(record.decisions).map((item) => item.trim()).filter(Boolean),
2867
+ followUps: stringArray$2(record.followUps).map((item) => item.trim()).filter(Boolean),
2868
+ highlights: stringArray$2(record.highlights).map((item) => item.trim()).filter(Boolean),
2869
+ markdown,
2870
+ metadata: asRecord(record.metadata),
2871
+ sections: Array.isArray(record.sections) ? record.sections.map((section) => normaliseSection(section)).filter((section) => Boolean(section)) : [],
2872
+ summary: stringValue(record.summary).trim() || void 0,
2873
+ title
2874
+ };
2875
+ }
2876
+ function normaliseArtefact(value) {
2877
+ const record = asRecord(value);
2878
+ if (!record) return;
2879
+ const id = stringValue(record.id).trim();
2880
+ const runId = stringValue(record.runId).trim();
2881
+ const meetingId = stringValue(record.meetingId).trim();
2882
+ const eventId = stringValue(record.eventId).trim();
2883
+ const matchId = stringValue(record.matchId).trim();
2884
+ const ruleId = stringValue(record.ruleId).trim();
2885
+ const ruleName = stringValue(record.ruleName).trim();
2886
+ const actionId = stringValue(record.actionId).trim();
2887
+ const actionName = stringValue(record.actionName).trim();
2888
+ const createdAt = stringValue(record.createdAt).trim();
2889
+ const updatedAt = stringValue(record.updatedAt).trim();
2890
+ const rawOutput = stringValue(record.rawOutput).trim();
2891
+ const prompt = stringValue(record.prompt).trim();
2892
+ const model = stringValue(record.model).trim();
2893
+ const provider = stringValue(record.provider).trim();
2894
+ const kind = stringValue(record.kind).trim();
2895
+ const status = stringValue(record.status).trim();
2896
+ const parseMode = stringValue(record.parseMode).trim();
2897
+ const structured = normaliseStructured(record.structured);
2898
+ if (!id || !runId || !meetingId || !eventId || !matchId || !ruleId || !ruleName || !actionId || !actionName || !createdAt || !updatedAt || !rawOutput || !prompt || !model || !structured || kind !== "enrichment" && kind !== "notes" || provider !== "codex" && provider !== "openai" && provider !== "openrouter" || status !== "approved" && status !== "generated" && status !== "rejected" && status !== "superseded" || parseMode !== "json" && parseMode !== "markdown-fallback") return;
2899
+ return {
2900
+ actionId,
2901
+ actionName,
2902
+ attempts: Array.isArray(record.attempts) ? record.attempts.map((attempt) => normaliseAttempt(attempt)).filter((attempt) => Boolean(attempt)) : [],
2903
+ createdAt,
2904
+ eventId,
2905
+ id,
2906
+ kind,
2907
+ matchId,
2908
+ meetingId,
2909
+ model,
2910
+ parseMode,
2911
+ prompt,
2912
+ provider,
2913
+ rawOutput,
2914
+ rerunOfId: stringValue(record.rerunOfId).trim() || void 0,
2915
+ ruleId,
2916
+ ruleName,
2917
+ runId,
2918
+ status,
2919
+ structured,
2920
+ supersededById: stringValue(record.supersededById).trim() || void 0,
2921
+ updatedAt
2922
+ };
2923
+ }
2924
+ function normaliseFile(parsed) {
2925
+ const record = asRecord(parsed);
2926
+ if (!record || record.version !== AUTOMATION_ARTEFACTS_VERSION || !Array.isArray(record.artefacts)) return {
2927
+ artefacts: [],
2928
+ version: AUTOMATION_ARTEFACTS_VERSION
2929
+ };
2930
+ return {
2931
+ artefacts: record.artefacts.map((artefact) => normaliseArtefact(artefact)).filter((artefact) => Boolean(artefact)).slice(0, MAX_AUTOMATION_ARTEFACTS),
2932
+ version: AUTOMATION_ARTEFACTS_VERSION
2933
+ };
2934
+ }
2935
+ function sortArtefacts(artefacts) {
2936
+ return artefacts.slice().sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
2937
+ }
2938
+ var FileAutomationArtefactStore = class {
2939
+ constructor(filePath = defaultAutomationArtefactsFilePath()) {
2940
+ this.filePath = filePath;
2941
+ }
2942
+ async readArtefact(id) {
2943
+ return (await this.readArtefacts({ limit: 0 })).find((artefact) => artefact.id === id);
2944
+ }
2945
+ async readArtefacts(options = {}) {
2946
+ try {
2947
+ const filtered = sortArtefacts(normaliseFile(parseJsonString(await readFile(this.filePath, "utf8"))).artefacts).filter((artefact) => {
2948
+ if (options.kind && artefact.kind !== options.kind) return false;
2949
+ if (options.meetingId && artefact.meetingId !== options.meetingId) return false;
2950
+ if (options.status && artefact.status !== options.status) return false;
2951
+ return true;
2952
+ });
2953
+ return (options.limit && options.limit > 0 ? filtered.slice(0, options.limit) : filtered).map((artefact) => cloneArtefact(artefact));
2954
+ } catch {
2955
+ return [];
2956
+ }
2957
+ }
2958
+ async writeArtefacts(artefacts) {
2959
+ const payload = {
2960
+ artefacts: sortArtefacts(artefacts).slice(0, MAX_AUTOMATION_ARTEFACTS),
2961
+ version: AUTOMATION_ARTEFACTS_VERSION
2962
+ };
2963
+ await mkdir(dirname(this.filePath), { recursive: true });
2964
+ await writeFile(this.filePath, `${JSON.stringify(payload, null, 2)}\n`, {
2965
+ encoding: "utf8",
2966
+ mode: 384
2967
+ });
2968
+ }
2969
+ };
2970
+ function defaultAutomationArtefactsFilePath() {
2971
+ return defaultGranolaToolkitPersistenceLayout().automationArtefactsFile;
2972
+ }
2973
+ function createDefaultAutomationArtefactStore(filePath) {
2974
+ return new FileAutomationArtefactStore(filePath);
2975
+ }
2976
+ //#endregion
2630
2977
  //#region src/client/auth.ts
2631
2978
  const execFileAsync$1 = promisify(execFile);
2632
2979
  const DEFAULT_CLIENT_ID = "client_GranolaMac";
@@ -3238,7 +3585,11 @@ function createDefaultAutomationAgentRunner(config, options = {}) {
3238
3585
  //#region src/automation-actions.ts
3239
3586
  function cloneAction$1(action) {
3240
3587
  switch (action.kind) {
3241
- case "agent": return { ...action };
3588
+ case "agent": return {
3589
+ ...action,
3590
+ fallbackHarnessIds: action.fallbackHarnessIds ? [...action.fallbackHarnessIds] : void 0,
3591
+ pipeline: action.pipeline ? { ...action.pipeline } : void 0
3592
+ };
3242
3593
  case "ask-user": return { ...action };
3243
3594
  case "command": return {
3244
3595
  ...action,
@@ -3258,19 +3609,22 @@ function buildAutomationActionRunId(match, actionId) {
3258
3609
  function enabledAutomationActions(rule) {
3259
3610
  return (rule.actions ?? []).filter((action) => action.enabled !== false).map((action) => cloneAction$1(action));
3260
3611
  }
3261
- function baseRun(match, rule, action, startedAt) {
3612
+ function baseRun(match, rule, action, startedAt, options = {}) {
3262
3613
  return {
3263
3614
  actionId: action.id,
3264
3615
  actionKind: action.kind,
3265
3616
  actionName: automationActionName(action),
3617
+ artefactIds: void 0,
3266
3618
  eventId: match.eventId,
3267
3619
  eventKind: match.eventKind,
3268
3620
  folders: match.folders.map((folder) => ({ ...folder })),
3269
- id: buildAutomationActionRunId(match, action.id),
3621
+ id: options.runId ?? buildAutomationActionRunId(match, action.id),
3622
+ matchId: match.id,
3270
3623
  matchedAt: match.matchedAt,
3271
3624
  meetingId: match.meetingId,
3272
3625
  ruleId: rule.id,
3273
3626
  ruleName: rule.name,
3627
+ rerunOfId: options.rerunOfId,
3274
3628
  startedAt,
3275
3629
  status: "completed",
3276
3630
  tags: [...match.tags],
@@ -3302,16 +3656,20 @@ function skippedRun(run, finishedAt, reason) {
3302
3656
  status: "skipped"
3303
3657
  };
3304
3658
  }
3305
- async function executeAutomationAction(match, rule, action, handlers) {
3306
- const run = baseRun(match, rule, action, handlers.nowIso());
3659
+ async function executeAutomationAction(match, rule, action, handlers, options = {}) {
3660
+ const run = baseRun(match, rule, action, handlers.nowIso(), options);
3307
3661
  switch (action.kind) {
3308
3662
  case "agent": try {
3309
- const result = await handlers.runAgent(match, rule, action);
3663
+ const result = await handlers.runAgent(match, rule, action, run);
3310
3664
  return completedRun(run, handlers.nowIso(), {
3665
+ artefactIds: result.artefactIds ? [...result.artefactIds] : void 0,
3311
3666
  meta: {
3667
+ attempts: result.attempts,
3668
+ artefactIds: result.artefactIds,
3312
3669
  command: result.command,
3313
3670
  dryRun: result.dryRun,
3314
3671
  model: result.model,
3672
+ pipelineKind: result.pipelineKind,
3315
3673
  provider: result.provider,
3316
3674
  systemPrompt: result.systemPrompt
3317
3675
  },
@@ -3414,6 +3772,7 @@ function createDefaultAutomationMatchStore(filePath) {
3414
3772
  function cloneRun(run) {
3415
3773
  return {
3416
3774
  ...run,
3775
+ artefactIds: run.artefactIds ? [...run.artefactIds] : void 0,
3417
3776
  folders: run.folders.map((folder) => ({ ...folder })),
3418
3777
  meta: run.meta ? structuredClone(run.meta) : void 0,
3419
3778
  tags: [...run.tags]
@@ -3477,7 +3836,11 @@ function cloneRule(rule) {
3477
3836
  }
3478
3837
  function cloneAction(action) {
3479
3838
  switch (action.kind) {
3480
- case "agent": return { ...action };
3839
+ case "agent": return {
3840
+ ...action,
3841
+ fallbackHarnessIds: action.fallbackHarnessIds ? [...action.fallbackHarnessIds] : void 0,
3842
+ pipeline: action.pipeline ? { ...action.pipeline } : void 0
3843
+ };
3481
3844
  case "ask-user": return { ...action };
3482
3845
  case "command": return {
3483
3846
  ...action,
@@ -3501,6 +3864,11 @@ function stringRecord(value) {
3501
3864
  if (entries.length === 0) return;
3502
3865
  return Object.fromEntries(entries.map(([key, item]) => [key.trim(), item.trim()]));
3503
3866
  }
3867
+ function parsePipeline(value) {
3868
+ const record = asRecord(value);
3869
+ const kind = record ? stringValue(record.kind).trim() : typeof value === "string" ? value.trim() : "";
3870
+ return kind === "enrichment" || kind === "notes" ? { kind } : void 0;
3871
+ }
3504
3872
  function parseAction(value, index) {
3505
3873
  if (!value || typeof value !== "object" || Array.isArray(value)) return;
3506
3874
  const record = value;
@@ -3514,17 +3882,22 @@ function parseAction(value, index) {
3514
3882
  const provider = record.provider === "codex" || record.provider === "openai" || record.provider === "openrouter" ? record.provider : void 0;
3515
3883
  const prompt = typeof record.prompt === "string" && record.prompt.trim() ? record.prompt.trim() : void 0;
3516
3884
  const promptFile = typeof record.promptFile === "string" && record.promptFile.trim() ? record.promptFile.trim() : void 0;
3885
+ const harnessId = typeof record.harnessId === "string" && record.harnessId.trim() ? record.harnessId.trim() : void 0;
3886
+ const fallbackHarnessIds = stringArray(record.fallbackHarnessIds);
3517
3887
  const systemPrompt = typeof record.systemPrompt === "string" && record.systemPrompt.trim() ? record.systemPrompt.trim() : void 0;
3518
3888
  const systemPromptFile = typeof record.systemPromptFile === "string" && record.systemPromptFile.trim() ? record.systemPromptFile.trim() : void 0;
3519
- if (!prompt && !promptFile) return;
3889
+ if (!prompt && !promptFile && !harnessId) return;
3520
3890
  return {
3521
3891
  cwd: typeof record.cwd === "string" && record.cwd.trim() ? record.cwd.trim() : void 0,
3522
3892
  dryRun: typeof record.dryRun === "boolean" ? record.dryRun : void 0,
3523
3893
  enabled,
3894
+ fallbackHarnessIds,
3895
+ harnessId,
3524
3896
  id,
3525
3897
  kind,
3526
3898
  model: typeof record.model === "string" && record.model.trim() ? record.model.trim() : void 0,
3527
3899
  name,
3900
+ pipeline: parsePipeline(record.pipeline),
3528
3901
  prompt,
3529
3902
  promptFile,
3530
3903
  provider,
@@ -3912,17 +4285,39 @@ function parseLastViewedPanel(value) {
3912
4285
  updatedAt: stringValue(panel.updated_at)
3913
4286
  };
3914
4287
  }
4288
+ function parseCalendarEvent(value) {
4289
+ const record = asRecord(value);
4290
+ if (!record) return;
4291
+ const id = stringValue(record.id);
4292
+ const recurringEventId = stringValue(record.recurring_event_id) || stringValue(record.recurringEventId);
4293
+ const calendarId = stringValue(record.calendar_id) || stringValue(record.calendarId);
4294
+ const url = stringValue(record.url);
4295
+ const htmlLink = stringValue(record.html_link) || stringValue(record.htmlLink);
4296
+ const startTime = stringValue(record.start_time) || stringValue(record.startTime);
4297
+ const endTime = stringValue(record.end_time) || stringValue(record.endTime);
4298
+ if (!id && !recurringEventId && !calendarId && !url && !htmlLink && !startTime && !endTime) return;
4299
+ return {
4300
+ calendarId: calendarId || void 0,
4301
+ endTime: endTime || void 0,
4302
+ htmlLink: htmlLink || void 0,
4303
+ id: id || void 0,
4304
+ recurringEventId: recurringEventId || void 0,
4305
+ startTime: startTime || void 0,
4306
+ url: url || void 0
4307
+ };
4308
+ }
3915
4309
  function parseDocument(value) {
3916
4310
  const record = asRecord(value);
3917
4311
  if (!record) throw new Error("document payload is not an object");
3918
4312
  return {
4313
+ calendarEvent: parseCalendarEvent(record.google_calendar_event),
3919
4314
  content: stringValue(record.content),
3920
4315
  createdAt: stringValue(record.created_at),
3921
4316
  id: stringValue(record.id),
3922
4317
  lastViewedPanel: parseLastViewedPanel(record.last_viewed_panel),
3923
4318
  notes: parseProseMirrorDoc(record.notes),
3924
4319
  notesPlain: stringValue(record.notes_plain),
3925
- tags: stringArray$1(record.tags),
4320
+ tags: stringArray$2(record.tags),
3926
4321
  title: stringValue(record.title),
3927
4322
  updatedAt: stringValue(record.updated_at)
3928
4323
  };
@@ -3973,6 +4368,7 @@ function parsePublicNote(value) {
3973
4368
  const summaryMarkdown = stringValue(record.summary_markdown);
3974
4369
  const summaryText = stringValue(record.summary_text);
3975
4370
  return {
4371
+ calendarEvent: parseCalendarEvent(record.google_calendar_event),
3976
4372
  content: summaryMarkdown || summaryText,
3977
4373
  createdAt: stringValue(record.created_at),
3978
4374
  folderMemberships: Array.isArray(record.folder_membership) ? record.folder_membership.map(parseFolderMembership).filter((membership) => Boolean(membership)) : [],
@@ -4848,6 +5244,122 @@ function meetingIdsFromSearchResults(results) {
4848
5244
  return results.map((result) => result.id);
4849
5245
  }
4850
5246
  //#endregion
5247
+ //#region src/processing.ts
5248
+ function firstParagraph(markdown) {
5249
+ const paragraph = markdown.split(/\n\s*\n/).map((block) => block.replace(/^#+\s+/gm, "").trim()).find((block) => block.length > 0);
5250
+ return paragraph ? paragraph.slice(0, 280) : void 0;
5251
+ }
5252
+ function markdownSections(markdown) {
5253
+ const lines = markdown.split("\n");
5254
+ const sections = [];
5255
+ let currentTitle = "Overview";
5256
+ let currentBody = [];
5257
+ const pushCurrent = () => {
5258
+ const body = currentBody.join("\n").trim();
5259
+ if (!body) return;
5260
+ sections.push({
5261
+ body,
5262
+ title: currentTitle
5263
+ });
5264
+ };
5265
+ for (const line of lines) {
5266
+ const headingMatch = /^(#{1,6})\s+(.+)$/.exec(line.trim());
5267
+ if (headingMatch) {
5268
+ pushCurrent();
5269
+ currentTitle = headingMatch[2]?.trim() || "Section";
5270
+ currentBody = [];
5271
+ continue;
5272
+ }
5273
+ currentBody.push(line);
5274
+ }
5275
+ pushCurrent();
5276
+ return sections;
5277
+ }
5278
+ function normaliseStrings(value) {
5279
+ return stringArray$2(value).map((item) => item.trim()).filter(Boolean);
5280
+ }
5281
+ function normaliseActionItems(value) {
5282
+ if (!Array.isArray(value)) return [];
5283
+ return value.map((item) => {
5284
+ const record = asRecord(item);
5285
+ if (!record) return;
5286
+ const title = stringValue(record.title).trim();
5287
+ if (!title) return;
5288
+ return {
5289
+ dueDate: stringValue(record.dueDate).trim() || void 0,
5290
+ owner: stringValue(record.owner).trim() || void 0,
5291
+ title
5292
+ };
5293
+ }).filter((item) => Boolean(item));
5294
+ }
5295
+ function normaliseSections(value) {
5296
+ if (!Array.isArray(value)) return [];
5297
+ return value.map((item) => {
5298
+ const record = asRecord(item);
5299
+ if (!record) return;
5300
+ const title = stringValue(record.title).trim();
5301
+ const body = stringValue(record.body).trim();
5302
+ if (!title || !body) return;
5303
+ return {
5304
+ body,
5305
+ title
5306
+ };
5307
+ }).filter((item) => Boolean(item));
5308
+ }
5309
+ function extractJsonPayload(rawOutput) {
5310
+ const direct = parseJsonString(rawOutput);
5311
+ if (direct) return direct;
5312
+ const fencedMatch = rawOutput.match(/```(?:json)?\s*([\s\S]+?)```/i);
5313
+ if (!fencedMatch?.[1]) return;
5314
+ return parseJsonString(fencedMatch[1].trim());
5315
+ }
5316
+ function buildPipelineInstructions(kind, instructions) {
5317
+ const task = kind === "notes" ? "Turn the transcript and meeting context into improved meeting notes." : "Turn the transcript and meeting context into a structured enrichment for downstream workflows.";
5318
+ return [
5319
+ instructions.trim(),
5320
+ task,
5321
+ "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\":{}}",
5322
+ "Keep arrays empty instead of omitting them. markdown must contain the full Markdown result."
5323
+ ].filter(Boolean).join("\n\n");
5324
+ }
5325
+ function parsePipelineOutput(options) {
5326
+ const rawOutput = options.rawOutput.trim();
5327
+ const payload = extractJsonPayload(rawOutput);
5328
+ if (payload) {
5329
+ const title = stringValue(payload.title).trim() || options.meetingTitle;
5330
+ const markdown = stringValue(payload.markdown).trim();
5331
+ if (markdown) return {
5332
+ parseMode: "json",
5333
+ structured: {
5334
+ actionItems: normaliseActionItems(payload.actionItems),
5335
+ decisions: normaliseStrings(payload.decisions),
5336
+ followUps: normaliseStrings(payload.followUps),
5337
+ highlights: normaliseStrings(payload.highlights),
5338
+ markdown,
5339
+ metadata: asRecord(payload.metadata),
5340
+ sections: normaliseSections(payload.sections),
5341
+ summary: stringValue(payload.summary).trim() || firstParagraph(markdown),
5342
+ title
5343
+ }
5344
+ };
5345
+ }
5346
+ const markdown = rawOutput || `# ${options.meetingTitle}\n`;
5347
+ return {
5348
+ parseMode: "markdown-fallback",
5349
+ structured: {
5350
+ actionItems: [],
5351
+ decisions: [],
5352
+ followUps: [],
5353
+ highlights: [],
5354
+ markdown,
5355
+ metadata: { fallbackKind: options.kind },
5356
+ sections: markdownSections(markdown),
5357
+ summary: firstParagraph(markdown),
5358
+ title: options.kind === "notes" ? `${options.meetingTitle} Generated Notes` : `${options.meetingTitle} Enrichment`
5359
+ }
5360
+ };
5361
+ }
5362
+ //#endregion
4851
5363
  //#region src/app/core.ts
4852
5364
  function transcriptCount(cacheData) {
4853
5365
  return Object.values(cacheData.transcripts).filter((segments) => segments.length > 0).length;
@@ -4890,6 +5402,10 @@ function cloneMeetingSummary(meeting) {
4890
5402
  function resolveActionFilePath(filePath, cwd) {
4891
5403
  return cwd ? resolve(cwd, filePath) : resolve(filePath);
4892
5404
  }
5405
+ async function readOptionalActionFile(filePath, cwd) {
5406
+ if (!filePath) return;
5407
+ return await readFile(resolveActionFilePath(filePath, cwd), "utf8");
5408
+ }
4893
5409
  function combinePromptSections(...values) {
4894
5410
  const sections = values.map((value) => value?.trim()).filter((value) => Boolean(value));
4895
5411
  return sections.length > 0 ? sections.join("\n\n") : void 0;
@@ -4899,6 +5415,9 @@ function meetingTranscriptText(bundle) {
4899
5415
  if (!segments?.length) return;
4900
5416
  return segments.slice().sort((left, right) => left.startTimestamp.localeCompare(right.startTimestamp)).map((segment) => segment.text.trim()).filter(Boolean).join("\n");
4901
5417
  }
5418
+ function buildAutomationArtefactId(runId, kind) {
5419
+ return `${kind}:${runId}`;
5420
+ }
4902
5421
  function buildAutomationAgentPrompt(match, rule, instructions, bundle) {
4903
5422
  const transcriptText = bundle ? meetingTranscriptText(bundle)?.trim() : void 0;
4904
5423
  const context = {
@@ -4967,6 +5486,8 @@ function defaultState(config, auth, surface) {
4967
5486
  return {
4968
5487
  auth: { ...auth },
4969
5488
  automation: {
5489
+ artefactCount: 0,
5490
+ artefactsFile: config.automation?.artefactsFile ?? defaultAutomationArtefactsFilePath(),
4970
5491
  loaded: false,
4971
5492
  matchCount: 0,
4972
5493
  matchesFile: defaultAutomationMatchesFilePath(),
@@ -5019,6 +5540,7 @@ function defaultState(config, auth, surface) {
5019
5540
  }
5020
5541
  var GranolaApp = class {
5021
5542
  #automationActionRuns;
5543
+ #automationArtefacts;
5022
5544
  #automationMatches;
5023
5545
  #automationRules;
5024
5546
  #cacheData;
@@ -5035,6 +5557,7 @@ var GranolaApp = class {
5035
5557
  this.config = config;
5036
5558
  this.deps = deps;
5037
5559
  this.#state = defaultState(config, deps.auth, options.surface ?? "cli");
5560
+ this.#automationArtefacts = (deps.automationArtefacts ?? []).map((artefact) => this.cloneAutomationArtefact(artefact));
5038
5561
  this.#automationMatches = (deps.automationMatches ?? []).map((match) => ({
5039
5562
  ...match,
5040
5563
  folders: match.folders.map((folder) => ({ ...folder })),
@@ -5044,6 +5567,8 @@ var GranolaApp = class {
5044
5567
  this.#automationRules = (deps.automationRules ?? []).map((rule) => this.cloneAutomationRule(rule));
5045
5568
  this.#state.exports.jobs = (deps.exportJobs ?? []).map((job) => cloneExportJob(job));
5046
5569
  this.#state.automation = {
5570
+ artefactCount: this.#automationArtefacts.length,
5571
+ artefactsFile: config.automation?.artefactsFile ?? defaultAutomationArtefactsFilePath(),
5047
5572
  lastRunAt: this.#automationActionRuns[0]?.finishedAt ?? this.#automationActionRuns[0]?.startedAt,
5048
5573
  lastMatchedAt: this.#automationMatches[0]?.matchedAt,
5049
5574
  loaded: true,
@@ -5170,11 +5695,27 @@ var GranolaApp = class {
5170
5695
  cloneAutomationRun(run) {
5171
5696
  return {
5172
5697
  ...run,
5698
+ artefactIds: run.artefactIds ? [...run.artefactIds] : void 0,
5173
5699
  folders: run.folders.map((folder) => ({ ...folder })),
5174
5700
  meta: run.meta ? structuredClone(run.meta) : void 0,
5175
5701
  tags: [...run.tags]
5176
5702
  };
5177
5703
  }
5704
+ cloneAutomationArtefact(artefact) {
5705
+ return {
5706
+ ...artefact,
5707
+ attempts: artefact.attempts.map((attempt) => ({ ...attempt })),
5708
+ structured: {
5709
+ ...artefact.structured,
5710
+ actionItems: artefact.structured.actionItems.map((item) => ({ ...item })),
5711
+ decisions: [...artefact.structured.decisions],
5712
+ followUps: [...artefact.structured.followUps],
5713
+ highlights: [...artefact.structured.highlights],
5714
+ metadata: artefact.structured.metadata ? structuredClone(artefact.structured.metadata) : void 0,
5715
+ sections: artefact.structured.sections.map((section) => ({ ...section }))
5716
+ }
5717
+ };
5718
+ }
5178
5719
  refreshAutomationState() {
5179
5720
  const latestMatch = this.#automationMatches.reduce((current, candidate) => !current || candidate.matchedAt.localeCompare(current.matchedAt) > 0 ? candidate : current, void 0);
5180
5721
  const latestRun = this.#automationActionRuns.reduce((current, candidate) => {
@@ -5184,6 +5725,8 @@ var GranolaApp = class {
5184
5725
  }, void 0);
5185
5726
  this.#state.automation = {
5186
5727
  ...this.#state.automation,
5728
+ artefactCount: this.#automationArtefacts.length,
5729
+ artefactsFile: this.config.automation?.artefactsFile ?? defaultAutomationArtefactsFilePath(),
5187
5730
  lastMatchedAt: latestMatch?.matchedAt ?? this.#state.automation.lastMatchedAt,
5188
5731
  lastRunAt: latestRun?.finishedAt ?? latestRun?.startedAt ?? this.#state.automation.lastRunAt,
5189
5732
  loaded: true,
@@ -5221,6 +5764,11 @@ var GranolaApp = class {
5221
5764
  this.#automationActionRuns.sort((left, right) => (right.finishedAt ?? right.startedAt).localeCompare(left.finishedAt ?? left.startedAt));
5222
5765
  this.refreshAutomationState();
5223
5766
  }
5767
+ async writeAutomationArtefacts(artefacts) {
5768
+ this.#automationArtefacts = artefacts.map((artefact) => this.cloneAutomationArtefact(artefact)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
5769
+ if (this.deps.automationArtefactStore) await this.deps.automationArtefactStore.writeArtefacts(this.#automationArtefacts);
5770
+ this.refreshAutomationState();
5771
+ }
5224
5772
  createSyncRunId() {
5225
5773
  return `sync-${this.nowIso().replaceAll(/[-:.]/g, "").replace("T", "").replace("Z", "")}`;
5226
5774
  }
@@ -5461,6 +6009,22 @@ var GranolaApp = class {
5461
6009
  if (!this.deps.syncEventStore) return { events: [] };
5462
6010
  return { events: (await this.deps.syncEventStore.readEvents(options.limit)).map(cloneSyncEvent) };
5463
6011
  }
6012
+ async listAutomationArtefacts(options = {}) {
6013
+ const limit = options.limit ?? 20;
6014
+ const artefacts = this.deps.automationArtefactStore ? await this.deps.automationArtefactStore.readArtefacts({
6015
+ kind: options.kind,
6016
+ limit,
6017
+ meetingId: options.meetingId,
6018
+ status: options.status
6019
+ }) : this.#automationArtefacts.filter((artefact) => {
6020
+ if (options.kind && artefact.kind !== options.kind) return false;
6021
+ if (options.meetingId && artefact.meetingId !== options.meetingId) return false;
6022
+ if (options.status && artefact.status !== options.status) return false;
6023
+ return true;
6024
+ }).slice(0, limit);
6025
+ this.setUiState({ view: "idle" });
6026
+ return { artefacts: artefacts.map((artefact) => this.cloneAutomationArtefact(artefact)) };
6027
+ }
5464
6028
  async listAutomationRules() {
5465
6029
  const rules = await this.loadAutomationRules({ forceRefresh: true });
5466
6030
  this.setUiState({ view: "idle" });
@@ -5501,6 +6065,39 @@ var GranolaApp = class {
5501
6065
  this.emitStateUpdate();
5502
6066
  return this.cloneAutomationRun(resolved);
5503
6067
  }
6068
+ async rerunAutomationArtefact(id) {
6069
+ const current = (this.deps.automationArtefactStore ? await this.deps.automationArtefactStore.readArtefact(id) : void 0) ?? this.#automationArtefacts.find((artefact) => artefact.id === id);
6070
+ if (!current) throw new Error(`automation artefact not found: ${id}`);
6071
+ const rule = (await this.loadAutomationRules({ forceRefresh: true })).find((candidate) => candidate.id === current.ruleId);
6072
+ if (!rule) throw new Error(`automation rule not found: ${current.ruleId}`);
6073
+ const action = enabledAutomationActions(rule).find((candidate) => candidate.id === current.actionId);
6074
+ if (!action || action.kind !== "agent" || !action.pipeline) throw new Error(`automation artefact is not rerunnable: ${id}`);
6075
+ const match = (this.deps.automationMatchStore ? (await this.deps.automationMatchStore.readMatches(0)).find((candidate) => candidate.id === current.matchId) : void 0) ?? this.#automationMatches.find((candidate) => candidate.id === current.matchId);
6076
+ if (!match) throw new Error(`automation match not found: ${current.matchId}`);
6077
+ const nextRun = await executeAutomationAction(this.cloneAutomationMatch(match), rule, action, {
6078
+ exportNotes: async (nextMatch, nextAction) => await this.runAutomationNotesAction(nextMatch, nextAction),
6079
+ exportTranscripts: async (nextMatch, nextAction) => await this.runAutomationTranscriptAction(nextMatch, nextAction),
6080
+ nowIso: () => this.nowIso(),
6081
+ runAgent: async (nextMatch, nextRule, nextAction, run) => await this.runAutomationAgent(nextMatch, nextRule, nextAction, run),
6082
+ runCommand: async (nextMatch, nextRule, nextAction) => await this.runAutomationCommand(nextMatch, nextRule, nextAction)
6083
+ }, {
6084
+ rerunOfId: current.runId,
6085
+ runId: `${current.runId}:rerun:${this.nowIso().replaceAll(/[-:.]/g, "").replace("T", "").replace("Z", "")}`
6086
+ });
6087
+ await this.appendAutomationRuns([nextRun]);
6088
+ const nextArtefactId = nextRun.artefactIds?.[0];
6089
+ const nextArtefact = nextArtefactId ? this.#automationArtefacts.find((artefact) => artefact.id === nextArtefactId) : void 0;
6090
+ if (!nextArtefact) throw new Error(`rerun did not produce an automation artefact: ${id}`);
6091
+ const updatedArtefacts = this.#automationArtefacts.map((artefact) => artefact.id === current.id ? {
6092
+ ...artefact,
6093
+ status: "superseded",
6094
+ supersededById: nextArtefact.id,
6095
+ updatedAt: nextArtefact.createdAt
6096
+ } : artefact);
6097
+ await this.writeAutomationArtefacts(updatedArtefacts);
6098
+ this.emitStateUpdate();
6099
+ return this.cloneAutomationArtefact(this.#automationArtefacts.find((artefact) => artefact.id === nextArtefact.id) ?? nextArtefact);
6100
+ }
5504
6101
  async loginAuth(options = {}) {
5505
6102
  const controller = this.requireAuthController();
5506
6103
  try {
@@ -5687,23 +6284,119 @@ var GranolaApp = class {
5687
6284
  child.stdin.end();
5688
6285
  });
5689
6286
  }
5690
- async runAutomationAgent(match, rule, action) {
5691
- const bundle = match.eventKind === "meeting.removed" ? void 0 : await this.maybeReadMeetingBundleById(match.meetingId, { requireCache: false });
5692
- const promptFile = action.promptFile ? await readFile(resolveActionFilePath(action.promptFile, action.cwd), "utf8") : void 0;
5693
- const systemPromptFile = action.systemPromptFile ? await readFile(resolveActionFilePath(action.systemPromptFile, action.cwd), "utf8") : void 0;
5694
- const instructions = combinePromptSections(promptFile, action.prompt);
6287
+ async buildAutomationAgentAttempt(match, rule, action, bundle, harness) {
6288
+ const harnessCwd = harness?.cwd;
6289
+ const promptFile = await readOptionalActionFile(action.promptFile, action.cwd ?? harnessCwd);
6290
+ const harnessPromptFile = await readOptionalActionFile(harness?.promptFile, harnessCwd);
6291
+ const systemPromptFile = await readOptionalActionFile(action.systemPromptFile, action.cwd ?? harnessCwd);
6292
+ const harnessSystemPromptFile = await readOptionalActionFile(harness?.systemPromptFile, harnessCwd);
6293
+ let instructions = combinePromptSections(harnessPromptFile, harness?.prompt, promptFile, action.prompt);
5695
6294
  if (!instructions) throw new Error(`automation agent action ${action.id} is missing prompt instructions`);
5696
- const request = {
5697
- cwd: action.cwd,
5698
- dryRun: action.dryRun,
5699
- model: action.model,
6295
+ if (action.pipeline) instructions = buildPipelineInstructions(action.pipeline.kind, instructions);
6296
+ return {
6297
+ harness,
5700
6298
  prompt: buildAutomationAgentPrompt(match, rule, instructions, bundle),
5701
- provider: action.provider,
5702
- retries: action.retries,
5703
- systemPrompt: combinePromptSections(systemPromptFile, action.systemPrompt),
5704
- timeoutMs: action.timeoutMs
6299
+ request: {
6300
+ cwd: action.cwd ?? harnessCwd,
6301
+ dryRun: action.dryRun,
6302
+ model: action.model ?? harness?.model,
6303
+ prompt: buildAutomationAgentPrompt(match, rule, instructions, bundle),
6304
+ provider: action.provider ?? harness?.provider,
6305
+ retries: action.retries,
6306
+ systemPrompt: combinePromptSections(harnessSystemPromptFile, harness?.systemPrompt, systemPromptFile, action.systemPrompt),
6307
+ timeoutMs: action.timeoutMs
6308
+ },
6309
+ systemPrompt: combinePromptSections(harnessSystemPromptFile, harness?.systemPrompt, systemPromptFile, action.systemPrompt)
5705
6310
  };
5706
- return await (this.deps.agentRunner ?? createDefaultAutomationAgentRunner(this.config)).run(request);
6311
+ }
6312
+ async runAutomationAgent(match, rule, action, run) {
6313
+ const bundle = match.eventKind === "meeting.removed" ? void 0 : await this.maybeReadMeetingBundleById(match.meetingId, { requireCache: false });
6314
+ const harnesses = this.deps.agentHarnessStore ? await this.deps.agentHarnessStore.readHarnesses() : [];
6315
+ const primaryHarness = resolveAgentHarness(harnesses, {
6316
+ bundle,
6317
+ match
6318
+ }, action.harnessId);
6319
+ const fallbackHarnessIds = [...action.fallbackHarnessIds ?? [], ...primaryHarness?.fallbackHarnessIds ?? []].filter((value, index, values) => Boolean(value) && values.indexOf(value) === index);
6320
+ const attempts = [await this.buildAutomationAgentAttempt(match, rule, action, bundle, primaryHarness), ...await Promise.all(fallbackHarnessIds.filter((harnessId) => harnessId !== primaryHarness?.id).map(async (harnessId) => {
6321
+ const fallbackHarness = resolveAgentHarness(harnesses, {
6322
+ bundle,
6323
+ match
6324
+ }, harnessId);
6325
+ return await this.buildAutomationAgentAttempt(match, rule, action, bundle, fallbackHarness);
6326
+ }))];
6327
+ const runner = this.deps.agentRunner ?? createDefaultAutomationAgentRunner(this.config);
6328
+ const attemptMeta = [];
6329
+ let lastError;
6330
+ for (const attempt of attempts) try {
6331
+ const result = await runner.run(attempt.request);
6332
+ attemptMeta.push({
6333
+ harnessId: attempt.harness?.id,
6334
+ model: result.model,
6335
+ provider: result.provider === "codex" || result.provider === "openai" || result.provider === "openrouter" ? result.provider : void 0
6336
+ });
6337
+ if (action.pipeline) {
6338
+ const parsed = parsePipelineOutput({
6339
+ kind: action.pipeline.kind,
6340
+ meetingTitle: match.title,
6341
+ rawOutput: result.output ?? ""
6342
+ });
6343
+ const artefact = {
6344
+ actionId: action.id,
6345
+ actionName: automationActionName(action),
6346
+ attempts: attemptMeta.map((item) => ({ ...item })),
6347
+ createdAt: this.nowIso(),
6348
+ eventId: match.eventId,
6349
+ id: buildAutomationArtefactId(run.id, action.pipeline.kind),
6350
+ kind: action.pipeline.kind,
6351
+ matchId: match.id,
6352
+ meetingId: match.meetingId,
6353
+ model: result.model,
6354
+ parseMode: parsed.parseMode,
6355
+ prompt: result.prompt,
6356
+ provider: result.provider === "codex" || result.provider === "openai" || result.provider === "openrouter" ? result.provider : "codex",
6357
+ rawOutput: result.output ?? "",
6358
+ rerunOfId: run.rerunOfId ? buildAutomationArtefactId(run.rerunOfId, action.pipeline.kind) : void 0,
6359
+ ruleId: rule.id,
6360
+ ruleName: rule.name,
6361
+ runId: run.id,
6362
+ status: "generated",
6363
+ structured: parsed.structured,
6364
+ updatedAt: this.nowIso()
6365
+ };
6366
+ await this.writeAutomationArtefacts([artefact, ...this.#automationArtefacts]);
6367
+ return {
6368
+ artefactIds: [artefact.id],
6369
+ attempts: attemptMeta,
6370
+ command: result.command,
6371
+ dryRun: result.dryRun,
6372
+ model: result.model,
6373
+ output: parsed.structured.summary ?? parsed.structured.markdown,
6374
+ pipelineKind: action.pipeline.kind,
6375
+ prompt: result.prompt,
6376
+ provider: result.provider,
6377
+ systemPrompt: result.systemPrompt
6378
+ };
6379
+ }
6380
+ return {
6381
+ attempts: attemptMeta,
6382
+ command: result.command,
6383
+ dryRun: result.dryRun,
6384
+ model: result.model,
6385
+ output: result.output,
6386
+ prompt: result.prompt,
6387
+ provider: result.provider,
6388
+ systemPrompt: result.systemPrompt
6389
+ };
6390
+ } catch (error) {
6391
+ lastError = error;
6392
+ attemptMeta.push({
6393
+ error: error instanceof Error ? error.message : String(error),
6394
+ harnessId: attempt.harness?.id,
6395
+ model: attempt.request.model,
6396
+ provider: attempt.request.provider
6397
+ });
6398
+ }
6399
+ throw lastError instanceof Error ? lastError : new Error(typeof lastError === "string" ? lastError : lastError ? JSON.stringify(lastError) : "automation agent failed");
5707
6400
  }
5708
6401
  async runAutomationActions(rules, matches) {
5709
6402
  const rulesById = new Map(rules.map((rule) => [rule.id, rule]));
@@ -5720,7 +6413,7 @@ var GranolaApp = class {
5720
6413
  exportNotes: async (nextMatch, nextAction) => await this.runAutomationNotesAction(nextMatch, nextAction),
5721
6414
  exportTranscripts: async (nextMatch, nextAction) => await this.runAutomationTranscriptAction(nextMatch, nextAction),
5722
6415
  nowIso: () => this.nowIso(),
5723
- runAgent: async (nextMatch, nextRule, nextAction) => await this.runAutomationAgent(nextMatch, nextRule, nextAction),
6416
+ runAgent: async (nextMatch, nextRule, nextAction, run) => await this.runAutomationAgent(nextMatch, nextRule, nextAction, run),
5724
6417
  runCommand: async (nextMatch, nextRule, nextAction) => await this.runAutomationCommand(nextMatch, nextRule, nextAction)
5725
6418
  }));
5726
6419
  }
@@ -6141,12 +6834,15 @@ var GranolaApp = class {
6141
6834
  };
6142
6835
  async function createGranolaApp(config, options = {}) {
6143
6836
  const auth = await inspectDefaultGranolaAuth(config);
6837
+ const automationArtefactStore = createDefaultAutomationArtefactStore(config.automation?.artefactsFile);
6838
+ const automationArtefacts = await automationArtefactStore.readArtefacts({ limit: 0 });
6144
6839
  const automationMatchStore = createDefaultAutomationMatchStore();
6145
6840
  const automationMatches = await automationMatchStore.readMatches(0);
6146
6841
  const automationRunStore = createDefaultAutomationRunStore();
6147
6842
  const automationRuns = await automationRunStore.readRuns({ limit: 0 });
6148
6843
  const automationRuleStore = createDefaultAutomationRuleStore(config.automation?.rulesFile ?? defaultAutomationRulesFilePath());
6149
6844
  const automationRules = await automationRuleStore.readRules();
6845
+ const agentHarnessStore = createDefaultAgentHarnessStore(config.agents?.harnessesFile);
6150
6846
  const authController = createDefaultGranolaAuthController(config);
6151
6847
  const exportJobStore = createDefaultExportJobStore();
6152
6848
  const exportJobs = await exportJobStore.readJobs();
@@ -6160,7 +6856,10 @@ async function createGranolaApp(config, options = {}) {
6160
6856
  return new GranolaApp(config, {
6161
6857
  auth,
6162
6858
  agentRunner: createDefaultAutomationAgentRunner(config),
6859
+ agentHarnessStore,
6163
6860
  authController,
6861
+ automationArtefactStore,
6862
+ automationArtefacts,
6164
6863
  automationMatches,
6165
6864
  automationMatchStore,
6166
6865
  automationRunStore,
@@ -6248,7 +6947,10 @@ async function loadConfig(options) {
6248
6947
  const agentTimeoutValue = pickString(env.GRANOLA_AGENT_TIMEOUT) ?? pickString(configValues["agent-timeout"]) ?? pickString(configValues.agentTimeout) ?? "5m";
6249
6948
  const timeoutValue = pickString(options.subcommandFlags.timeout) ?? pickString(env.TIMEOUT) ?? pickString(configValues.timeout) ?? "2m";
6250
6949
  return {
6251
- automation: { rulesFile: pickString(options.globalFlags.rules) ?? pickString(env.GRANOLA_AUTOMATION_RULES_FILE) ?? pickString(configValues["automation-rules-file"]) ?? pickString(configValues.automationRulesFile) ?? defaultGranolaToolkitPersistenceLayout().automationRulesFile },
6950
+ automation: {
6951
+ artefactsFile: pickString(env.GRANOLA_AUTOMATION_ARTEFACTS_FILE) ?? pickString(configValues["automation-artefacts-file"]) ?? pickString(configValues.automationArtefactsFile) ?? defaultGranolaToolkitPersistenceLayout().automationArtefactsFile,
6952
+ rulesFile: pickString(options.globalFlags.rules) ?? pickString(env.GRANOLA_AUTOMATION_RULES_FILE) ?? pickString(configValues["automation-rules-file"]) ?? pickString(configValues.automationRulesFile) ?? defaultGranolaToolkitPersistenceLayout().automationRulesFile
6953
+ },
6252
6954
  agents: {
6253
6955
  codexCommand: pickString(env.GRANOLA_CODEX_COMMAND) ?? pickString(configValues["codex-command"]) ?? pickString(configValues.codexCommand) ?? "codex",
6254
6956
  defaultModel: pickString(env.GRANOLA_AGENT_MODEL) ?? pickString(configValues["agent-model"]) ?? pickString(configValues.agentModel),
@@ -6257,6 +6959,7 @@ async function loadConfig(options) {
6257
6959
  return value === "codex" || value === "openai" || value === "openrouter" ? value : void 0;
6258
6960
  })(),
6259
6961
  dryRun: envFlag(env.GRANOLA_AGENT_DRY_RUN) ?? pickBoolean(configValues["agent-dry-run"]) ?? pickBoolean(configValues.agentDryRun) ?? false,
6962
+ harnessesFile: pickString(env.GRANOLA_AGENT_HARNESSES_FILE) ?? pickString(configValues["agent-harnesses-file"]) ?? pickString(configValues.agentHarnessesFile) ?? defaultGranolaToolkitPersistenceLayout().agentHarnessesFile,
6260
6963
  maxRetries: pickNumber(env.GRANOLA_AGENT_MAX_RETRIES) ?? pickNumber(configValues["agent-max-retries"]) ?? pickNumber(configValues.agentMaxRetries) ?? 2,
6261
6964
  openaiBaseUrl: pickString(env.GRANOLA_OPENAI_BASE_URL) ?? pickString(env.OPENAI_BASE_URL) ?? pickString(configValues["openai-base-url"]) ?? pickString(configValues.openaiBaseUrl) ?? "https://api.openai.com/v1",
6262
6965
  openrouterBaseUrl: pickString(env.GRANOLA_OPENROUTER_BASE_URL) ?? pickString(env.OPENROUTER_BASE_URL) ?? pickString(configValues["openrouter-base-url"]) ?? pickString(configValues.openrouterBaseUrl) ?? "https://openrouter.ai/api/v1",
@@ -6346,18 +7049,22 @@ function automationHelp() {
6346
7049
  return `Granola automation
6347
7050
 
6348
7051
  Usage:
6349
- granola automation <rules|matches|runs|approve|reject> [options]
7052
+ granola automation <rules|matches|runs|artefacts|approve|reject|rerun> [options]
6350
7053
 
6351
7054
  Subcommands:
6352
7055
  rules List configured automation rules
6353
7056
  matches Show recent rule matches from sync events
6354
7057
  runs Show recent automation action runs
7058
+ artefacts Show generated note and enrichment artefacts
6355
7059
  approve <id> Approve a pending ask-user action run
6356
7060
  reject <id> Reject a pending ask-user action run
7061
+ rerun <id> Re-run the pipeline that produced an artefact
6357
7062
 
6358
7063
  Options:
6359
7064
  --format <value> text, json, yaml (default: text)
6360
7065
  --limit <n> Number of matches to show (default: 20)
7066
+ --kind <value> notes or enrichment
7067
+ --meeting <id> Filter artefacts to one meeting id
6361
7068
  --status <value> completed, failed, pending, skipped
6362
7069
  --note <text> Note to store with approve/reject decisions
6363
7070
  --rules <path> Path to automation rules JSON
@@ -6415,6 +7122,32 @@ function parseRunStatus(value) {
6415
7122
  default: throw new Error("invalid automation status: expected completed, failed, pending, or skipped");
6416
7123
  }
6417
7124
  }
7125
+ function parseArtefactKind(value) {
7126
+ switch (value) {
7127
+ case void 0: return;
7128
+ case "enrichment":
7129
+ case "notes": return value;
7130
+ default: throw new Error("invalid automation artefact kind: expected notes or enrichment");
7131
+ }
7132
+ }
7133
+ function parseArtefactStatus(value) {
7134
+ switch (value) {
7135
+ case void 0: return;
7136
+ case "approved":
7137
+ case "generated":
7138
+ case "rejected":
7139
+ case "superseded": return value;
7140
+ default: throw new Error("invalid automation artefact status: expected approved, generated, rejected, or superseded");
7141
+ }
7142
+ }
7143
+ function renderArtefacts(artefacts, format) {
7144
+ if (format === "json") return toJson({ artefacts });
7145
+ if (format === "yaml") return toYaml({ artefacts });
7146
+ if (artefacts.length === 0) return "No automation artefacts yet\n";
7147
+ return `${["UPDATED AT STATUS KIND TITLE", ...artefacts.map((artefact) => {
7148
+ 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(" - ")}`;
7149
+ })].join("\n")}\n`;
7150
+ }
6418
7151
  function renderRuns(runs, format) {
6419
7152
  if (format === "json") return toJson({ runs });
6420
7153
  if (format === "yaml") return toYaml({ runs });
@@ -6428,7 +7161,9 @@ const automationCommand = {
6428
7161
  flags: {
6429
7162
  format: { type: "string" },
6430
7163
  help: { type: "boolean" },
7164
+ kind: { type: "string" },
6431
7165
  limit: { type: "string" },
7166
+ meeting: { type: "string" },
6432
7167
  note: { type: "string" },
6433
7168
  status: { type: "string" },
6434
7169
  timeout: { type: "string" }
@@ -6464,6 +7199,16 @@ const automationCommand = {
6464
7199
  console.log(renderRuns(result.runs, format).trimEnd());
6465
7200
  return 0;
6466
7201
  }
7202
+ case "artefacts": {
7203
+ const result = await app.listAutomationArtefacts({
7204
+ kind: parseArtefactKind(commandFlags.kind),
7205
+ limit: parseLimit$4(commandFlags.limit),
7206
+ meetingId: typeof commandFlags.meeting === "string" ? commandFlags.meeting.trim() : void 0,
7207
+ status: parseArtefactStatus(commandFlags.status)
7208
+ });
7209
+ console.log(renderArtefacts(result.artefacts, format).trimEnd());
7210
+ return 0;
7211
+ }
6467
7212
  case "approve":
6468
7213
  case "reject": {
6469
7214
  const id = commandArgs[1]?.trim();
@@ -6472,10 +7217,17 @@ const automationCommand = {
6472
7217
  console.log(`${action === "approve" ? "Approved" : "Rejected"} ${run.actionName} for ${run.title} (${run.id})`);
6473
7218
  return 0;
6474
7219
  }
7220
+ case "rerun": {
7221
+ const id = commandArgs[1]?.trim();
7222
+ if (!id) throw new Error("missing automation artefact id for rerun");
7223
+ const artefact = await app.rerunAutomationArtefact(id);
7224
+ console.log(`Re-ran ${artefact.kind} pipeline for ${artefact.structured.title} (${artefact.id})`);
7225
+ return 0;
7226
+ }
6475
7227
  case void 0:
6476
7228
  console.log(automationHelp());
6477
7229
  return 1;
6478
- default: throw new Error("invalid automation command: expected rules, matches, runs, approve, or reject");
7230
+ default: throw new Error("invalid automation command: expected rules, matches, runs, artefacts, approve, reject, or rerun");
6479
7231
  }
6480
7232
  }
6481
7233
  };
@@ -6826,7 +7578,7 @@ async function openExternalUrl(url, options = {}) {
6826
7578
  //#endregion
6827
7579
  //#region src/web/generated.ts
6828
7580
  const granolaWebClientCss = ":root {\n --bg: #f2ede2;\n --panel: rgba(255, 252, 247, 0.86);\n --panel-strong: #fffaf2;\n --line: rgba(36, 39, 44, 0.12);\n --ink: #1d242c;\n --muted: #5d6b77;\n --accent: #0d6a6d;\n --accent-soft: rgba(13, 106, 109, 0.12);\n --warm: #a34f2f;\n --ok: #246b4f;\n --error: #9d2c2c;\n --shadow: 0 24px 80px rgba(40, 32, 16, 0.12);\n --radius: 24px;\n --mono: \"SF Mono\", \"IBM Plex Mono\", \"Cascadia Code\", monospace;\n --serif: \"Iowan Old Style\", \"Palatino Linotype\", \"Book Antiqua\", Georgia, serif;\n --sans: \"Avenir Next\", \"Segoe UI\", sans-serif;\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n min-height: 100vh;\n font-family: var(--sans);\n color: var(--ink);\n background:\n radial-gradient(circle at top left, rgba(163, 79, 47, 0.18), transparent 32%),\n radial-gradient(circle at right 12%, rgba(13, 106, 109, 0.16), transparent 28%),\n linear-gradient(180deg, #f8f2e8 0%, var(--bg) 100%);\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\n#granola-web-root {\n min-height: 100vh;\n}\n\n.shell {\n display: grid;\n grid-template-columns: 320px minmax(0, 1fr);\n gap: 18px;\n min-height: 100vh;\n padding: 24px;\n}\n\n.pane {\n background: var(--panel);\n backdrop-filter: blur(18px);\n border: 1px solid var(--line);\n border-radius: var(--radius);\n box-shadow: var(--shadow);\n}\n\n.sidebar {\n display: grid;\n grid-template-rows: auto auto auto auto 1fr;\n overflow: hidden;\n}\n\n.hero,\n.toolbar,\n.detail-head,\n.folder-panel {\n padding: 22px 24px;\n border-bottom: 1px solid var(--line);\n}\n\n.hero h1 {\n margin: 0;\n font-family: var(--serif);\n font-size: clamp(2rem, 3vw, 2.8rem);\n font-weight: 600;\n letter-spacing: -0.04em;\n}\n\n.hero p,\n.toolbar p {\n margin: 8px 0 0;\n color: var(--muted);\n line-height: 1.5;\n}\n\n.search,\n.select,\n.field-input,\n.input {\n width: 100%;\n margin-top: 16px;\n padding: 12px 14px;\n border: 1px solid var(--line);\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.7);\n color: var(--ink);\n}\n\n.field-row {\n display: grid;\n gap: 10px;\n margin-top: 12px;\n}\n\n.field-row--inline {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.field-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.folder-panel {\n display: grid;\n gap: 14px;\n}\n\n.folder-panel__head h2 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.folder-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.folder-list,\n.jobs-list,\n.saved-filter-list {\n display: grid;\n gap: 10px;\n}\n\n.saved-filter-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n\n.folder-row,\n.meeting-row,\n.saved-filter-card__main {\n width: 100%;\n display: grid;\n gap: 4px;\n text-align: left;\n padding: 12px 14px;\n border: 1px solid transparent;\n border-radius: 16px;\n background: rgba(255, 255, 255, 0.72);\n color: inherit;\n cursor: pointer;\n transition:\n transform 140ms ease,\n border-color 140ms ease,\n background 140ms ease;\n}\n\n.meeting-row {\n margin: 0 0 10px;\n padding: 14px 16px;\n border-radius: 18px;\n}\n\n.saved-filter-card {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px;\n align-items: start;\n}\n\n.saved-filter-card__main {\n margin: 0;\n}\n\n.saved-filter-card__remove {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 12px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n cursor: pointer;\n font-weight: 700;\n}\n\n.folder-row:hover,\n.folder-row[data-selected=\"true\"],\n.saved-filter-card__main:hover {\n transform: translateY(-1px);\n border-color: rgba(163, 79, 47, 0.26);\n background: var(--panel-strong);\n}\n\n.meeting-row:hover,\n.meeting-row[data-selected=\"true\"] {\n transform: translateY(-1px);\n border-color: rgba(13, 106, 109, 0.25);\n background: var(--panel-strong);\n}\n\n.folder-row__title,\n.job-card__title,\n.saved-filter-card__title {\n font-weight: 700;\n}\n\n.meeting-row__title {\n font-weight: 600;\n}\n\n.folder-row__meta,\n.meeting-row__meta,\n.auth-card__meta,\n.job-card__meta,\n.folder-empty,\n.job-empty,\n.meeting-empty,\n.saved-filter-card__meta {\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.folder-empty--error,\n.meeting-empty--error,\n.auth-card__error {\n color: var(--error);\n}\n\n.meeting-list {\n padding: 14px;\n overflow: auto;\n}\n\n.detail {\n display: grid;\n grid-template-rows: auto auto 1fr;\n min-width: 0;\n}\n\n.detail-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 18px;\n}\n\n.detail-head h2 {\n margin: 0;\n font-family: var(--serif);\n font-size: clamp(1.8rem, 2.4vw, 2.4rem);\n font-weight: 600;\n}\n\n.state-badge {\n padding: 10px 14px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.92rem;\n font-weight: 700;\n}\n\n.state-badge[data-tone=\"busy\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.state-badge[data-tone=\"error\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.state-badge[data-tone=\"ok\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 14px;\n}\n\n.toolbar-actions,\n.auth-card__actions,\n.job-card__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n\n.toolbar-form {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px;\n width: min(440px, 100%);\n}\n\n.security-panel,\n.auth-panel,\n.jobs-panel {\n padding: 0 24px 18px;\n}\n\n.security-panel__head h3,\n.auth-panel__head h3,\n.jobs-panel__head h3 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.security-panel__head p,\n.auth-panel__head p,\n.jobs-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.security-panel__body,\n.auth-panel__body {\n display: grid;\n gap: 12px;\n margin-top: 14px;\n}\n\n.auth-card,\n.job-card {\n display: grid;\n gap: 12px;\n padding: 14px 16px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.72);\n}\n\n.job-card__head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n}\n\n.job-card__status {\n padding: 6px 10px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.82rem;\n font-weight: 700;\n}\n\n.job-card__status[data-status=\"running\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"failed\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"completed\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.workspace-tabs {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.workspace-tab,\n.button {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 14px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--ink);\n cursor: pointer;\n font-weight: 700;\n}\n\n.button {\n padding: 12px 16px;\n}\n\n.workspace-tab[data-selected=\"true\"],\n.button--primary {\n background: var(--ink);\n color: #fff;\n border-color: var(--ink);\n}\n\n.button--secondary {\n background: rgba(255, 255, 255, 0.72);\n}\n\n.button:disabled {\n cursor: not-allowed;\n opacity: 0.56;\n}\n\n.workspace-hint {\n margin-left: auto;\n color: var(--muted);\n font-size: 0.86rem;\n}\n\n.status-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 14px;\n}\n\n.status-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.detail-chip {\n padding: 10px 12px;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n color: var(--muted);\n font-size: 0.88rem;\n}\n\n.detail-body {\n padding: 0 24px 24px;\n overflow: auto;\n}\n\n.workspace-grid {\n display: grid;\n grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);\n gap: 18px;\n}\n\n.detail-section {\n margin-bottom: 20px;\n padding: 20px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n border-radius: 20px;\n}\n\n.detail-section h2 {\n margin: 0 0 14px;\n font-size: 1rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--mono);\n line-height: 1.55;\n}\n\n.empty {\n margin: 24px;\n padding: 24px;\n border-radius: 20px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n}\n\n@media (max-width: 1024px) {\n .shell,\n .workspace-grid {\n grid-template-columns: 1fr;\n }\n}\n/*$vite$:1*/";
6829
- const granolaWebClientJs = "//#region node_modules/solid-js/dist/solid.js\nvar sharedConfig = {\n context: void 0,\n registry: void 0,\n effects: void 0,\n done: false,\n getContextId() {\n return getContextId(this.context.count);\n },\n getNextContextId() {\n return getContextId(this.context.count++);\n }\n};\nfunction getContextId(count) {\n const num = String(count), len = num.length - 1;\n return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : \"\") + num;\n}\nfunction setHydrateContext(context) {\n sharedConfig.context = context;\n}\nfunction nextHydrateContext() {\n return {\n ...sharedConfig.context,\n id: sharedConfig.getNextContextId(),\n count: 0\n };\n}\nvar equalFn = (a, b) => a === b;\nvar $PROXY = Symbol(\"solid-proxy\");\nvar $TRACK = Symbol(\"solid-track\");\nvar signalOptions = { equals: equalFn };\nvar ERROR = null;\nvar runEffects = runQueue;\nvar STALE = 1;\nvar PENDING = 2;\nvar UNOWNED = {\n owned: null,\n cleanups: null,\n context: null,\n owner: null\n};\nvar Owner = null;\nvar Transition = null;\nvar Scheduler = null;\nvar ExternalSourceConfig = null;\nvar Listener = null;\nvar Updates = null;\nvar Effects = null;\nvar ExecCount = 0;\nfunction createRoot(fn, detachedOwner) {\n const listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === void 0 ? owner : detachedOwner, root = unowned ? UNOWNED : {\n owned: null,\n cleanups: null,\n context: current ? current.context : null,\n owner: current\n }, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));\n Owner = root;\n Listener = null;\n try {\n return runUpdates(updateFn, true);\n } finally {\n Listener = listener;\n Owner = owner;\n }\n}\nfunction createSignal(value, options) {\n options = options ? Object.assign({}, signalOptions, options) : signalOptions;\n const s = {\n value,\n observers: null,\n observerSlots: null,\n comparator: options.equals || void 0\n };\n const setter = (value) => {\n if (typeof value === \"function\") if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);\n else value = value(s.value);\n return writeSignal(s, value);\n };\n return [readSignal.bind(s), setter];\n}\nfunction createRenderEffect(fn, value, options) {\n const c = createComputation(fn, value, false, STALE);\n if (Scheduler && Transition && Transition.running) Updates.push(c);\n else updateComputation(c);\n}\nfunction createEffect(fn, value, options) {\n runEffects = runUserEffects;\n const c = createComputation(fn, value, false, STALE), s = SuspenseContext && useContext(SuspenseContext);\n if (s) c.suspense = s;\n if (!options || !options.render) c.user = true;\n Effects ? Effects.push(c) : updateComputation(c);\n}\nfunction createMemo(fn, value, options) {\n options = options ? Object.assign({}, signalOptions, options) : signalOptions;\n const c = createComputation(fn, value, true, 0);\n c.observers = null;\n c.observerSlots = null;\n c.comparator = options.equals || void 0;\n if (Scheduler && Transition && Transition.running) {\n c.tState = STALE;\n Updates.push(c);\n } else updateComputation(c);\n return readSignal.bind(c);\n}\nfunction batch(fn) {\n return runUpdates(fn, false);\n}\nfunction untrack(fn) {\n if (!ExternalSourceConfig && Listener === null) return fn();\n const listener = Listener;\n Listener = null;\n try {\n if (ExternalSourceConfig) return ExternalSourceConfig.untrack(fn);\n return fn();\n } finally {\n Listener = listener;\n }\n}\nfunction onMount(fn) {\n createEffect(() => untrack(fn));\n}\nfunction onCleanup(fn) {\n if (Owner === null);\n else if (Owner.cleanups === null) Owner.cleanups = [fn];\n else Owner.cleanups.push(fn);\n return fn;\n}\nfunction getListener() {\n return Listener;\n}\nfunction startTransition(fn) {\n if (Transition && Transition.running) {\n fn();\n return Transition.done;\n }\n const l = Listener;\n const o = Owner;\n return Promise.resolve().then(() => {\n Listener = l;\n Owner = o;\n let t;\n if (Scheduler || SuspenseContext) {\n t = Transition || (Transition = {\n sources: /* @__PURE__ */ new Set(),\n effects: [],\n promises: /* @__PURE__ */ new Set(),\n disposed: /* @__PURE__ */ new Set(),\n queue: /* @__PURE__ */ new Set(),\n running: true\n });\n t.done || (t.done = new Promise((res) => t.resolve = res));\n t.running = true;\n }\n runUpdates(fn, false);\n Listener = Owner = null;\n return t ? t.done : void 0;\n });\n}\nvar [transPending, setTransPending] = /* @__PURE__ */ createSignal(false);\nfunction useContext(context) {\n let value;\n return Owner && Owner.context && (value = Owner.context[context.id]) !== void 0 ? value : context.defaultValue;\n}\nvar SuspenseContext;\nfunction readSignal() {\n const runningTransition = Transition && Transition.running;\n if (this.sources && (runningTransition ? this.tState : this.state)) if ((runningTransition ? this.tState : this.state) === STALE) updateComputation(this);\n else {\n const updates = Updates;\n Updates = null;\n runUpdates(() => lookUpstream(this), false);\n Updates = updates;\n }\n if (Listener) {\n const sSlot = this.observers ? this.observers.length : 0;\n if (!Listener.sources) {\n Listener.sources = [this];\n Listener.sourceSlots = [sSlot];\n } else {\n Listener.sources.push(this);\n Listener.sourceSlots.push(sSlot);\n }\n if (!this.observers) {\n this.observers = [Listener];\n this.observerSlots = [Listener.sources.length - 1];\n } else {\n this.observers.push(Listener);\n this.observerSlots.push(Listener.sources.length - 1);\n }\n }\n if (runningTransition && Transition.sources.has(this)) return this.tValue;\n return this.value;\n}\nfunction writeSignal(node, value, isComp) {\n let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;\n if (!node.comparator || !node.comparator(current, value)) {\n if (Transition) {\n const TransitionRunning = Transition.running;\n if (TransitionRunning || !isComp && Transition.sources.has(node)) {\n Transition.sources.add(node);\n node.tValue = value;\n }\n if (!TransitionRunning) node.value = value;\n } else node.value = value;\n if (node.observers && node.observers.length) runUpdates(() => {\n for (let i = 0; i < node.observers.length; i += 1) {\n const o = node.observers[i];\n const TransitionRunning = Transition && Transition.running;\n if (TransitionRunning && Transition.disposed.has(o)) continue;\n if (TransitionRunning ? !o.tState : !o.state) {\n if (o.pure) Updates.push(o);\n else Effects.push(o);\n if (o.observers) markDownstream(o);\n }\n if (!TransitionRunning) o.state = STALE;\n else o.tState = STALE;\n }\n if (Updates.length > 1e6) {\n Updates = [];\n throw new Error();\n }\n }, false);\n }\n return value;\n}\nfunction updateComputation(node) {\n if (!node.fn) return;\n cleanNode(node);\n const time = ExecCount;\n runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time);\n if (Transition && !Transition.running && Transition.sources.has(node)) queueMicrotask(() => {\n runUpdates(() => {\n Transition && (Transition.running = true);\n Listener = Owner = node;\n runComputation(node, node.tValue, time);\n Listener = Owner = null;\n }, false);\n });\n}\nfunction runComputation(node, value, time) {\n let nextValue;\n const owner = Owner, listener = Listener;\n Listener = Owner = node;\n try {\n nextValue = node.fn(value);\n } catch (err) {\n if (node.pure) if (Transition && Transition.running) {\n node.tState = STALE;\n node.tOwned && node.tOwned.forEach(cleanNode);\n node.tOwned = void 0;\n } else {\n node.state = STALE;\n node.owned && node.owned.forEach(cleanNode);\n node.owned = null;\n }\n node.updatedAt = time + 1;\n return handleError(err);\n } finally {\n Listener = listener;\n Owner = owner;\n }\n if (!node.updatedAt || node.updatedAt <= time) {\n if (node.updatedAt != null && \"observers\" in node) writeSignal(node, nextValue, true);\n else if (Transition && Transition.running && node.pure) {\n if (!Transition.sources.has(node)) node.value = nextValue;\n Transition.sources.add(node);\n node.tValue = nextValue;\n } else node.value = nextValue;\n node.updatedAt = time;\n }\n}\nfunction createComputation(fn, init, pure, state = STALE, options) {\n const c = {\n fn,\n state,\n updatedAt: null,\n owned: null,\n sources: null,\n sourceSlots: null,\n cleanups: null,\n value: init,\n owner: Owner,\n context: Owner ? Owner.context : null,\n pure\n };\n if (Transition && Transition.running) {\n c.state = 0;\n c.tState = state;\n }\n if (Owner === null);\n else if (Owner !== UNOWNED) if (Transition && Transition.running && Owner.pure) if (!Owner.tOwned) Owner.tOwned = [c];\n else Owner.tOwned.push(c);\n else if (!Owner.owned) Owner.owned = [c];\n else Owner.owned.push(c);\n if (ExternalSourceConfig && c.fn) {\n const sourceFn = c.fn;\n const [track, trigger] = createSignal(void 0, { equals: false });\n const ordinary = ExternalSourceConfig.factory(sourceFn, trigger);\n onCleanup(() => ordinary.dispose());\n let inTransition;\n const triggerInTransition = () => startTransition(trigger).then(() => {\n if (inTransition) {\n inTransition.dispose();\n inTransition = void 0;\n }\n });\n c.fn = (x) => {\n track();\n if (Transition && Transition.running) {\n if (!inTransition) inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition);\n return inTransition.track(x);\n }\n return ordinary.track(x);\n };\n }\n return c;\n}\nfunction runTop(node) {\n const runningTransition = Transition && Transition.running;\n if ((runningTransition ? node.tState : node.state) === 0) return;\n if ((runningTransition ? node.tState : node.state) === PENDING) return lookUpstream(node);\n if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);\n const ancestors = [node];\n while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {\n if (runningTransition && Transition.disposed.has(node)) return;\n if (runningTransition ? node.tState : node.state) ancestors.push(node);\n }\n for (let i = ancestors.length - 1; i >= 0; i--) {\n node = ancestors[i];\n if (runningTransition) {\n let top = node, prev = ancestors[i + 1];\n while ((top = top.owner) && top !== prev) if (Transition.disposed.has(top)) return;\n }\n if ((runningTransition ? node.tState : node.state) === STALE) updateComputation(node);\n else if ((runningTransition ? node.tState : node.state) === PENDING) {\n const updates = Updates;\n Updates = null;\n runUpdates(() => lookUpstream(node, ancestors[0]), false);\n Updates = updates;\n }\n }\n}\nfunction runUpdates(fn, init) {\n if (Updates) return fn();\n let wait = false;\n if (!init) Updates = [];\n if (Effects) wait = true;\n else Effects = [];\n ExecCount++;\n try {\n const res = fn();\n completeUpdates(wait);\n return res;\n } catch (err) {\n if (!wait) Effects = null;\n Updates = null;\n handleError(err);\n }\n}\nfunction completeUpdates(wait) {\n if (Updates) {\n if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);\n else runQueue(Updates);\n Updates = null;\n }\n if (wait) return;\n let res;\n if (Transition) {\n if (!Transition.promises.size && !Transition.queue.size) {\n const sources = Transition.sources;\n const disposed = Transition.disposed;\n Effects.push.apply(Effects, Transition.effects);\n res = Transition.resolve;\n for (const e of Effects) {\n \"tState\" in e && (e.state = e.tState);\n delete e.tState;\n }\n Transition = null;\n runUpdates(() => {\n for (const d of disposed) cleanNode(d);\n for (const v of sources) {\n v.value = v.tValue;\n if (v.owned) for (let i = 0, len = v.owned.length; i < len; i++) cleanNode(v.owned[i]);\n if (v.tOwned) v.owned = v.tOwned;\n delete v.tValue;\n delete v.tOwned;\n v.tState = 0;\n }\n setTransPending(false);\n }, false);\n } else if (Transition.running) {\n Transition.running = false;\n Transition.effects.push.apply(Transition.effects, Effects);\n Effects = null;\n setTransPending(true);\n return;\n }\n }\n const e = Effects;\n Effects = null;\n if (e.length) runUpdates(() => runEffects(e), false);\n if (res) res();\n}\nfunction runQueue(queue) {\n for (let i = 0; i < queue.length; i++) runTop(queue[i]);\n}\nfunction scheduleQueue(queue) {\n for (let i = 0; i < queue.length; i++) {\n const item = queue[i];\n const tasks = Transition.queue;\n if (!tasks.has(item)) {\n tasks.add(item);\n Scheduler(() => {\n tasks.delete(item);\n runUpdates(() => {\n Transition.running = true;\n runTop(item);\n }, false);\n Transition && (Transition.running = false);\n });\n }\n }\n}\nfunction runUserEffects(queue) {\n let i, userLength = 0;\n for (i = 0; i < queue.length; i++) {\n const e = queue[i];\n if (!e.user) runTop(e);\n else queue[userLength++] = e;\n }\n if (sharedConfig.context) {\n if (sharedConfig.count) {\n sharedConfig.effects || (sharedConfig.effects = []);\n sharedConfig.effects.push(...queue.slice(0, userLength));\n return;\n }\n setHydrateContext();\n }\n if (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count)) {\n queue = [...sharedConfig.effects, ...queue];\n userLength += sharedConfig.effects.length;\n delete sharedConfig.effects;\n }\n for (i = 0; i < userLength; i++) runTop(queue[i]);\n}\nfunction lookUpstream(node, ignore) {\n const runningTransition = Transition && Transition.running;\n if (runningTransition) node.tState = 0;\n else node.state = 0;\n for (let i = 0; i < node.sources.length; i += 1) {\n const source = node.sources[i];\n if (source.sources) {\n const state = runningTransition ? source.tState : source.state;\n if (state === STALE) {\n if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source);\n } else if (state === PENDING) lookUpstream(source, ignore);\n }\n }\n}\nfunction markDownstream(node) {\n const runningTransition = Transition && Transition.running;\n for (let i = 0; i < node.observers.length; i += 1) {\n const o = node.observers[i];\n if (runningTransition ? !o.tState : !o.state) {\n if (runningTransition) o.tState = PENDING;\n else o.state = PENDING;\n if (o.pure) Updates.push(o);\n else Effects.push(o);\n o.observers && markDownstream(o);\n }\n }\n}\nfunction cleanNode(node) {\n let i;\n if (node.sources) while (node.sources.length) {\n const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;\n if (obs && obs.length) {\n const n = obs.pop(), s = source.observerSlots.pop();\n if (index < obs.length) {\n n.sourceSlots[s] = index;\n obs[index] = n;\n source.observerSlots[index] = s;\n }\n }\n }\n if (node.tOwned) {\n for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]);\n delete node.tOwned;\n }\n if (Transition && Transition.running && node.pure) reset(node, true);\n else if (node.owned) {\n for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);\n node.owned = null;\n }\n if (node.cleanups) {\n for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();\n node.cleanups = null;\n }\n if (Transition && Transition.running) node.tState = 0;\n else node.state = 0;\n}\nfunction reset(node, top) {\n if (!top) {\n node.tState = 0;\n Transition.disposed.add(node);\n }\n if (node.owned) for (let i = 0; i < node.owned.length; i++) reset(node.owned[i]);\n}\nfunction castError(err) {\n if (err instanceof Error) return err;\n return new Error(typeof err === \"string\" ? err : \"Unknown error\", { cause: err });\n}\nfunction runErrors(err, fns, owner) {\n try {\n for (const f of fns) f(err);\n } catch (e) {\n handleError(e, owner && owner.owner || null);\n }\n}\nfunction handleError(err, owner = Owner) {\n const fns = ERROR && owner && owner.context && owner.context[ERROR];\n const error = castError(err);\n if (!fns) throw error;\n if (Effects) Effects.push({\n fn() {\n runErrors(error, fns, owner);\n },\n state: STALE\n });\n else runErrors(error, fns, owner);\n}\nvar FALLBACK = Symbol(\"fallback\");\nfunction dispose(d) {\n for (let i = 0; i < d.length; i++) d[i]();\n}\nfunction mapArray(list, mapFn, options = {}) {\n let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null;\n onCleanup(() => dispose(disposers));\n return () => {\n let newItems = list() || [], newLen = newItems.length, i, j;\n newItems[$TRACK];\n return untrack(() => {\n let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;\n if (newLen === 0) {\n if (len !== 0) {\n dispose(disposers);\n disposers = [];\n items = [];\n mapped = [];\n len = 0;\n indexes && (indexes = []);\n }\n if (options.fallback) {\n items = [FALLBACK];\n mapped[0] = createRoot((disposer) => {\n disposers[0] = disposer;\n return options.fallback();\n });\n len = 1;\n }\n } else if (len === 0) {\n mapped = new Array(newLen);\n for (j = 0; j < newLen; j++) {\n items[j] = newItems[j];\n mapped[j] = createRoot(mapper);\n }\n len = newLen;\n } else {\n temp = new Array(newLen);\n tempdisposers = new Array(newLen);\n indexes && (tempIndexes = new Array(newLen));\n for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++);\n for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {\n temp[newEnd] = mapped[end];\n tempdisposers[newEnd] = disposers[end];\n indexes && (tempIndexes[newEnd] = indexes[end]);\n }\n newIndices = /* @__PURE__ */ new Map();\n newIndicesNext = new Array(newEnd + 1);\n for (j = newEnd; j >= start; j--) {\n item = newItems[j];\n i = newIndices.get(item);\n newIndicesNext[j] = i === void 0 ? -1 : i;\n newIndices.set(item, j);\n }\n for (i = start; i <= end; i++) {\n item = items[i];\n j = newIndices.get(item);\n if (j !== void 0 && j !== -1) {\n temp[j] = mapped[i];\n tempdisposers[j] = disposers[i];\n indexes && (tempIndexes[j] = indexes[i]);\n j = newIndicesNext[j];\n newIndices.set(item, j);\n } else disposers[i]();\n }\n for (j = start; j < newLen; j++) if (j in temp) {\n mapped[j] = temp[j];\n disposers[j] = tempdisposers[j];\n if (indexes) {\n indexes[j] = tempIndexes[j];\n indexes[j](j);\n }\n } else mapped[j] = createRoot(mapper);\n mapped = mapped.slice(0, len = newLen);\n items = newItems.slice(0);\n }\n return mapped;\n });\n function mapper(disposer) {\n disposers[j] = disposer;\n if (indexes) {\n const [s, set] = createSignal(j);\n indexes[j] = set;\n return mapFn(newItems[j], s);\n }\n return mapFn(newItems[j]);\n }\n };\n}\nvar hydrationEnabled = false;\nfunction createComponent(Comp, props) {\n if (hydrationEnabled) {\n if (sharedConfig.context) {\n const c = sharedConfig.context;\n setHydrateContext(nextHydrateContext());\n const r = untrack(() => Comp(props || {}));\n setHydrateContext(c);\n return r;\n }\n }\n return untrack(() => Comp(props || {}));\n}\nvar narrowedError = (name) => `Stale read from <${name}>.`;\nfunction For(props) {\n const fallback = \"fallback\" in props && { fallback: () => props.fallback };\n return createMemo(mapArray(() => props.each, props.children, fallback || void 0));\n}\nfunction Show(props) {\n const keyed = props.keyed;\n const conditionValue = createMemo(() => props.when, void 0, void 0);\n const condition = keyed ? conditionValue : createMemo(conditionValue, void 0, { equals: (a, b) => !a === !b });\n return createMemo(() => {\n const c = condition();\n if (c) {\n const child = props.children;\n return typeof child === \"function\" && child.length > 0 ? untrack(() => child(keyed ? c : () => {\n if (!untrack(condition)) throw narrowedError(\"Show\");\n return conditionValue();\n })) : child;\n }\n return props.fallback;\n }, void 0, void 0);\n}\n//#endregion\n//#region node_modules/solid-js/web/dist/web.js\nvar memo = (fn) => createMemo(() => fn());\nfunction reconcileArrays(parentNode, a, b) {\n let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null;\n while (aStart < aEnd || bStart < bEnd) {\n if (a[aStart] === b[bStart]) {\n aStart++;\n bStart++;\n continue;\n }\n while (a[aEnd - 1] === b[bEnd - 1]) {\n aEnd--;\n bEnd--;\n }\n if (aEnd === aStart) {\n const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;\n while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node);\n } else if (bEnd === bStart) while (aStart < aEnd) {\n if (!map || !map.has(a[aStart])) a[aStart].remove();\n aStart++;\n }\n else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {\n const node = a[--aEnd].nextSibling;\n parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling);\n parentNode.insertBefore(b[--bEnd], node);\n a[aEnd] = b[bEnd];\n } else {\n if (!map) {\n map = /* @__PURE__ */ new Map();\n let i = bStart;\n while (i < bEnd) map.set(b[i], i++);\n }\n const index = map.get(a[aStart]);\n if (index != null) if (bStart < index && index < bEnd) {\n let i = aStart, sequence = 1, t;\n while (++i < aEnd && i < bEnd) {\n if ((t = map.get(a[i])) == null || t !== index + sequence) break;\n sequence++;\n }\n if (sequence > index - bStart) {\n const node = a[aStart];\n while (bStart < index) parentNode.insertBefore(b[bStart++], node);\n } else parentNode.replaceChild(b[bStart++], a[aStart++]);\n } else aStart++;\n else a[aStart++].remove();\n }\n }\n}\nvar $$EVENTS = \"_$DX_DELEGATE\";\nfunction render(code, element, init, options = {}) {\n let disposer;\n createRoot((dispose) => {\n disposer = dispose;\n element === document ? code() : insert(element, code(), element.firstChild ? null : void 0, init);\n }, options.owner);\n return () => {\n disposer();\n element.textContent = \"\";\n };\n}\nfunction template(html, isImportNode, isSVG, isMathML) {\n let node;\n const create = () => {\n const t = isMathML ? document.createElementNS(\"http://www.w3.org/1998/Math/MathML\", \"template\") : document.createElement(\"template\");\n t.innerHTML = html;\n return isSVG ? t.content.firstChild.firstChild : isMathML ? t.firstChild : t.content.firstChild;\n };\n const fn = isImportNode ? () => untrack(() => document.importNode(node || (node = create()), true)) : () => (node || (node = create())).cloneNode(true);\n fn.cloneNode = fn;\n return fn;\n}\nfunction delegateEvents(eventNames, document = window.document) {\n const e = document[$$EVENTS] || (document[$$EVENTS] = /* @__PURE__ */ new Set());\n for (let i = 0, l = eventNames.length; i < l; i++) {\n const name = eventNames[i];\n if (!e.has(name)) {\n e.add(name);\n document.addEventListener(name, eventHandler);\n }\n }\n}\nfunction setAttribute(node, name, value) {\n if (isHydrating(node)) return;\n if (value == null) node.removeAttribute(name);\n else node.setAttribute(name, value);\n}\nfunction addEventListener(node, name, handler, delegate) {\n if (delegate) if (Array.isArray(handler)) {\n node[`$$${name}`] = handler[0];\n node[`$$${name}Data`] = handler[1];\n } else node[`$$${name}`] = handler;\n else if (Array.isArray(handler)) {\n const handlerFn = handler[0];\n node.addEventListener(name, handler[0] = (e) => handlerFn.call(node, handler[1], e));\n } else node.addEventListener(name, handler, typeof handler !== \"function\" && handler);\n}\nfunction insert(parent, accessor, marker, initial) {\n if (marker !== void 0 && !initial) initial = [];\n if (typeof accessor !== \"function\") return insertExpression(parent, accessor, initial, marker);\n createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);\n}\nfunction isHydrating(node) {\n return !!sharedConfig.context && !sharedConfig.done && (!node || node.isConnected);\n}\nfunction eventHandler(e) {\n if (sharedConfig.registry && sharedConfig.events) {\n if (sharedConfig.events.find(([el, ev]) => ev === e)) return;\n }\n let node = e.target;\n const key = `$$${e.type}`;\n const oriTarget = e.target;\n const oriCurrentTarget = e.currentTarget;\n const retarget = (value) => Object.defineProperty(e, \"target\", {\n configurable: true,\n value\n });\n const handleNode = () => {\n const handler = node[key];\n if (handler && !node.disabled) {\n const data = node[`${key}Data`];\n data !== void 0 ? handler.call(node, data, e) : handler.call(node, e);\n if (e.cancelBubble) return;\n }\n node.host && typeof node.host !== \"string\" && !node.host._$host && node.contains(e.target) && retarget(node.host);\n return true;\n };\n const walkUpTree = () => {\n while (handleNode() && (node = node._$host || node.parentNode || node.host));\n };\n Object.defineProperty(e, \"currentTarget\", {\n configurable: true,\n get() {\n return node || document;\n }\n });\n if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;\n if (e.composedPath) {\n const path = e.composedPath();\n retarget(path[0]);\n for (let i = 0; i < path.length - 2; i++) {\n node = path[i];\n if (!handleNode()) break;\n if (node._$host) {\n node = node._$host;\n walkUpTree();\n break;\n }\n if (node.parentNode === oriCurrentTarget) break;\n }\n } else walkUpTree();\n retarget(oriTarget);\n}\nfunction insertExpression(parent, value, current, marker, unwrapArray) {\n const hydrating = isHydrating(parent);\n if (hydrating) {\n !current && (current = [...parent.childNodes]);\n let cleaned = [];\n for (let i = 0; i < current.length; i++) {\n const node = current[i];\n if (node.nodeType === 8 && node.data.slice(0, 2) === \"!$\") node.remove();\n else cleaned.push(node);\n }\n current = cleaned;\n }\n while (typeof current === \"function\") current = current();\n if (value === current) return current;\n const t = typeof value, multi = marker !== void 0;\n parent = multi && current[0] && current[0].parentNode || parent;\n if (t === \"string\" || t === \"number\") {\n if (hydrating) return current;\n if (t === \"number\") {\n value = value.toString();\n if (value === current) return current;\n }\n if (multi) {\n let node = current[0];\n if (node && node.nodeType === 3) node.data !== value && (node.data = value);\n else node = document.createTextNode(value);\n current = cleanChildren(parent, current, marker, node);\n } else if (current !== \"\" && typeof current === \"string\") current = parent.firstChild.data = value;\n else current = parent.textContent = value;\n } else if (value == null || t === \"boolean\") {\n if (hydrating) return current;\n current = cleanChildren(parent, current, marker);\n } else if (t === \"function\") {\n createRenderEffect(() => {\n let v = value();\n while (typeof v === \"function\") v = v();\n current = insertExpression(parent, v, current, marker);\n });\n return () => current;\n } else if (Array.isArray(value)) {\n const array = [];\n const currentArray = current && Array.isArray(current);\n if (normalizeIncomingArray(array, value, current, unwrapArray)) {\n createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));\n return () => current;\n }\n if (hydrating) {\n if (!array.length) return current;\n if (marker === void 0) return current = [...parent.childNodes];\n let node = array[0];\n if (node.parentNode !== parent) return current;\n const nodes = [node];\n while ((node = node.nextSibling) !== marker) nodes.push(node);\n return current = nodes;\n }\n if (array.length === 0) {\n current = cleanChildren(parent, current, marker);\n if (multi) return current;\n } else if (currentArray) if (current.length === 0) appendNodes(parent, array, marker);\n else reconcileArrays(parent, current, array);\n else {\n current && cleanChildren(parent);\n appendNodes(parent, array);\n }\n current = array;\n } else if (value.nodeType) {\n if (hydrating && value.parentNode) return current = multi ? [value] : value;\n if (Array.isArray(current)) {\n if (multi) return current = cleanChildren(parent, current, marker, value);\n cleanChildren(parent, current, null, value);\n } else if (current == null || current === \"\" || !parent.firstChild) parent.appendChild(value);\n else parent.replaceChild(value, parent.firstChild);\n current = value;\n }\n return current;\n}\nfunction normalizeIncomingArray(normalized, array, current, unwrap) {\n let dynamic = false;\n for (let i = 0, len = array.length; i < len; i++) {\n let item = array[i], prev = current && current[normalized.length], t;\n if (item == null || item === true || item === false);\n else if ((t = typeof item) === \"object\" && item.nodeType) normalized.push(item);\n else if (Array.isArray(item)) dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;\n else if (t === \"function\") if (unwrap) {\n while (typeof item === \"function\") item = item();\n dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;\n } else {\n normalized.push(item);\n dynamic = true;\n }\n else {\n const value = String(item);\n if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);\n else normalized.push(document.createTextNode(value));\n }\n }\n return dynamic;\n}\nfunction appendNodes(parent, array, marker = null) {\n for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);\n}\nfunction cleanChildren(parent, current, marker, replacement) {\n if (marker === void 0) return parent.textContent = \"\";\n const node = replacement || document.createTextNode(\"\");\n if (current.length) {\n let inserted = false;\n for (let i = current.length - 1; i >= 0; i--) {\n const el = current[i];\n if (node !== el) {\n const isParent = el.parentNode === parent;\n if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);\n else isParent && el.remove();\n } else inserted = true;\n }\n } else parent.insertBefore(node, marker);\n return [node];\n}\n//#endregion\n//#region node_modules/solid-js/store/dist/store.js\nvar $RAW = Symbol(\"store-raw\"), $NODE = Symbol(\"store-node\"), $HAS = Symbol(\"store-has\"), $SELF = Symbol(\"store-self\");\nfunction wrap$1(value) {\n let p = value[$PROXY];\n if (!p) {\n Object.defineProperty(value, $PROXY, { value: p = new Proxy(value, proxyTraps$1) });\n if (!Array.isArray(value)) {\n const keys = Object.keys(value), desc = Object.getOwnPropertyDescriptors(value);\n for (let i = 0, l = keys.length; i < l; i++) {\n const prop = keys[i];\n if (desc[prop].get) Object.defineProperty(value, prop, {\n enumerable: desc[prop].enumerable,\n get: desc[prop].get.bind(p)\n });\n }\n }\n }\n return p;\n}\nfunction isWrappable(obj) {\n let proto;\n return obj != null && typeof obj === \"object\" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj));\n}\nfunction unwrap(item, set = /* @__PURE__ */ new Set()) {\n let result, unwrapped, v, prop;\n if (result = item != null && item[$RAW]) return result;\n if (!isWrappable(item) || set.has(item)) return item;\n if (Array.isArray(item)) {\n if (Object.isFrozen(item)) item = item.slice(0);\n else set.add(item);\n for (let i = 0, l = item.length; i < l; i++) {\n v = item[i];\n if ((unwrapped = unwrap(v, set)) !== v) item[i] = unwrapped;\n }\n } else {\n if (Object.isFrozen(item)) item = Object.assign({}, item);\n else set.add(item);\n const keys = Object.keys(item), desc = Object.getOwnPropertyDescriptors(item);\n for (let i = 0, l = keys.length; i < l; i++) {\n prop = keys[i];\n if (desc[prop].get) continue;\n v = item[prop];\n if ((unwrapped = unwrap(v, set)) !== v) item[prop] = unwrapped;\n }\n }\n return item;\n}\nfunction getNodes(target, symbol) {\n let nodes = target[symbol];\n if (!nodes) Object.defineProperty(target, symbol, { value: nodes = Object.create(null) });\n return nodes;\n}\nfunction getNode(nodes, property, value) {\n if (nodes[property]) return nodes[property];\n const [s, set] = createSignal(value, {\n equals: false,\n internal: true\n });\n s.$ = set;\n return nodes[property] = s;\n}\nfunction proxyDescriptor$1(target, property) {\n const desc = Reflect.getOwnPropertyDescriptor(target, property);\n if (!desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE) return desc;\n delete desc.value;\n delete desc.writable;\n desc.get = () => target[$PROXY][property];\n return desc;\n}\nfunction trackSelf(target) {\n getListener() && getNode(getNodes(target, $NODE), $SELF)();\n}\nfunction ownKeys(target) {\n trackSelf(target);\n return Reflect.ownKeys(target);\n}\nvar proxyTraps$1 = {\n get(target, property, receiver) {\n if (property === $RAW) return target;\n if (property === $PROXY) return receiver;\n if (property === $TRACK) {\n trackSelf(target);\n return receiver;\n }\n const nodes = getNodes(target, $NODE);\n const tracked = nodes[property];\n let value = tracked ? tracked() : target[property];\n if (property === $NODE || property === $HAS || property === \"__proto__\") return value;\n if (!tracked) {\n const desc = Object.getOwnPropertyDescriptor(target, property);\n if (getListener() && (typeof value !== \"function\" || target.hasOwnProperty(property)) && !(desc && desc.get)) value = getNode(nodes, property, value)();\n }\n return isWrappable(value) ? wrap$1(value) : value;\n },\n has(target, property) {\n if (property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === $HAS || property === \"__proto__\") return true;\n getListener() && getNode(getNodes(target, $HAS), property)();\n return property in target;\n },\n set() {\n return true;\n },\n deleteProperty() {\n return true;\n },\n ownKeys,\n getOwnPropertyDescriptor: proxyDescriptor$1\n};\nfunction setProperty(state, property, value, deleting = false) {\n if (!deleting && state[property] === value) return;\n const prev = state[property], len = state.length;\n if (value === void 0) {\n delete state[property];\n if (state[$HAS] && state[$HAS][property] && prev !== void 0) state[$HAS][property].$();\n } else {\n state[property] = value;\n if (state[$HAS] && state[$HAS][property] && prev === void 0) state[$HAS][property].$();\n }\n let nodes = getNodes(state, $NODE), node;\n if (node = getNode(nodes, property, prev)) node.$(() => value);\n if (Array.isArray(state) && state.length !== len) {\n for (let i = state.length; i < len; i++) (node = nodes[i]) && node.$();\n (node = getNode(nodes, \"length\", len)) && node.$(state.length);\n }\n (node = nodes[$SELF]) && node.$();\n}\nfunction mergeStoreNode(state, value) {\n const keys = Object.keys(value);\n for (let i = 0; i < keys.length; i += 1) {\n const key = keys[i];\n setProperty(state, key, value[key]);\n }\n}\nfunction updateArray(current, next) {\n if (typeof next === \"function\") next = next(current);\n next = unwrap(next);\n if (Array.isArray(next)) {\n if (current === next) return;\n let i = 0, len = next.length;\n for (; i < len; i++) {\n const value = next[i];\n if (current[i] !== value) setProperty(current, i, value);\n }\n setProperty(current, \"length\", len);\n } else mergeStoreNode(current, next);\n}\nfunction updatePath(current, path, traversed = []) {\n let part, prev = current;\n if (path.length > 1) {\n part = path.shift();\n const partType = typeof part, isArray = Array.isArray(current);\n if (Array.isArray(part)) {\n for (let i = 0; i < part.length; i++) updatePath(current, [part[i]].concat(path), traversed);\n return;\n } else if (isArray && partType === \"function\") {\n for (let i = 0; i < current.length; i++) if (part(current[i], i)) updatePath(current, [i].concat(path), traversed);\n return;\n } else if (isArray && partType === \"object\") {\n const { from = 0, to = current.length - 1, by = 1 } = part;\n for (let i = from; i <= to; i += by) updatePath(current, [i].concat(path), traversed);\n return;\n } else if (path.length > 1) {\n updatePath(current[part], path, [part].concat(traversed));\n return;\n }\n prev = current[part];\n traversed = [part].concat(traversed);\n }\n let value = path[0];\n if (typeof value === \"function\") {\n value = value(prev, traversed);\n if (value === prev) return;\n }\n if (part === void 0 && value == void 0) return;\n value = unwrap(value);\n if (part === void 0 || isWrappable(prev) && isWrappable(value) && !Array.isArray(value)) mergeStoreNode(prev, value);\n else setProperty(current, part, value);\n}\nfunction createStore(...[store, options]) {\n const unwrappedStore = unwrap(store || {});\n const isArray = Array.isArray(unwrappedStore);\n const wrappedStore = wrap$1(unwrappedStore);\n function setStore(...args) {\n batch(() => {\n isArray && args.length === 1 ? updateArray(unwrappedStore, args[0]) : updatePath(unwrappedStore, args);\n });\n }\n return [wrappedStore, setStore];\n}\n//#endregion\n//#region src/transport.ts\nvar granolaTransportPaths = {\n authLock: \"/auth/lock\",\n authLogin: \"/auth/login\",\n authLogout: \"/auth/logout\",\n authMode: \"/auth/mode\",\n authRefresh: \"/auth/refresh\",\n authStatus: \"/auth/status\",\n authUnlock: \"/auth/unlock\",\n automationMatches: \"/automation/matches\",\n automationRules: \"/automation/rules\",\n automationRuns: \"/automation/runs\",\n events: \"/events\",\n exportJobs: \"/exports/jobs\",\n exportNotes: \"/exports/notes\",\n exportTranscripts: \"/exports/transcripts\",\n folderResolve: \"/folders/resolve\",\n folders: \"/folders\",\n health: \"/health\",\n meetingResolve: \"/meetings/resolve\",\n meetings: \"/meetings\",\n root: \"/\",\n serverInfo: \"/server/info\",\n syncRun: \"/sync\",\n syncEvents: \"/sync/events\",\n state: \"/state\"\n};\nfunction appendSearchParams(path, params) {\n const url = new URL(path, \"http://localhost\");\n for (const [key, value] of Object.entries(params)) {\n if (value === void 0 || value === false || value === \"\") continue;\n url.searchParams.set(key, String(value));\n }\n return `${url.pathname}${url.search}`;\n}\nfunction granolaMeetingPath(id) {\n return `${granolaTransportPaths.meetings}/${encodeURIComponent(id)}`;\n}\nfunction granolaMeetingResolvePath(query, options = {}) {\n return appendSearchParams(granolaTransportPaths.meetingResolve, {\n includeTranscript: options.includeTranscript ? \"true\" : void 0,\n q: query\n });\n}\nfunction granolaMeetingsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.meetings, {\n folderId: options.folderId,\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search,\n sort: options.sort,\n updatedFrom: options.updatedFrom,\n updatedTo: options.updatedTo\n });\n}\nfunction granolaFolderPath(id) {\n return `${granolaTransportPaths.folders}/${encodeURIComponent(id)}`;\n}\nfunction granolaFolderResolvePath(query) {\n return appendSearchParams(granolaTransportPaths.folderResolve, { q: query });\n}\nfunction granolaFoldersPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.folders, {\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search\n });\n}\nfunction granolaExportJobsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.exportJobs, { limit: options.limit });\n}\nfunction granolaAutomationRunsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationRuns, {\n limit: options.limit,\n status: options.status\n });\n}\nfunction granolaAutomationRunDecisionPath(id, decision) {\n return `${granolaTransportPaths.automationRuns}/${encodeURIComponent(id)}/${decision}`;\n}\nfunction granolaExportJobRerunPath(id) {\n return `${granolaTransportPaths.exportJobs}/${encodeURIComponent(id)}/rerun`;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/checkPrivateRedeclaration.js\nfunction _checkPrivateRedeclaration(e, t) {\n if (t.has(e)) throw new TypeError(\"Cannot initialize the same private elements twice on an object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldInitSpec.js\nfunction _classPrivateFieldInitSpec(e, t, a) {\n _checkPrivateRedeclaration(e, t), t.set(e, a);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/typeof.js\nfunction _typeof(o) {\n \"@babel/helpers - typeof\";\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function(o) {\n return typeof o;\n } : function(o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/defineProperty.js\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/assertClassBrand.js\nfunction _assertClassBrand(e, t, n) {\n if (\"function\" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;\n throw new TypeError(\"Private element is not present on this object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldSet2.js\nfunction _classPrivateFieldSet2(s, a, r) {\n return s.set(_assertClassBrand(s, a), r), r;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldGet2.js\nfunction _classPrivateFieldGet2(s, a) {\n return s.get(_assertClassBrand(s, a));\n}\n//#endregion\n//#region src/server/client.ts\nfunction cloneValue(value) {\n return structuredClone(value);\n}\nfunction normaliseServerUrl(serverUrl) {\n const raw = serverUrl instanceof URL ? serverUrl.href : serverUrl.trim();\n if (!raw) throw new Error(\"server URL is required\");\n const withProtocol = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(raw) ? raw : `http://${raw}`;\n const parsed = new URL(withProtocol);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") throw new Error(\"server URL must use http or https\");\n parsed.pathname = \"/\";\n parsed.search = \"\";\n parsed.hash = \"\";\n return parsed;\n}\nfunction mergeHeaders(...values) {\n const headers = new Headers();\n for (const value of values) {\n if (!value) continue;\n new Headers(value).forEach((headerValue, headerName) => {\n headers.set(headerName, headerValue);\n });\n }\n return headers;\n}\nasync function responseError(response) {\n let message = `${response.status} ${response.statusText}`.trim();\n try {\n const payload = await response.json();\n if (typeof payload.error === \"string\" && payload.error.trim()) message = payload.error;\n else if (typeof payload.message === \"string\" && payload.message.trim()) message = payload.message;\n } catch {\n const text = (await response.text()).trim();\n if (text) message = text;\n }\n return new Error(message);\n}\nfunction parseSseEvent(payload) {\n const data = payload.replaceAll(\"\\r\\n\", \"\\n\").split(\"\\n\").filter((line) => line.startsWith(\"data:\")).map((line) => line.slice(5).trimStart()).join(\"\\n\");\n if (!data) return;\n return JSON.parse(data);\n}\nvar _closed = /* @__PURE__ */ new WeakMap();\nvar _eventLoop = /* @__PURE__ */ new WeakMap();\nvar _listeners = /* @__PURE__ */ new WeakMap();\nvar _fetchImpl = /* @__PURE__ */ new WeakMap();\nvar _password = /* @__PURE__ */ new WeakMap();\nvar _reconnectDelayMs = /* @__PURE__ */ new WeakMap();\nvar _streamAbortController = /* @__PURE__ */ new WeakMap();\nvar _state = /* @__PURE__ */ new WeakMap();\nvar GranolaServerClient = class GranolaServerClient {\n constructor(info, url, initialState, options = {}) {\n _classPrivateFieldInitSpec(this, _closed, false);\n _classPrivateFieldInitSpec(this, _eventLoop, void 0);\n _classPrivateFieldInitSpec(this, _listeners, /* @__PURE__ */ new Set());\n _classPrivateFieldInitSpec(this, _fetchImpl, void 0);\n _classPrivateFieldInitSpec(this, _password, void 0);\n _classPrivateFieldInitSpec(this, _reconnectDelayMs, void 0);\n _defineProperty(this, \"info\", void 0);\n _classPrivateFieldInitSpec(this, _streamAbortController, void 0);\n _classPrivateFieldInitSpec(this, _state, void 0);\n this.url = url;\n _classPrivateFieldSet2(_fetchImpl, this, options.fetchImpl ?? fetch);\n this.info = cloneValue(info);\n _classPrivateFieldSet2(_password, this, options.password?.trim() || void 0);\n _classPrivateFieldSet2(_reconnectDelayMs, this, options.reconnectDelayMs ?? 1e3);\n _classPrivateFieldSet2(_state, this, cloneValue(initialState));\n }\n static async connect(serverUrl, options = {}) {\n const url = normaliseServerUrl(serverUrl);\n const fetchImpl = options.fetchImpl ?? fetch;\n const infoResponse = await fetchImpl(new URL(granolaTransportPaths.serverInfo, url), { headers: mergeHeaders({\n ...options.password?.trim() ? { \"x-granola-password\": options.password.trim() } : {},\n accept: \"application/json\"\n }) });\n if (!infoResponse.ok) throw await responseError(infoResponse);\n const info = await infoResponse.json();\n if (info.protocolVersion !== 2) throw new Error(`unsupported Granola transport protocol: expected 2, got ${info.protocolVersion}`);\n const response = await fetchImpl(new URL(granolaTransportPaths.state, url), { headers: mergeHeaders({\n ...options.password?.trim() ? { \"x-granola-password\": options.password.trim() } : {},\n accept: \"application/json\"\n }) });\n if (!response.ok) throw await responseError(response);\n const client = new GranolaServerClient(info, url, await response.json(), options);\n client.startEvents();\n return client;\n }\n async close() {\n _classPrivateFieldSet2(_closed, this, true);\n _classPrivateFieldGet2(_streamAbortController, this)?.abort();\n try {\n await _classPrivateFieldGet2(_eventLoop, this);\n } catch {}\n }\n getState() {\n return cloneValue(_classPrivateFieldGet2(_state, this));\n }\n subscribe(listener) {\n _classPrivateFieldGet2(_listeners, this).add(listener);\n return () => {\n _classPrivateFieldGet2(_listeners, this).delete(listener);\n };\n }\n async inspectAuth() {\n return await this.requestJson(granolaTransportPaths.authStatus);\n }\n async listAutomationRules() {\n return await this.requestJson(granolaTransportPaths.automationRules);\n }\n async listAutomationMatches(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.automationMatches}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.automationMatches;\n return await this.requestJson(path);\n }\n async listAutomationRuns(options = {}) {\n return await this.requestJson(granolaAutomationRunsPath(options));\n }\n async resolveAutomationRun(id, decision, options = {}) {\n return await this.requestJson(granolaAutomationRunDecisionPath(id, decision), {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async inspectSync() {\n return cloneValue(_classPrivateFieldGet2(_state, this).sync);\n }\n async listSyncEvents(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.syncEvents}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.syncEvents;\n return await this.requestJson(path);\n }\n async loginAuth(options = {}) {\n return await this.requestJson(granolaTransportPaths.authLogin, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async logoutAuth() {\n return await this.requestJson(granolaTransportPaths.authLogout, { method: \"POST\" });\n }\n async refreshAuth() {\n return await this.requestJson(granolaTransportPaths.authRefresh, { method: \"POST\" });\n }\n async switchAuthMode(mode) {\n return await this.requestJson(granolaTransportPaths.authMode, {\n body: JSON.stringify({ mode }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async sync(options = {}) {\n return await this.requestJson(granolaTransportPaths.syncRun, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async listFolders(options = {}) {\n return await this.requestJson(granolaFoldersPath(options));\n }\n async getFolder(id) {\n return await this.requestJson(granolaFolderPath(id));\n }\n async findFolder(query) {\n return await this.requestJson(granolaFolderResolvePath(query));\n }\n async listMeetings(options = {}) {\n return await this.requestJson(granolaMeetingsPath(options));\n }\n async getMeeting(id, options = {}) {\n return await this.requestJson(`${granolaMeetingPath(id)}${options.requireCache ? \"?includeTranscript=true\" : \"\"}`);\n }\n async findMeeting(query, options = {}) {\n return await this.requestJson(granolaMeetingResolvePath(query, { includeTranscript: options.requireCache }));\n }\n async listExportJobs(options = {}) {\n return await this.requestJson(granolaExportJobsPath(options));\n }\n async exportNotes(format = \"markdown\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportNotes, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async exportTranscripts(format = \"text\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportTranscripts, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async rerunExportJob(id) {\n return await this.requestJson(granolaExportJobRerunPath(id), { method: \"POST\" });\n }\n async request(path, init = {}) {\n const response = await _classPrivateFieldGet2(_fetchImpl, this).call(this, new URL(path, this.url), {\n ...init,\n headers: mergeHeaders({\n ..._classPrivateFieldGet2(_password, this) ? { \"x-granola-password\": _classPrivateFieldGet2(_password, this) } : {},\n accept: \"application/json\"\n }, init.headers)\n });\n if (!response.ok) throw await responseError(response);\n return response;\n }\n async requestJson(path, init = {}) {\n return cloneValue(await (await this.request(path, init)).json());\n }\n emit(event) {\n _classPrivateFieldSet2(_state, this, cloneValue(event.state));\n const nextEvent = cloneValue(event);\n for (const listener of _classPrivateFieldGet2(_listeners, this)) listener(nextEvent);\n }\n startEvents() {\n if (_classPrivateFieldGet2(_eventLoop, this)) return;\n _classPrivateFieldSet2(_eventLoop, this, this.runEventsLoop());\n }\n async runEventsLoop() {\n while (!_classPrivateFieldGet2(_closed, this)) {\n const controller = new AbortController();\n _classPrivateFieldSet2(_streamAbortController, this, controller);\n try {\n const response = await this.request(granolaTransportPaths.events, {\n headers: { accept: \"text/event-stream\" },\n signal: controller.signal\n });\n await this.consumeEventStream(response);\n } catch {\n if (_classPrivateFieldGet2(_closed, this) || controller.signal.aborted) break;\n await new Promise((resolve) => {\n setTimeout(resolve, _classPrivateFieldGet2(_reconnectDelayMs, this));\n });\n }\n }\n }\n async consumeEventStream(response) {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"server did not provide an event stream\");\n const decoder = new TextDecoder();\n let buffer = \"\";\n while (!_classPrivateFieldGet2(_closed, this)) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n buffer = buffer.replaceAll(\"\\r\\n\", \"\\n\");\n while (true) {\n const boundary = buffer.indexOf(\"\\n\\n\");\n if (boundary < 0) break;\n const chunk = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n const event = parseSseEvent(chunk);\n if (event) this.emit(event);\n }\n }\n }\n};\nasync function createGranolaServerClient(serverUrl, options = {}) {\n return await GranolaServerClient.connect(serverUrl, options);\n}\n//#endregion\n//#region src/web/client-state.ts\nvar granolaWebWorkspaceStorageKey = \"granola-toolkit.web-workspace\";\nvar maxRecentMeetings = 6;\nvar maxSavedFilters = 6;\nfunction normaliseFilterValue(value) {\n const trimmed = value?.trim();\n return trimmed ? trimmed : void 0;\n}\nfunction normaliseFilters(filters) {\n const selectedFolderId = normaliseFilterValue(filters.selectedFolderId);\n return {\n search: normaliseFilterValue(filters.search),\n selectedFolderId,\n sort: normaliseFilterValue(filters.sort) ?? \"updated-desc\",\n updatedFrom: normaliseFilterValue(filters.updatedFrom),\n updatedTo: normaliseFilterValue(filters.updatedTo)\n };\n}\nfunction filtersKey(filters) {\n return JSON.stringify(normaliseFilters(filters));\n}\nfunction defaultWorkspacePreferences() {\n return {\n recentMeetings: [],\n savedFilters: []\n };\n}\nfunction parseWorkspacePreferences(raw) {\n if (!raw) return defaultWorkspacePreferences();\n try {\n const parsed = JSON.parse(raw);\n return {\n recentMeetings: Array.isArray(parsed?.recentMeetings) ? parsed.recentMeetings.map((entry) => ({\n folderId: normaliseFilterValue(entry?.folderId),\n id: normaliseFilterValue(entry?.id) || \"\",\n title: normaliseFilterValue(entry?.title) || \"\",\n updatedAt: normaliseFilterValue(entry?.updatedAt) || \"\"\n })).filter((entry) => entry.id && entry.title).slice(0, maxRecentMeetings) : [],\n savedFilters: Array.isArray(parsed?.savedFilters) ? parsed.savedFilters.map((preset) => ({\n filters: normaliseFilters(preset?.filters ?? {}),\n id: normaliseFilterValue(preset?.id) || \"\",\n label: normaliseFilterValue(preset?.label) || \"\"\n })).filter((preset) => preset.id && preset.label).slice(0, maxSavedFilters) : []\n };\n } catch {\n return defaultWorkspacePreferences();\n }\n}\nfunction serialiseWorkspacePreferences(preferences) {\n return JSON.stringify({\n recentMeetings: preferences.recentMeetings.slice(0, maxRecentMeetings),\n savedFilters: preferences.savedFilters.slice(0, maxSavedFilters)\n });\n}\nfunction hasActiveFilters(filters) {\n const normalised = normaliseFilters(filters);\n return Boolean(normalised.search || normalised.selectedFolderId || normalised.updatedFrom || normalised.updatedTo || normalised.sort !== \"updated-desc\");\n}\nfunction filterLabel(filters) {\n const summary = currentFilterSummary(filters);\n if (!summary) return \"Current workspace\";\n return summary;\n}\nfunction rememberRecentMeeting(preferences, meeting) {\n const nextEntry = {\n folderId: meeting.folders?.[0]?.id,\n id: meeting.id,\n title: meeting.title?.trim() || meeting.id,\n updatedAt: meeting.updatedAt\n };\n return {\n ...preferences,\n recentMeetings: [nextEntry, ...preferences.recentMeetings.filter((entry) => entry.id !== nextEntry.id)].slice(0, maxRecentMeetings)\n };\n}\nfunction saveWorkspaceFilter(preferences, filters, options = {}) {\n const nextFilters = normaliseFilters(filters);\n if (!hasActiveFilters(nextFilters)) return preferences;\n const key = filtersKey(nextFilters);\n const nextPreset = {\n filters: nextFilters,\n id: preferences.savedFilters.find((preset) => filtersKey(preset.filters) === key)?.id ?? options.idFactory?.() ?? `filter-${preferences.savedFilters.length + 1}`,\n label: filterLabel(filters)\n };\n return {\n ...preferences,\n savedFilters: [nextPreset, ...preferences.savedFilters.filter((preset) => preset.id !== nextPreset.id)].slice(0, maxSavedFilters)\n };\n}\nfunction removeWorkspaceFilter(preferences, id) {\n return {\n ...preferences,\n savedFilters: preferences.savedFilters.filter((preset) => preset.id !== id)\n };\n}\nfunction applyWorkspaceFilter(preset) {\n return {\n search: preset.filters.search ?? \"\",\n selectedFolderId: preset.filters.selectedFolderId ?? null,\n sort: preset.filters.sort ?? \"updated-desc\",\n updatedFrom: preset.filters.updatedFrom ?? \"\",\n updatedTo: preset.filters.updatedTo ?? \"\"\n };\n}\nfunction parseWorkspaceTab(value) {\n switch (value) {\n case \"metadata\":\n case \"raw\":\n case \"transcript\": return value;\n default: return \"notes\";\n }\n}\nfunction startupSelectionFromSearch(search) {\n const params = new URLSearchParams(search);\n return {\n folderId: params.get(\"folder\")?.trim() || \"\",\n meetingId: params.get(\"meeting\")?.trim() || \"\",\n workspaceTab: parseWorkspaceTab(params.get(\"tab\"))\n };\n}\nfunction buildBrowserUrlPath(currentHref, selection) {\n const url = new URL(currentHref);\n if (selection.selectedFolderId) url.searchParams.set(\"folder\", selection.selectedFolderId);\n else url.searchParams.delete(\"folder\");\n if (selection.selectedMeetingId) url.searchParams.set(\"meeting\", selection.selectedMeetingId);\n else url.searchParams.delete(\"meeting\");\n if (parseWorkspaceTab(selection.workspaceTab) !== \"notes\") url.searchParams.set(\"tab\", parseWorkspaceTab(selection.workspaceTab));\n else url.searchParams.delete(\"tab\");\n return `${url.pathname}${url.search}${url.hash}`;\n}\nfunction exportScopeLabel(scope) {\n return scope && scope.mode === \"folder\" ? `Folder: ${scope.folderName || scope.folderId}` : \"Scope: All meetings\";\n}\nfunction currentFilterSummary(filters) {\n const parts = [];\n if (filters.selectedFolderId) {\n const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);\n parts.push(`folder \"${folder ? folder.name : filters.selectedFolderId}\"`);\n }\n if (filters.search) parts.push(`search \"${filters.search}\"`);\n if (filters.updatedFrom) parts.push(`from ${filters.updatedFrom}`);\n if (filters.updatedTo) parts.push(`to ${filters.updatedTo}`);\n if (filters.sort && filters.sort !== \"updated-desc\") parts.push(filters.sort === \"updated-asc\" ? \"oldest first\" : filters.sort === \"title-asc\" ? \"title A-Z\" : \"title Z-A\");\n return parts.join(\", \");\n}\nfunction selectMeetingId(meetings, selectedMeetingId) {\n if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;\n return meetings[0]?.id ?? null;\n}\nfunction nextWorkspaceTab(currentTab, key) {\n const current = parseWorkspaceTab(currentTab);\n switch (key) {\n case \"1\": return \"notes\";\n case \"2\": return \"transcript\";\n case \"3\": return \"metadata\";\n case \"4\": return \"raw\";\n case \"]\":\n switch (current) {\n case \"notes\": return \"transcript\";\n case \"transcript\": return \"metadata\";\n case \"metadata\": return \"raw\";\n case \"raw\": return \"notes\";\n }\n break;\n case \"[\":\n switch (current) {\n case \"notes\": return \"raw\";\n case \"transcript\": return \"notes\";\n case \"metadata\": return \"transcript\";\n case \"raw\": return \"metadata\";\n }\n break;\n default: return;\n }\n}\nfunction describeSyncStatus(sync) {\n if (sync.running) return \"Sync running\";\n if (sync.lastError) return \"Sync needs attention\";\n if (sync.lastCompletedAt) {\n const suffix = sync.summary?.changedCount ? ` · ${sync.summary.changedCount} changes` : \"\";\n return `Synced ${sync.lastCompletedAt.slice(11, 19)}${suffix}`;\n }\n return \"Sync idle\";\n}\nfunction describeAuthStatus(auth) {\n if (!auth) return \"Waiting for auth\";\n if (auth.lastError) return \"Auth needs attention\";\n switch (auth.mode) {\n case \"api-key\": return \"API key active\";\n case \"stored-session\": return \"Stored session active\";\n default: return \"supabase.json active\";\n }\n}\n//#endregion\n//#region src/web-app/components.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$$1 = /* @__PURE__ */ template(`<section class=hero><h1>Granola Toolkit</h1><p>Browser workspace for folders, meetings, notes, transcripts, and export flows on top of one local server instance.</p><input class=search placeholder=\"Search meetings, ids, or tags\"><div class=\"field-row field-row--inline\"><label><span class=field-label>Sort</span><select class=select><option value=updated-desc>Newest first</option><option value=updated-asc>Oldest first</option><option value=title-asc>Title A-Z</option><option value=title-desc>Title Z-A</option></select></label><label><span class=field-label>Updated From</span><input class=field-input type=date></label></div><label class=field-row><span class=field-label>Updated To</span><input class=field-input type=date>`), _tmpl$2 = /* @__PURE__ */ template(`<section class=toolbar><div><p>Meetings are loaded from the shared server state so this view can stay aligned with the terminal UI and sync loop.</p></div><div class=toolbar-form><input class=field-input placeholder=\"Quick open by id or title\"><button class=\"button button--secondary\"type=button>Open`), _tmpl$3 = /* @__PURE__ */ template(`<div class=\"folder-empty folder-empty--error\">`), _tmpl$4 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Folders</h2><p>Pick a folder to scope the meeting browser, or stay on All meetings.</p></div><div class=folder-list>`), _tmpl$5 = /* @__PURE__ */ template(`<button class=folder-row type=button><span class=folder-row__title>All meetings</span><span class=folder-row__meta>Browse the full meeting list.`), _tmpl$6 = /* @__PURE__ */ template(`<div class=folder-empty>No folders found.`), _tmpl$7 = /* @__PURE__ */ template(`<button class=folder-row type=button><span class=folder-row__title></span><span class=folder-row__meta>`), _tmpl$8 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Saved Filters</h2><p>Keep the slices you revisit often close at hand.</p></div><div class=saved-filter-actions><button class=\"button button--secondary\"type=button>Save current filter</button></div><div class=saved-filter-list>`), _tmpl$9 = /* @__PURE__ */ template(`<div class=folder-empty>No saved filters yet.`), _tmpl$0 = /* @__PURE__ */ template(`<div class=saved-filter-card><button class=saved-filter-card__main type=button><span class=folder-row__title></span><span class=folder-row__meta></span></button><button class=saved-filter-card__remove type=button>Remove`), _tmpl$1 = /* @__PURE__ */ template(`<section class=folder-panel><div class=folder-panel__head><h2>Recent Meetings</h2><p>Jump back into the conversations you opened most recently.</p></div><div class=folder-list>`), _tmpl$10 = /* @__PURE__ */ template(`<div class=folder-empty>No recent meetings yet.`), _tmpl$11 = /* @__PURE__ */ template(`<div class=\"meeting-empty meeting-empty--error\">`), _tmpl$12 = /* @__PURE__ */ template(`<section class=meeting-list>`), _tmpl$13 = /* @__PURE__ */ template(`<div class=meeting-empty>`), _tmpl$14 = /* @__PURE__ */ template(`<button class=meeting-row type=button><span class=meeting-row__title></span><span class=meeting-row__meta></span><span class=meeting-row__meta>`), _tmpl$15 = /* @__PURE__ */ template(`<p>`), _tmpl$16 = /* @__PURE__ */ template(`<section class=detail-head><div><h2>Meeting Workspace</h2></div><div class=state-badge>`), _tmpl$17 = /* @__PURE__ */ template(`<p>Waiting for server state…`), _tmpl$18 = /* @__PURE__ */ template(`<div class=status-grid><div><span class=status-label>Surface</span><strong></strong></div><div><span class=status-label>View</span><strong></strong></div><div><span class=status-label>Auth</span><strong></strong></div><div><span class=status-label>Sync</span><strong></strong></div><div><span class=status-label>Documents</span><strong></strong></div><div><span class=status-label>Folders</span><strong></strong></div><div><span class=status-label>Cache</span><strong></strong></div><div><span class=status-label>Index</span><strong></strong></div><div><span class=status-label>Automation</span><strong>`), _tmpl$19 = /* @__PURE__ */ template(`<section class=security-panel><div class=security-panel__head><h3>Server Access</h3><p>This server is locked with a password. Unlock it to load meetings and live state.</p></div><div class=security-panel__body><input class=field-input placeholder=\"Server password\"type=password><div class=toolbar-actions><button class=\"button button--primary\"type=button>Unlock</button><button class=\"button button--secondary\"type=button>Lock`), _tmpl$20 = /* @__PURE__ */ template(`<section class=auth-panel><div class=auth-panel__head><h3>Auth Session</h3><p>Inspect, refresh, and switch between API key, stored session, and <code>supabase.json</code>.</p></div><div class=auth-panel__body>`), _tmpl$21 = /* @__PURE__ */ template(`<div class=auth-card><div class=auth-card__meta>Auth state unavailable.`), _tmpl$22 = /* @__PURE__ */ template(`<div class=auth-card__meta>Client ID: `), _tmpl$23 = /* @__PURE__ */ template(`<div class=auth-card__meta>Sign-in method: `), _tmpl$24 = /* @__PURE__ */ template(`<div class=auth-card__meta>supabase path: `), _tmpl$25 = /* @__PURE__ */ template(`<div class=\"auth-card__meta auth-card__error\">`), _tmpl$26 = /* @__PURE__ */ template(`<div class=auth-card><div class=status-grid><div><span class=status-label>Active</span><strong></strong></div><div><span class=status-label>API key</span><strong></strong></div><div><span class=status-label>Stored</span><strong></strong></div><div><span class=status-label>supabase.json</span><strong></strong></div><div><span class=status-label>Refresh</span><strong></strong></div></div><div class=auth-card__meta>Store a Granola Personal API key here or use <code>granola auth login --api-key &lt;token&gt;</code>.</div><div class=auth-card__actions><input class=input placeholder=grn_... type=password><button class=\"button button--secondary\"type=button>Save API key</button><button class=\"button button--secondary\"type=button>Import desktop session</button><button class=\"button button--secondary\"type=button>Refresh stored session</button><button class=\"button button--secondary\"type=button>Use API key</button><button class=\"button button--secondary\"type=button>Use stored session</button><button class=\"button button--secondary\"type=button>Use supabase.json</button><button class=\"button button--secondary\"type=button>Sign out`), _tmpl$27 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Recent Export Jobs</h3><p>Tracked across CLI and web runs.</p></div><div class=jobs-list>`), _tmpl$28 = /* @__PURE__ */ template(`<div class=job-empty>No export jobs yet.`), _tmpl$29 = /* @__PURE__ */ template(`<div class=job-card__meta>`), _tmpl$30 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Rerun`), _tmpl$31 = /* @__PURE__ */ template(`<article class=job-card><div class=job-card__head><div><div class=job-card__title> export</div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta>Started: </div><div class=job-card__meta>Output: </div><div class=job-card__actions>`), _tmpl$32 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Automation Runs</h3><p>Recent action runs triggered by durable sync events.</p></div><div class=jobs-list>`), _tmpl$33 = /* @__PURE__ */ template(`<div class=job-empty>No automation runs yet.`), _tmpl$34 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Approve`), _tmpl$35 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Reject`), _tmpl$36 = /* @__PURE__ */ template(`<article class=job-card><div class=job-card__head><div><div class=job-card__title></div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta></div><div class=job-card__actions>`), _tmpl$37 = /* @__PURE__ */ template(`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle`), _tmpl$38 = /* @__PURE__ */ template(`<button class=workspace-tab type=button>`), _tmpl$39 = /* @__PURE__ */ template(`<div class=empty>`), _tmpl$40 = /* @__PURE__ */ template(`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>`), _tmpl$41 = /* @__PURE__ */ template(`<div class=detail-body><div class=workspace-grid><aside class=\"detail-section workspace-sidebar\"><h2>Meeting Metadata</h2><pre class=detail-pre></pre></aside><section class=\"detail-section workspace-main\"><h2></h2><pre class=detail-pre>`);\nfunction authModeLabel(mode) {\n switch (mode) {\n case \"api-key\": return \"API key\";\n case \"stored-session\": return \"Stored session\";\n default: return \"supabase.json\";\n }\n}\nfunction metadataLines(record) {\n return [\n `Title: ${record.meeting.title || record.meeting.id}`,\n `Created: ${record.meeting.createdAt}`,\n `Updated: ${record.meeting.updatedAt}`,\n `Folders: ${record.meeting.folders.length ? record.meeting.folders.map((folder) => folder.name).join(\", \") : \"none\"}`,\n `Tags: ${record.meeting.tags.length ? record.meeting.tags.join(\", \") : \"none\"}`,\n `Transcript loaded: ${record.meeting.transcriptLoaded ? \"yes\" : \"no\"}`\n ].join(\"\\n\");\n}\nfunction workspaceBody(bundle, record, tab) {\n switch (tab) {\n case \"transcript\": return {\n body: record.transcriptText || \"(Transcript unavailable)\",\n title: \"Transcript\"\n };\n case \"metadata\": return {\n body: metadataLines(record),\n title: \"Metadata\"\n };\n case \"raw\": return {\n body: JSON.stringify(bundle || record, null, 2),\n title: \"Raw Bundle\"\n };\n default: return {\n body: record.noteMarkdown || \"(No notes available)\",\n title: \"Notes\"\n };\n }\n}\nfunction scopeLabel(scope) {\n return exportScopeLabel(scope);\n}\nfunction ToolbarFilters(props) {\n return [(() => {\n var _el$ = _tmpl$$1(), _el$4 = _el$.firstChild.nextSibling.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$8 = _el$6.firstChild.nextSibling, _el$1 = _el$6.nextSibling.firstChild.nextSibling, _el$12 = _el$5.nextSibling.firstChild.nextSibling;\n _el$4.$$input = (event) => {\n props.onSearchInput(event.currentTarget.value);\n };\n _el$8.addEventListener(\"change\", (event) => {\n props.onSortChange(event.currentTarget.value);\n });\n _el$1.addEventListener(\"change\", (event) => {\n props.onUpdatedFromChange(event.currentTarget.value);\n });\n _el$12.addEventListener(\"change\", (event) => {\n props.onUpdatedToChange(event.currentTarget.value);\n });\n createRenderEffect(() => _el$4.value = props.search);\n createRenderEffect(() => _el$8.value = props.sort);\n createRenderEffect(() => _el$1.value = props.updatedFrom);\n createRenderEffect(() => _el$12.value = props.updatedTo);\n return _el$;\n })(), (() => {\n var _el$13 = _tmpl$2(), _el$16 = _el$13.firstChild.nextSibling.firstChild, _el$17 = _el$16.nextSibling;\n _el$16.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onQuickOpen();\n }\n };\n _el$16.$$input = (event) => {\n props.onQuickOpenInput(event.currentTarget.value);\n };\n addEventListener(_el$17, \"click\", props.onQuickOpen, true);\n createRenderEffect(() => _el$16.value = props.quickOpen);\n return _el$13;\n })()];\n}\nfunction FolderList(props) {\n return (() => {\n var _el$18 = _tmpl$4(), _el$20 = _el$18.firstChild.nextSibling;\n insert(_el$20, createComponent(Show, {\n get fallback() {\n return [\n (() => {\n var _el$22 = _tmpl$5();\n _el$22.$$click = () => {\n props.onSelect(null);\n };\n createRenderEffect(() => setAttribute(_el$22, \"data-selected\", !props.selectedFolderId ? \"true\" : void 0));\n return _el$22;\n })(),\n createComponent(For, {\n get each() {\n return props.folders;\n },\n children: (folder) => (() => {\n var _el$24 = _tmpl$7(), _el$25 = _el$24.firstChild, _el$26 = _el$25.nextSibling;\n _el$24.$$click = () => {\n props.onSelect(folder.id);\n };\n insert(_el$25, () => (folder.isFavourite ? \"★ \" : \"\") + (folder.name || folder.id));\n insert(_el$26, () => `${folder.documentCount} meetings`);\n createRenderEffect(() => setAttribute(_el$24, \"data-selected\", folder.id === props.selectedFolderId ? \"true\" : void 0));\n return _el$24;\n })()\n }),\n createComponent(Show, {\n get when() {\n return props.folders.length === 0;\n },\n get children() {\n return _tmpl$6();\n }\n })\n ];\n },\n get when() {\n return !props.error;\n },\n get children() {\n var _el$21 = _tmpl$3();\n insert(_el$21, () => props.error);\n return _el$21;\n }\n }));\n return _el$18;\n })();\n}\nfunction SavedFiltersPanel(props) {\n const canSaveCurrent = () => hasActiveFilters({\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n sort: props.sort,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$27 = _tmpl$8(), _el$29 = _el$27.firstChild.nextSibling, _el$30 = _el$29.firstChild, _el$31 = _el$29.nextSibling;\n _el$30.$$click = () => {\n props.onSaveCurrent();\n };\n insert(_el$31, createComponent(Show, {\n get when() {\n return props.savedFilters.length > 0;\n },\n get fallback() {\n return _tmpl$9();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.savedFilters;\n },\n children: (preset) => (() => {\n var _el$33 = _tmpl$0(), _el$34 = _el$33.firstChild, _el$35 = _el$34.firstChild, _el$36 = _el$35.nextSibling, _el$37 = _el$34.nextSibling;\n _el$34.$$click = () => {\n props.onApply(preset);\n };\n insert(_el$35, () => preset.label);\n insert(_el$36, () => currentFilterSummary({\n folders: props.folders,\n ...preset.filters\n }) || \"Saved workspace scope\");\n _el$37.$$click = () => {\n props.onRemove(preset.id);\n };\n return _el$33;\n })()\n });\n }\n }));\n createRenderEffect(() => _el$30.disabled = !canSaveCurrent());\n return _el$27;\n })();\n}\nfunction RecentMeetingsPanel(props) {\n return (() => {\n var _el$38 = _tmpl$1(), _el$40 = _el$38.firstChild.nextSibling;\n insert(_el$40, createComponent(Show, {\n get when() {\n return props.recentMeetings.length > 0;\n },\n get fallback() {\n return _tmpl$10();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.recentMeetings;\n },\n children: (meeting) => (() => {\n var _el$42 = _tmpl$7(), _el$43 = _el$42.firstChild, _el$44 = _el$43.nextSibling;\n _el$42.$$click = () => {\n props.onOpen(meeting);\n };\n insert(_el$43, () => meeting.title);\n insert(_el$44, () => meeting.updatedAt.slice(0, 10));\n return _el$42;\n })()\n });\n }\n }));\n return _el$38;\n })();\n}\nfunction MeetingList(props) {\n const summary = () => currentFilterSummary({\n folders: props.folders,\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$45 = _tmpl$12();\n insert(_el$45, createComponent(Show, {\n get fallback() {\n return createComponent(Show, {\n get fallback() {\n return (() => {\n var _el$47 = _tmpl$13();\n insert(_el$47, (() => {\n var _c$ = memo(() => !!summary());\n return () => _c$() ? `No meetings match ${summary()}.` : props.emptyHint || \"No meetings yet. Try Sync now.\";\n })());\n return _el$47;\n })();\n },\n get when() {\n return props.meetings.length > 0;\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.meetings;\n },\n children: (meeting) => (() => {\n var _el$48 = _tmpl$14(), _el$49 = _el$48.firstChild, _el$50 = _el$49.nextSibling, _el$51 = _el$50.nextSibling;\n _el$48.$$click = () => {\n props.onSelect(meeting.id);\n };\n insert(_el$49, () => meeting.title || meeting.id);\n insert(_el$50, (() => {\n var _c$2 = memo(() => !!meeting.tags.length);\n return () => _c$2() ? meeting.tags.map((tag) => `#${tag}`).join(\" \") : \"untagged\";\n })());\n insert(_el$51, (() => {\n var _c$3 = memo(() => !!meeting.updatedAt);\n return () => _c$3() ? meeting.updatedAt.slice(0, 10) : \"unknown\";\n })());\n createRenderEffect(() => setAttribute(_el$48, \"data-selected\", meeting.id === props.selectedMeetingId ? \"true\" : void 0));\n return _el$48;\n })()\n });\n }\n });\n },\n get when() {\n return props.error;\n },\n get children() {\n var _el$46 = _tmpl$11();\n insert(_el$46, () => props.error);\n return _el$46;\n }\n }));\n return _el$45;\n })();\n}\nfunction AppStatePanel(props) {\n const syncStatus = () => describeSyncStatus(props.appState?.sync ?? {});\n const authStatus = () => describeAuthStatus(props.appState?.auth);\n return (() => {\n var _el$52 = _tmpl$16(), _el$53 = _el$52.firstChild;\n _el$53.firstChild;\n var _el$56 = _el$53.nextSibling;\n insert(_el$53, createComponent(Show, {\n get fallback() {\n return _tmpl$17();\n },\n get when() {\n return props.appState;\n },\n children: (appState) => (() => {\n var _el$58 = _tmpl$18(), _el$59 = _el$58.firstChild, _el$61 = _el$59.firstChild.nextSibling, _el$62 = _el$59.nextSibling, _el$64 = _el$62.firstChild.nextSibling, _el$65 = _el$62.nextSibling, _el$67 = _el$65.firstChild.nextSibling, _el$68 = _el$65.nextSibling, _el$70 = _el$68.firstChild.nextSibling, _el$71 = _el$68.nextSibling, _el$73 = _el$71.firstChild.nextSibling, _el$74 = _el$71.nextSibling, _el$76 = _el$74.firstChild.nextSibling, _el$77 = _el$74.nextSibling, _el$79 = _el$77.firstChild.nextSibling, _el$80 = _el$77.nextSibling, _el$82 = _el$80.firstChild.nextSibling, _el$85 = _el$80.nextSibling.firstChild.nextSibling;\n insert(_el$61, () => appState().ui.surface);\n insert(_el$64, () => appState().ui.view);\n insert(_el$67, authStatus);\n insert(_el$70, syncStatus);\n insert(_el$73, (() => {\n var _c$4 = memo(() => !!appState().documents.loaded);\n return () => _c$4() ? String(appState().documents.count) : \"not loaded\";\n })());\n insert(_el$76, (() => {\n var _c$5 = memo(() => !!appState().folders.loaded);\n return () => _c$5() ? String(appState().folders.count) : \"not loaded\";\n })());\n insert(_el$79, (() => {\n var _c$6 = memo(() => !!appState().cache.loaded);\n return () => _c$6() ? `${appState().cache.transcriptCount} transcript sets` : appState().cache.configured ? \"configured\" : \"not configured\";\n })());\n insert(_el$82, (() => {\n var _c$7 = memo(() => !!appState().index.loaded);\n return () => _c$7() ? `${appState().index.meetingCount} meetings` : appState().index.available ? \"available\" : \"not built\";\n })());\n insert(_el$85, () => `${appState().automation.runCount} runs / ${appState().automation.pendingRunCount} pending`);\n return _el$58;\n })()\n }), null);\n insert(_el$53, createComponent(Show, {\n get when() {\n return props.appState?.auth.lastError;\n },\n get children() {\n var _el$55 = _tmpl$15();\n insert(_el$55, () => props.appState?.auth.lastError);\n return _el$55;\n }\n }), null);\n insert(_el$56, () => props.statusLabel);\n createRenderEffect(() => setAttribute(_el$56, \"data-tone\", props.statusTone));\n return _el$52;\n })();\n}\nfunction SecurityPanel(props) {\n return createComponent(Show, {\n get when() {\n return props.visible;\n },\n get children() {\n var _el$86 = _tmpl$19(), _el$89 = _el$86.firstChild.nextSibling.firstChild, _el$91 = _el$89.nextSibling.firstChild, _el$92 = _el$91.nextSibling;\n _el$89.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onUnlock();\n }\n };\n _el$89.$$input = (event) => {\n props.onPasswordChange(event.currentTarget.value);\n };\n addEventListener(_el$91, \"click\", props.onUnlock, true);\n addEventListener(_el$92, \"click\", props.onLock, true);\n createRenderEffect(() => _el$89.value = props.password);\n return _el$86;\n }\n });\n}\nfunction AuthPanel(props) {\n return (() => {\n var _el$93 = _tmpl$20(), _el$95 = _el$93.firstChild.nextSibling;\n insert(_el$95, createComponent(Show, {\n get fallback() {\n return _tmpl$21();\n },\n get when() {\n return props.auth;\n },\n children: (auth) => (() => {\n var _el$97 = _tmpl$26(), _el$98 = _el$97.firstChild, _el$99 = _el$98.firstChild, _el$101 = _el$99.firstChild.nextSibling, _el$102 = _el$99.nextSibling, _el$104 = _el$102.firstChild.nextSibling, _el$105 = _el$102.nextSibling, _el$107 = _el$105.firstChild.nextSibling, _el$108 = _el$105.nextSibling, _el$110 = _el$108.firstChild.nextSibling, _el$113 = _el$108.nextSibling.firstChild.nextSibling, _el$121 = _el$98.nextSibling, _el$123 = _el$121.nextSibling.firstChild, _el$124 = _el$123.nextSibling, _el$125 = _el$124.nextSibling, _el$126 = _el$125.nextSibling, _el$127 = _el$126.nextSibling, _el$128 = _el$127.nextSibling, _el$129 = _el$128.nextSibling, _el$130 = _el$129.nextSibling;\n insert(_el$101, () => authModeLabel(auth().mode));\n insert(_el$104, () => auth().apiKeyAvailable ? \"available\" : \"missing\");\n insert(_el$107, () => auth().storedSessionAvailable ? \"available\" : \"missing\");\n insert(_el$110, () => auth().supabaseAvailable ? \"available\" : \"missing\");\n insert(_el$113, () => auth().refreshAvailable ? \"available\" : \"missing\");\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().clientId;\n },\n get children() {\n var _el$114 = _tmpl$22();\n _el$114.firstChild;\n insert(_el$114, () => auth().clientId, null);\n return _el$114;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().signInMethod;\n },\n get children() {\n var _el$116 = _tmpl$23();\n _el$116.firstChild;\n insert(_el$116, () => auth().signInMethod, null);\n return _el$116;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().supabasePath;\n },\n get children() {\n var _el$118 = _tmpl$24();\n _el$118.firstChild;\n insert(_el$118, () => auth().supabasePath, null);\n return _el$118;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().lastError;\n },\n get children() {\n var _el$120 = _tmpl$25();\n insert(_el$120, () => auth().lastError);\n return _el$120;\n }\n }), _el$121);\n _el$123.$$input = (event) => {\n props.onApiKeyDraftChange(event.currentTarget.value);\n };\n addEventListener(_el$124, \"click\", props.onSaveApiKey, true);\n addEventListener(_el$125, \"click\", props.onImportDesktopSession, true);\n addEventListener(_el$126, \"click\", props.onRefresh, true);\n _el$127.$$click = () => {\n props.onSwitchMode(\"api-key\");\n };\n _el$128.$$click = () => {\n props.onSwitchMode(\"stored-session\");\n };\n _el$129.$$click = () => {\n props.onSwitchMode(\"supabase-file\");\n };\n addEventListener(_el$130, \"click\", props.onLogout, true);\n createRenderEffect((_p$) => {\n var _v$ = !auth().supabaseAvailable, _v$2 = !auth().storedSessionAvailable || !auth().refreshAvailable, _v$3 = !auth().apiKeyAvailable || auth().mode === \"api-key\", _v$4 = !auth().storedSessionAvailable || auth().mode === \"stored-session\", _v$5 = !auth().supabaseAvailable || auth().mode === \"supabase-file\", _v$6 = !auth().apiKeyAvailable && !auth().storedSessionAvailable;\n _v$ !== _p$.e && (_el$125.disabled = _p$.e = _v$);\n _v$2 !== _p$.t && (_el$126.disabled = _p$.t = _v$2);\n _v$3 !== _p$.a && (_el$127.disabled = _p$.a = _v$3);\n _v$4 !== _p$.o && (_el$128.disabled = _p$.o = _v$4);\n _v$5 !== _p$.i && (_el$129.disabled = _p$.i = _v$5);\n _v$6 !== _p$.n && (_el$130.disabled = _p$.n = _v$6);\n return _p$;\n }, {\n e: void 0,\n t: void 0,\n a: void 0,\n o: void 0,\n i: void 0,\n n: void 0\n });\n createRenderEffect(() => _el$123.value = props.apiKeyDraft);\n return _el$97;\n })()\n }));\n return _el$93;\n })();\n}\nfunction ExportJobsPanel(props) {\n return (() => {\n var _el$131 = _tmpl$27(), _el$133 = _el$131.firstChild.nextSibling;\n insert(_el$133, createComponent(Show, {\n get when() {\n return props.jobs.length > 0;\n },\n get fallback() {\n return _tmpl$28();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.jobs.slice(0, 6);\n },\n children: (job) => (() => {\n var _el$135 = _tmpl$31(), _el$136 = _el$135.firstChild, _el$137 = _el$136.firstChild, _el$138 = _el$137.firstChild, _el$139 = _el$138.firstChild, _el$140 = _el$138.nextSibling, _el$141 = _el$137.nextSibling, _el$142 = _el$136.nextSibling, _el$143 = _el$142.nextSibling;\n _el$143.firstChild;\n var _el$145 = _el$143.nextSibling;\n _el$145.firstChild;\n var _el$148 = _el$145.nextSibling;\n insert(_el$138, () => job.kind, _el$139);\n insert(_el$140, () => job.id);\n insert(_el$141, () => job.status);\n insert(_el$142, () => `Format: ${job.format} • ${scopeLabel(job.scope)} • ${job.itemCount > 0 ? `${job.completedCount}/${job.itemCount} items` : \"0 items\"} • Written: ${job.written}`);\n insert(_el$143, () => job.startedAt.slice(0, 19), null);\n insert(_el$145, () => job.outputDir, null);\n insert(_el$135, createComponent(Show, {\n get when() {\n return job.error;\n },\n get children() {\n var _el$147 = _tmpl$29();\n insert(_el$147, () => job.error);\n return _el$147;\n }\n }), _el$148);\n insert(_el$148, createComponent(Show, {\n get when() {\n return job.status !== \"running\";\n },\n get children() {\n var _el$149 = _tmpl$30();\n _el$149.$$click = () => {\n props.onRerun(job.id);\n };\n return _el$149;\n }\n }));\n createRenderEffect(() => setAttribute(_el$141, \"data-status\", job.status));\n return _el$135;\n })()\n });\n }\n }));\n return _el$131;\n })();\n}\nfunction AutomationRunsPanel(props) {\n return (() => {\n var _el$150 = _tmpl$32(), _el$152 = _el$150.firstChild.nextSibling;\n insert(_el$152, createComponent(Show, {\n get when() {\n return props.runs.length > 0;\n },\n get fallback() {\n return _tmpl$33();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.runs.slice(0, 6);\n },\n children: (run) => (() => {\n var _el$154 = _tmpl$36(), _el$155 = _el$154.firstChild, _el$156 = _el$155.firstChild, _el$157 = _el$156.firstChild, _el$158 = _el$157.nextSibling, _el$159 = _el$156.nextSibling, _el$160 = _el$155.nextSibling, _el$161 = _el$160.nextSibling, _el$165 = _el$161.nextSibling;\n insert(_el$157, () => run.actionName);\n insert(_el$158, () => `${run.ruleName} • ${run.id}`);\n insert(_el$159, () => run.status);\n insert(_el$160, () => `${run.title} • ${run.eventKind}`);\n insert(_el$161, () => `Started: ${run.startedAt.slice(0, 19)}`);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.prompt;\n },\n get children() {\n var _el$162 = _tmpl$29();\n insert(_el$162, () => run.prompt);\n return _el$162;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.result;\n },\n get children() {\n var _el$163 = _tmpl$29();\n insert(_el$163, () => run.result);\n return _el$163;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.error;\n },\n get children() {\n var _el$164 = _tmpl$29();\n insert(_el$164, () => run.error);\n return _el$164;\n }\n }), _el$165);\n insert(_el$165, createComponent(Show, {\n get when() {\n return run.status === \"pending\";\n },\n get children() {\n return [(() => {\n var _el$166 = _tmpl$34();\n _el$166.$$click = () => {\n props.onApprove(run.id);\n };\n return _el$166;\n })(), (() => {\n var _el$167 = _tmpl$35();\n _el$167.$$click = () => {\n props.onReject(run.id);\n };\n return _el$167;\n })()];\n }\n }));\n createRenderEffect(() => setAttribute(_el$159, \"data-status\", run.status));\n return _el$154;\n })()\n });\n }\n }));\n return _el$150;\n })();\n}\nfunction Workspace(props) {\n const parsedTab = () => parseWorkspaceTab(props.tab);\n const details = () => {\n if (!props.selectedMeeting) return null;\n return workspaceBody(props.bundle, props.selectedMeeting, parsedTab());\n };\n return [(() => {\n var _el$168 = _tmpl$37(), _el$169 = _el$168.firstChild;\n insert(_el$168, createComponent(For, {\n each: [\n \"notes\",\n \"transcript\",\n \"metadata\",\n \"raw\"\n ],\n children: (tab) => (() => {\n var _el$170 = _tmpl$38();\n _el$170.$$click = () => {\n props.onSelectTab(tab);\n };\n insert(_el$170, tab === \"notes\" ? \"Notes\" : tab === \"transcript\" ? \"Transcript\" : tab === \"metadata\" ? \"Metadata\" : \"Raw\");\n createRenderEffect(() => setAttribute(_el$170, \"data-selected\", parsedTab() === tab ? \"true\" : void 0));\n return _el$170;\n })()\n }), _el$169);\n return _el$168;\n })(), createComponent(Show, {\n get when() {\n return props.selectedMeeting;\n },\n get fallback() {\n return (() => {\n var _el$171 = _tmpl$39();\n insert(_el$171, () => props.detailError || \"Select a meeting to inspect its notes and transcript.\");\n return _el$171;\n })();\n },\n children: (meeting) => [(() => {\n var _el$172 = _tmpl$40(), _el$173 = _el$172.firstChild, _el$174 = _el$173.nextSibling, _el$175 = _el$174.nextSibling;\n insert(_el$173, () => `ID: ${meeting().meeting.id}`);\n insert(_el$174, () => `Source: ${meeting().meeting.noteContentSource}`);\n insert(_el$175, () => `Transcript: ${meeting().meeting.transcriptSegmentCount} segments`);\n return _el$172;\n })(), createComponent(Show, {\n get when() {\n return !props.detailError;\n },\n get fallback() {\n return (() => {\n var _el$184 = _tmpl$39();\n insert(_el$184, () => props.detailError);\n return _el$184;\n })();\n },\n get children() {\n var _el$176 = _tmpl$41(), _el$178 = _el$176.firstChild.firstChild, _el$180 = _el$178.firstChild.nextSibling, _el$182 = _el$178.nextSibling.firstChild, _el$183 = _el$182.nextSibling;\n insert(_el$180, () => metadataLines(meeting()));\n insert(_el$182, () => details()?.title);\n insert(_el$183, () => details()?.body);\n return _el$176;\n }\n })]\n })];\n}\ndelegateEvents([\n \"input\",\n \"keydown\",\n \"click\"\n]);\n//#endregion\n//#region src/web-app/App.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$ = /* @__PURE__ */ template(`<div class=shell><aside class=\"pane sidebar\"></aside><main class=\"pane detail\"><section class=toolbar><div class=toolbar-actions><button class=\"button button--primary\"type=button>Sync now</button><button class=\"button button--secondary\"type=button>Clear Filters</button><button class=\"button button--secondary\"type=button>Export Notes</button><button class=\"button button--secondary\"type=button>Export Transcripts</button></div><p>Solid-powered web workspace on top of the same local server, sync loop, and shared app contracts.`);\nfunction browserConfig() {\n return { passwordRequired: Boolean(window.__GRANOLA_SERVER__?.passwordRequired) };\n}\nasync function requestJson(path, init) {\n const response = await fetch(path, init);\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const error = typeof payload.error === \"string\" && payload.error.trim() ? payload.error : response.statusText || \"Request failed\";\n throw new Error(error);\n }\n return payload;\n}\nfunction App() {\n const startup = startupSelectionFromSearch(window.location.search);\n const initialPreferences = parseWorkspacePreferences(window.localStorage.getItem(granolaWebWorkspaceStorageKey));\n const [state, setState] = createStore({\n apiKeyDraft: \"\",\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n listError: \"\",\n meetingSource: \"live\",\n meetings: [],\n quickOpen: \"\",\n recentMeetings: initialPreferences.recentMeetings,\n savedFilters: initialPreferences.savedFilters,\n search: \"\",\n selectedFolderId: startup.folderId || null,\n selectedMeetingBundle: null,\n selectedMeetingId: startup.meetingId || null,\n selectedMeeting: null,\n serverLocked: browserConfig().passwordRequired,\n serverPassword: \"\",\n sort: \"updated-desc\",\n statusLabel: browserConfig().passwordRequired ? \"Server locked\" : \"Connecting…\",\n statusTone: browserConfig().passwordRequired ? \"error\" : \"idle\",\n updatedFrom: \"\",\n updatedTo: \"\",\n workspaceTab: parseWorkspaceTab(startup.workspaceTab)\n });\n let client = null;\n let unsubscribe;\n const setStatus = (label, tone = \"idle\") => {\n setState({\n statusLabel: label,\n statusTone: tone\n });\n };\n const updatePreferences = (updater) => {\n const next = updater({\n recentMeetings: state.recentMeetings,\n savedFilters: state.savedFilters\n });\n window.localStorage.setItem(granolaWebWorkspaceStorageKey, serialiseWorkspacePreferences(next));\n setState(\"recentMeetings\", next.recentMeetings);\n setState(\"savedFilters\", next.savedFilters);\n };\n const mergeAuthState = async (authState) => {\n if (!client) return;\n const nextState = client.getState();\n if (authState) {\n setState(\"appState\", {\n ...nextState,\n auth: authState\n });\n return;\n }\n try {\n setState(\"appState\", {\n ...nextState,\n auth: await client.inspectAuth()\n });\n } catch {\n setState(\"appState\", nextState);\n }\n };\n const detachClient = async () => {\n unsubscribe?.();\n unsubscribe = void 0;\n if (client) {\n await client.close().catch(() => void 0);\n client = null;\n }\n };\n const attachClient = async () => {\n await detachClient();\n client = await createGranolaServerClient(window.location.origin);\n setState(\"appState\", client.getState());\n unsubscribe = client.subscribe((event) => {\n setState(\"appState\", event.state);\n });\n await mergeAuthState();\n };\n const loadFolders = async (refresh = false) => {\n if (!client) return;\n try {\n setState(\"folderError\", \"\");\n const result = await client.listFolders({\n forceRefresh: refresh,\n limit: 100\n });\n setState(\"folders\", result.folders);\n if (state.selectedFolderId && !result.folders.some((folder) => folder.id === state.selectedFolderId)) setState(\"selectedFolderId\", null);\n } catch (error) {\n setState(\"folderError\", error instanceof Error ? error.message : String(error));\n setState(\"folders\", []);\n setState(\"selectedFolderId\", null);\n }\n };\n const loadAutomationRuns = async () => {\n if (!client) return;\n try {\n setState(\"automationRuns\", (await client.listAutomationRuns({ limit: 20 })).runs);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadMeeting = async (meetingId) => {\n if (!client) return;\n setState(\"selectedMeetingId\", meetingId);\n try {\n setState(\"detailError\", \"\");\n const bundle = await client.getMeeting(meetingId);\n setState(\"selectedMeetingBundle\", bundle);\n setState(\"selectedMeeting\", bundle.meeting);\n updatePreferences((preferences) => rememberRecentMeeting(preferences, bundle.meeting.meeting));\n } catch (error) {\n setState(\"selectedMeetingBundle\", null);\n setState(\"selectedMeeting\", null);\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadMeetings = async (options = {}) => {\n if (!client) return;\n try {\n setState(\"listError\", \"\");\n const result = await client.listMeetings({\n folderId: state.selectedFolderId || void 0,\n forceRefresh: options.refresh,\n limit: 100,\n search: state.search || void 0,\n sort: state.sort,\n updatedFrom: state.updatedFrom || void 0,\n updatedTo: state.updatedTo || void 0\n });\n const preferredMeetingId = options.preferredMeetingId ?? state.selectedMeetingId;\n const nextMeetingId = selectMeetingId(result.meetings, preferredMeetingId);\n setState(\"meetings\", result.meetings);\n setState(\"meetingSource\", result.source);\n setState(\"selectedMeetingId\", nextMeetingId);\n if (nextMeetingId) await loadMeeting(nextMeetingId);\n else {\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", \"\");\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n setState(\"listError\", message);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", message);\n }\n };\n const refreshAll = async (forceRefresh = false) => {\n if (!client) await attachClient();\n setStatus(forceRefresh ? \"Syncing…\" : \"Refreshing…\", \"busy\");\n if (forceRefresh) await client?.sync({\n forceRefresh: true,\n foreground: true\n });\n await Promise.all([\n loadFolders(forceRefresh),\n loadAutomationRuns(),\n mergeAuthState()\n ]);\n await loadMeetings({ refresh: forceRefresh });\n setState(\"serverLocked\", false);\n setStatus(forceRefresh ? \"Sync complete\" : state.meetingSource === \"index\" ? \"Loaded from index\" : \"Connected\", \"ok\");\n };\n const connectAndRefresh = async (forceRefresh = false) => {\n try {\n await refreshAll(forceRefresh);\n } catch (error) {\n setStatus(\"Connection failed\", \"error\");\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const quickOpenMeeting = async () => {\n if (!client) return;\n const query = state.quickOpen.trim();\n if (!query) {\n setStatus(\"Enter a title or id\", \"error\");\n return;\n }\n setStatus(\"Opening meeting…\", \"busy\");\n try {\n const bundle = await client.findMeeting(query);\n setState(\"selectedFolderId\", bundle.meeting.meeting.folders[0]?.id || null);\n setState(\"search\", \"\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n await loadMeetings({ preferredMeetingId: bundle.document.id });\n setStatus(\"Connected\", \"ok\");\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Quick open failed\", \"error\");\n }\n };\n const saveApiKey = async () => {\n if (!client) return;\n if (!state.apiKeyDraft.trim()) {\n setStatus(\"Enter a Granola API key\", \"error\");\n return;\n }\n setStatus(\"Saving API key…\", \"busy\");\n try {\n const auth = await client.loginAuth({ apiKey: state.apiKeyDraft.trim() });\n setState(\"apiKeyDraft\", \"\");\n await mergeAuthState(auth);\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"API key save failed\", \"error\");\n }\n };\n const importDesktopSession = async () => {\n if (!client) return;\n setStatus(\"Importing desktop session…\", \"busy\");\n try {\n await mergeAuthState(await client.loginAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Auth import failed\", \"error\");\n }\n };\n const refreshAuth = async () => {\n if (!client) return;\n setStatus(\"Refreshing session…\", \"busy\");\n try {\n await mergeAuthState(await client.refreshAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Refresh failed\", \"error\");\n }\n };\n const switchAuthMode = async (mode) => {\n if (!client) return;\n setStatus(\"Switching auth source…\", \"busy\");\n try {\n await mergeAuthState(await client.switchAuthMode(mode));\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Switch failed\", \"error\");\n }\n };\n const logout = async () => {\n if (!client) return;\n setStatus(\"Signing out…\", \"busy\");\n try {\n await mergeAuthState(await client.logoutAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Sign out failed\", \"error\");\n }\n };\n const exportNotes = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder notes…\" : \"Exporting notes…\", \"busy\");\n try {\n await client.exportNotes(\"markdown\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const clearFilters = async () => {\n setState(\"search\", \"\");\n setState(\"sort\", \"updated-desc\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n setState(\"selectedFolderId\", null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(\"Filters cleared\", \"ok\");\n };\n const saveCurrentFilter = () => {\n updatePreferences((preferences) => saveWorkspaceFilter(preferences, {\n folders: state.folders,\n search: state.search,\n selectedFolderId: state.selectedFolderId,\n sort: state.sort,\n updatedFrom: state.updatedFrom,\n updatedTo: state.updatedTo\n }, { idFactory: () => `filter-${Date.now()}` }));\n setStatus(\"Saved filter\", \"ok\");\n };\n const applySavedFilterPreset = async (id) => {\n const preset = state.savedFilters.find((candidate) => candidate.id === id);\n if (!preset) return;\n const nextFilters = applyWorkspaceFilter(preset);\n setState(\"search\", nextFilters.search);\n setState(\"selectedFolderId\", nextFilters.selectedFolderId);\n setState(\"sort\", nextFilters.sort);\n setState(\"updatedFrom\", nextFilters.updatedFrom);\n setState(\"updatedTo\", nextFilters.updatedTo);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(`Applied ${preset.label}`, \"ok\");\n };\n const removeSavedFilterPreset = (id) => {\n updatePreferences((preferences) => removeWorkspaceFilter(preferences, id));\n setStatus(\"Removed saved filter\", \"ok\");\n };\n const openRecentMeeting = async (meetingId, folderId) => {\n if (folderId !== void 0) {\n setState(\"selectedFolderId\", folderId || null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings({ preferredMeetingId: meetingId });\n return;\n }\n await loadMeeting(meetingId);\n };\n const meetingEmptyHint = () => {\n if (!state.appState) return \"Connect to the local server to load meetings.\";\n if (state.appState.auth.lastError) return \"Resolve auth first, then sync again.\";\n if (!state.appState.documents.loaded && !state.appState.sync.lastCompletedAt) return \"Run Sync now to populate your local meeting index.\";\n return \"Try a different folder or filter, or sync again.\";\n };\n const exportTranscripts = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder transcripts…\" : \"Exporting transcripts…\", \"busy\");\n try {\n await client.exportTranscripts(\"text\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const rerunJob = async (jobId) => {\n if (!client) return;\n setStatus(\"Rerunning export…\", \"busy\");\n try {\n await client.rerunExportJob(jobId);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Rerun failed\", \"error\");\n }\n };\n const resolveAutomationRun = async (id, decision) => {\n if (!client) return;\n setStatus(decision === \"approve\" ? \"Approving automation…\" : \"Rejecting automation…\", \"busy\");\n try {\n await client.resolveAutomationRun(id, decision);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Automation decision failed\", \"error\");\n }\n };\n const unlockServer = async () => {\n if (!state.serverPassword.trim()) {\n setStatus(\"Enter the server password\", \"error\");\n return;\n }\n setStatus(\"Unlocking server…\", \"busy\");\n try {\n await requestJson(\"/auth/unlock\", {\n body: JSON.stringify({ password: state.serverPassword }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n setState(\"serverPassword\", \"\");\n setState(\"serverLocked\", false);\n await connectAndRefresh(true);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unlock failed\", \"error\");\n }\n };\n const lockServer = async () => {\n try {\n await requestJson(\"/auth/lock\", { method: \"POST\" });\n } catch {}\n await detachClient();\n setState({\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n listError: \"\",\n meetings: [],\n selectedFolderId: null,\n selectedMeeting: null,\n selectedMeetingBundle: null,\n selectedMeetingId: null,\n serverLocked: true,\n serverPassword: \"\"\n });\n setStatus(\"Server locked\", \"error\");\n };\n createEffect(() => {\n const nextPath = buildBrowserUrlPath(window.location.href, {\n selectedFolderId: state.selectedFolderId,\n selectedMeetingId: state.selectedMeetingId,\n workspaceTab: state.workspaceTab\n });\n if (nextPath !== `${window.location.pathname}${window.location.search}${window.location.hash}`) history.replaceState(null, \"\", nextPath);\n });\n createEffect(() => {\n if (!state.appState?.automation.loaded || !client) return;\n loadAutomationRuns();\n });\n onMount(() => {\n const onKeyDown = (event) => {\n const target = event.target;\n if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return;\n const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);\n if (nextTab) setState(\"workspaceTab\", nextTab);\n };\n document.addEventListener(\"keydown\", onKeyDown);\n onCleanup(() => {\n document.removeEventListener(\"keydown\", onKeyDown);\n });\n if (!state.serverLocked) connectAndRefresh();\n });\n onCleanup(() => {\n detachClient();\n });\n return (() => {\n var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$6 = _el$4.firstChild.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling;\n insert(_el$2, createComponent(ToolbarFilters, {\n onQuickOpen: () => {\n quickOpenMeeting();\n },\n onQuickOpenInput: (value) => {\n setState(\"quickOpen\", value);\n },\n onSearchInput: (value) => {\n setState(\"search\", value.trim());\n loadMeetings();\n },\n onSortChange: (value) => {\n setState(\"sort\", value);\n loadMeetings();\n },\n onUpdatedFromChange: (value) => {\n setState(\"updatedFrom\", value);\n loadMeetings();\n },\n onUpdatedToChange: (value) => {\n setState(\"updatedTo\", value);\n loadMeetings();\n },\n get quickOpen() {\n return state.quickOpen;\n },\n get search() {\n return state.search;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(SavedFiltersPanel, {\n get folders() {\n return state.folders;\n },\n onApply: (preset) => {\n applySavedFilterPreset(preset.id);\n },\n onRemove: removeSavedFilterPreset,\n onSaveCurrent: saveCurrentFilter,\n get savedFilters() {\n return state.savedFilters;\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(RecentMeetingsPanel, {\n onOpen: (meeting) => {\n openRecentMeeting(meeting.id, meeting.folderId);\n },\n get recentMeetings() {\n return state.recentMeetings;\n }\n }), null);\n insert(_el$2, createComponent(FolderList, {\n get error() {\n return state.folderError;\n },\n get folders() {\n return state.folders;\n },\n onSelect: (folderId) => {\n setState(\"selectedFolderId\", folderId);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n loadMeetings();\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n }\n }), null);\n insert(_el$2, createComponent(MeetingList, {\n get error() {\n return state.listError;\n },\n get emptyHint() {\n return meetingEmptyHint();\n },\n get folders() {\n return state.folders;\n },\n get meetings() {\n return state.meetings;\n },\n onSelect: (meetingId) => {\n loadMeeting(meetingId);\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get selectedMeetingId() {\n return state.selectedMeetingId;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$3, createComponent(AppStatePanel, {\n get appState() {\n return state.appState;\n },\n get statusLabel() {\n return state.statusLabel;\n },\n get statusTone() {\n return state.statusTone;\n }\n }), _el$4);\n _el$6.$$click = () => {\n connectAndRefresh(true);\n };\n _el$7.$$click = () => {\n clearFilters();\n };\n _el$8.$$click = () => {\n exportNotes();\n };\n _el$9.$$click = () => {\n exportTranscripts();\n };\n insert(_el$3, createComponent(SecurityPanel, {\n onLock: () => {\n lockServer();\n },\n onPasswordChange: (value) => {\n setState(\"serverPassword\", value);\n },\n onUnlock: () => {\n unlockServer();\n },\n get password() {\n return state.serverPassword;\n },\n get visible() {\n return state.serverLocked;\n }\n }), null);\n insert(_el$3, createComponent(AuthPanel, {\n get apiKeyDraft() {\n return state.apiKeyDraft;\n },\n get auth() {\n return state.appState?.auth;\n },\n onApiKeyDraftChange: (value) => {\n setState(\"apiKeyDraft\", value);\n },\n onImportDesktopSession: () => {\n importDesktopSession();\n },\n onLogout: () => {\n logout();\n },\n onRefresh: () => {\n refreshAuth();\n },\n onSaveApiKey: () => {\n saveApiKey();\n },\n onSwitchMode: (mode) => {\n switchAuthMode(mode);\n }\n }), null);\n insert(_el$3, createComponent(ExportJobsPanel, {\n get jobs() {\n return state.appState?.exports.jobs || [];\n },\n onRerun: (jobId) => {\n rerunJob(jobId);\n }\n }), null);\n insert(_el$3, createComponent(AutomationRunsPanel, {\n onApprove: (runId) => {\n resolveAutomationRun(runId, \"approve\");\n },\n onReject: (runId) => {\n resolveAutomationRun(runId, \"reject\");\n },\n get runs() {\n return state.automationRuns;\n }\n }), null);\n insert(_el$3, createComponent(Workspace, {\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get detailError() {\n return state.detailError;\n },\n onSelectTab: (tab) => {\n setState(\"workspaceTab\", tab);\n },\n get selectedMeeting() {\n return state.selectedMeeting;\n },\n get tab() {\n return state.workspaceTab;\n }\n }), null);\n return _el$;\n })();\n}\ndelegateEvents([\"click\"]);\n//#endregion\n//#region src/web-app/main.tsx\n/** @jsxImportSource solid-js */\nvar root = document.getElementById(\"granola-web-root\");\nif (!root) throw new Error(\"Granola web root element not found\");\nrender(() => createComponent(App, {}), root);\n//#endregion\n";
7581
+ const granolaWebClientJs = "//#region node_modules/solid-js/dist/solid.js\nvar sharedConfig = {\n context: void 0,\n registry: void 0,\n effects: void 0,\n done: false,\n getContextId() {\n return getContextId(this.context.count);\n },\n getNextContextId() {\n return getContextId(this.context.count++);\n }\n};\nfunction getContextId(count) {\n const num = String(count), len = num.length - 1;\n return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : \"\") + num;\n}\nfunction setHydrateContext(context) {\n sharedConfig.context = context;\n}\nfunction nextHydrateContext() {\n return {\n ...sharedConfig.context,\n id: sharedConfig.getNextContextId(),\n count: 0\n };\n}\nvar equalFn = (a, b) => a === b;\nvar $PROXY = Symbol(\"solid-proxy\");\nvar $TRACK = Symbol(\"solid-track\");\nvar signalOptions = { equals: equalFn };\nvar ERROR = null;\nvar runEffects = runQueue;\nvar STALE = 1;\nvar PENDING = 2;\nvar UNOWNED = {\n owned: null,\n cleanups: null,\n context: null,\n owner: null\n};\nvar Owner = null;\nvar Transition = null;\nvar Scheduler = null;\nvar ExternalSourceConfig = null;\nvar Listener = null;\nvar Updates = null;\nvar Effects = null;\nvar ExecCount = 0;\nfunction createRoot(fn, detachedOwner) {\n const listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === void 0 ? owner : detachedOwner, root = unowned ? UNOWNED : {\n owned: null,\n cleanups: null,\n context: current ? current.context : null,\n owner: current\n }, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));\n Owner = root;\n Listener = null;\n try {\n return runUpdates(updateFn, true);\n } finally {\n Listener = listener;\n Owner = owner;\n }\n}\nfunction createSignal(value, options) {\n options = options ? Object.assign({}, signalOptions, options) : signalOptions;\n const s = {\n value,\n observers: null,\n observerSlots: null,\n comparator: options.equals || void 0\n };\n const setter = (value) => {\n if (typeof value === \"function\") if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);\n else value = value(s.value);\n return writeSignal(s, value);\n };\n return [readSignal.bind(s), setter];\n}\nfunction createRenderEffect(fn, value, options) {\n const c = createComputation(fn, value, false, STALE);\n if (Scheduler && Transition && Transition.running) Updates.push(c);\n else updateComputation(c);\n}\nfunction createEffect(fn, value, options) {\n runEffects = runUserEffects;\n const c = createComputation(fn, value, false, STALE), s = SuspenseContext && useContext(SuspenseContext);\n if (s) c.suspense = s;\n if (!options || !options.render) c.user = true;\n Effects ? Effects.push(c) : updateComputation(c);\n}\nfunction createMemo(fn, value, options) {\n options = options ? Object.assign({}, signalOptions, options) : signalOptions;\n const c = createComputation(fn, value, true, 0);\n c.observers = null;\n c.observerSlots = null;\n c.comparator = options.equals || void 0;\n if (Scheduler && Transition && Transition.running) {\n c.tState = STALE;\n Updates.push(c);\n } else updateComputation(c);\n return readSignal.bind(c);\n}\nfunction batch(fn) {\n return runUpdates(fn, false);\n}\nfunction untrack(fn) {\n if (!ExternalSourceConfig && Listener === null) return fn();\n const listener = Listener;\n Listener = null;\n try {\n if (ExternalSourceConfig) return ExternalSourceConfig.untrack(fn);\n return fn();\n } finally {\n Listener = listener;\n }\n}\nfunction onMount(fn) {\n createEffect(() => untrack(fn));\n}\nfunction onCleanup(fn) {\n if (Owner === null);\n else if (Owner.cleanups === null) Owner.cleanups = [fn];\n else Owner.cleanups.push(fn);\n return fn;\n}\nfunction getListener() {\n return Listener;\n}\nfunction startTransition(fn) {\n if (Transition && Transition.running) {\n fn();\n return Transition.done;\n }\n const l = Listener;\n const o = Owner;\n return Promise.resolve().then(() => {\n Listener = l;\n Owner = o;\n let t;\n if (Scheduler || SuspenseContext) {\n t = Transition || (Transition = {\n sources: /* @__PURE__ */ new Set(),\n effects: [],\n promises: /* @__PURE__ */ new Set(),\n disposed: /* @__PURE__ */ new Set(),\n queue: /* @__PURE__ */ new Set(),\n running: true\n });\n t.done || (t.done = new Promise((res) => t.resolve = res));\n t.running = true;\n }\n runUpdates(fn, false);\n Listener = Owner = null;\n return t ? t.done : void 0;\n });\n}\nvar [transPending, setTransPending] = /* @__PURE__ */ createSignal(false);\nfunction useContext(context) {\n let value;\n return Owner && Owner.context && (value = Owner.context[context.id]) !== void 0 ? value : context.defaultValue;\n}\nvar SuspenseContext;\nfunction readSignal() {\n const runningTransition = Transition && Transition.running;\n if (this.sources && (runningTransition ? this.tState : this.state)) if ((runningTransition ? this.tState : this.state) === STALE) updateComputation(this);\n else {\n const updates = Updates;\n Updates = null;\n runUpdates(() => lookUpstream(this), false);\n Updates = updates;\n }\n if (Listener) {\n const sSlot = this.observers ? this.observers.length : 0;\n if (!Listener.sources) {\n Listener.sources = [this];\n Listener.sourceSlots = [sSlot];\n } else {\n Listener.sources.push(this);\n Listener.sourceSlots.push(sSlot);\n }\n if (!this.observers) {\n this.observers = [Listener];\n this.observerSlots = [Listener.sources.length - 1];\n } else {\n this.observers.push(Listener);\n this.observerSlots.push(Listener.sources.length - 1);\n }\n }\n if (runningTransition && Transition.sources.has(this)) return this.tValue;\n return this.value;\n}\nfunction writeSignal(node, value, isComp) {\n let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;\n if (!node.comparator || !node.comparator(current, value)) {\n if (Transition) {\n const TransitionRunning = Transition.running;\n if (TransitionRunning || !isComp && Transition.sources.has(node)) {\n Transition.sources.add(node);\n node.tValue = value;\n }\n if (!TransitionRunning) node.value = value;\n } else node.value = value;\n if (node.observers && node.observers.length) runUpdates(() => {\n for (let i = 0; i < node.observers.length; i += 1) {\n const o = node.observers[i];\n const TransitionRunning = Transition && Transition.running;\n if (TransitionRunning && Transition.disposed.has(o)) continue;\n if (TransitionRunning ? !o.tState : !o.state) {\n if (o.pure) Updates.push(o);\n else Effects.push(o);\n if (o.observers) markDownstream(o);\n }\n if (!TransitionRunning) o.state = STALE;\n else o.tState = STALE;\n }\n if (Updates.length > 1e6) {\n Updates = [];\n throw new Error();\n }\n }, false);\n }\n return value;\n}\nfunction updateComputation(node) {\n if (!node.fn) return;\n cleanNode(node);\n const time = ExecCount;\n runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time);\n if (Transition && !Transition.running && Transition.sources.has(node)) queueMicrotask(() => {\n runUpdates(() => {\n Transition && (Transition.running = true);\n Listener = Owner = node;\n runComputation(node, node.tValue, time);\n Listener = Owner = null;\n }, false);\n });\n}\nfunction runComputation(node, value, time) {\n let nextValue;\n const owner = Owner, listener = Listener;\n Listener = Owner = node;\n try {\n nextValue = node.fn(value);\n } catch (err) {\n if (node.pure) if (Transition && Transition.running) {\n node.tState = STALE;\n node.tOwned && node.tOwned.forEach(cleanNode);\n node.tOwned = void 0;\n } else {\n node.state = STALE;\n node.owned && node.owned.forEach(cleanNode);\n node.owned = null;\n }\n node.updatedAt = time + 1;\n return handleError(err);\n } finally {\n Listener = listener;\n Owner = owner;\n }\n if (!node.updatedAt || node.updatedAt <= time) {\n if (node.updatedAt != null && \"observers\" in node) writeSignal(node, nextValue, true);\n else if (Transition && Transition.running && node.pure) {\n if (!Transition.sources.has(node)) node.value = nextValue;\n Transition.sources.add(node);\n node.tValue = nextValue;\n } else node.value = nextValue;\n node.updatedAt = time;\n }\n}\nfunction createComputation(fn, init, pure, state = STALE, options) {\n const c = {\n fn,\n state,\n updatedAt: null,\n owned: null,\n sources: null,\n sourceSlots: null,\n cleanups: null,\n value: init,\n owner: Owner,\n context: Owner ? Owner.context : null,\n pure\n };\n if (Transition && Transition.running) {\n c.state = 0;\n c.tState = state;\n }\n if (Owner === null);\n else if (Owner !== UNOWNED) if (Transition && Transition.running && Owner.pure) if (!Owner.tOwned) Owner.tOwned = [c];\n else Owner.tOwned.push(c);\n else if (!Owner.owned) Owner.owned = [c];\n else Owner.owned.push(c);\n if (ExternalSourceConfig && c.fn) {\n const sourceFn = c.fn;\n const [track, trigger] = createSignal(void 0, { equals: false });\n const ordinary = ExternalSourceConfig.factory(sourceFn, trigger);\n onCleanup(() => ordinary.dispose());\n let inTransition;\n const triggerInTransition = () => startTransition(trigger).then(() => {\n if (inTransition) {\n inTransition.dispose();\n inTransition = void 0;\n }\n });\n c.fn = (x) => {\n track();\n if (Transition && Transition.running) {\n if (!inTransition) inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition);\n return inTransition.track(x);\n }\n return ordinary.track(x);\n };\n }\n return c;\n}\nfunction runTop(node) {\n const runningTransition = Transition && Transition.running;\n if ((runningTransition ? node.tState : node.state) === 0) return;\n if ((runningTransition ? node.tState : node.state) === PENDING) return lookUpstream(node);\n if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);\n const ancestors = [node];\n while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {\n if (runningTransition && Transition.disposed.has(node)) return;\n if (runningTransition ? node.tState : node.state) ancestors.push(node);\n }\n for (let i = ancestors.length - 1; i >= 0; i--) {\n node = ancestors[i];\n if (runningTransition) {\n let top = node, prev = ancestors[i + 1];\n while ((top = top.owner) && top !== prev) if (Transition.disposed.has(top)) return;\n }\n if ((runningTransition ? node.tState : node.state) === STALE) updateComputation(node);\n else if ((runningTransition ? node.tState : node.state) === PENDING) {\n const updates = Updates;\n Updates = null;\n runUpdates(() => lookUpstream(node, ancestors[0]), false);\n Updates = updates;\n }\n }\n}\nfunction runUpdates(fn, init) {\n if (Updates) return fn();\n let wait = false;\n if (!init) Updates = [];\n if (Effects) wait = true;\n else Effects = [];\n ExecCount++;\n try {\n const res = fn();\n completeUpdates(wait);\n return res;\n } catch (err) {\n if (!wait) Effects = null;\n Updates = null;\n handleError(err);\n }\n}\nfunction completeUpdates(wait) {\n if (Updates) {\n if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);\n else runQueue(Updates);\n Updates = null;\n }\n if (wait) return;\n let res;\n if (Transition) {\n if (!Transition.promises.size && !Transition.queue.size) {\n const sources = Transition.sources;\n const disposed = Transition.disposed;\n Effects.push.apply(Effects, Transition.effects);\n res = Transition.resolve;\n for (const e of Effects) {\n \"tState\" in e && (e.state = e.tState);\n delete e.tState;\n }\n Transition = null;\n runUpdates(() => {\n for (const d of disposed) cleanNode(d);\n for (const v of sources) {\n v.value = v.tValue;\n if (v.owned) for (let i = 0, len = v.owned.length; i < len; i++) cleanNode(v.owned[i]);\n if (v.tOwned) v.owned = v.tOwned;\n delete v.tValue;\n delete v.tOwned;\n v.tState = 0;\n }\n setTransPending(false);\n }, false);\n } else if (Transition.running) {\n Transition.running = false;\n Transition.effects.push.apply(Transition.effects, Effects);\n Effects = null;\n setTransPending(true);\n return;\n }\n }\n const e = Effects;\n Effects = null;\n if (e.length) runUpdates(() => runEffects(e), false);\n if (res) res();\n}\nfunction runQueue(queue) {\n for (let i = 0; i < queue.length; i++) runTop(queue[i]);\n}\nfunction scheduleQueue(queue) {\n for (let i = 0; i < queue.length; i++) {\n const item = queue[i];\n const tasks = Transition.queue;\n if (!tasks.has(item)) {\n tasks.add(item);\n Scheduler(() => {\n tasks.delete(item);\n runUpdates(() => {\n Transition.running = true;\n runTop(item);\n }, false);\n Transition && (Transition.running = false);\n });\n }\n }\n}\nfunction runUserEffects(queue) {\n let i, userLength = 0;\n for (i = 0; i < queue.length; i++) {\n const e = queue[i];\n if (!e.user) runTop(e);\n else queue[userLength++] = e;\n }\n if (sharedConfig.context) {\n if (sharedConfig.count) {\n sharedConfig.effects || (sharedConfig.effects = []);\n sharedConfig.effects.push(...queue.slice(0, userLength));\n return;\n }\n setHydrateContext();\n }\n if (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count)) {\n queue = [...sharedConfig.effects, ...queue];\n userLength += sharedConfig.effects.length;\n delete sharedConfig.effects;\n }\n for (i = 0; i < userLength; i++) runTop(queue[i]);\n}\nfunction lookUpstream(node, ignore) {\n const runningTransition = Transition && Transition.running;\n if (runningTransition) node.tState = 0;\n else node.state = 0;\n for (let i = 0; i < node.sources.length; i += 1) {\n const source = node.sources[i];\n if (source.sources) {\n const state = runningTransition ? source.tState : source.state;\n if (state === STALE) {\n if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source);\n } else if (state === PENDING) lookUpstream(source, ignore);\n }\n }\n}\nfunction markDownstream(node) {\n const runningTransition = Transition && Transition.running;\n for (let i = 0; i < node.observers.length; i += 1) {\n const o = node.observers[i];\n if (runningTransition ? !o.tState : !o.state) {\n if (runningTransition) o.tState = PENDING;\n else o.state = PENDING;\n if (o.pure) Updates.push(o);\n else Effects.push(o);\n o.observers && markDownstream(o);\n }\n }\n}\nfunction cleanNode(node) {\n let i;\n if (node.sources) while (node.sources.length) {\n const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;\n if (obs && obs.length) {\n const n = obs.pop(), s = source.observerSlots.pop();\n if (index < obs.length) {\n n.sourceSlots[s] = index;\n obs[index] = n;\n source.observerSlots[index] = s;\n }\n }\n }\n if (node.tOwned) {\n for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]);\n delete node.tOwned;\n }\n if (Transition && Transition.running && node.pure) reset(node, true);\n else if (node.owned) {\n for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);\n node.owned = null;\n }\n if (node.cleanups) {\n for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();\n node.cleanups = null;\n }\n if (Transition && Transition.running) node.tState = 0;\n else node.state = 0;\n}\nfunction reset(node, top) {\n if (!top) {\n node.tState = 0;\n Transition.disposed.add(node);\n }\n if (node.owned) for (let i = 0; i < node.owned.length; i++) reset(node.owned[i]);\n}\nfunction castError(err) {\n if (err instanceof Error) return err;\n return new Error(typeof err === \"string\" ? err : \"Unknown error\", { cause: err });\n}\nfunction runErrors(err, fns, owner) {\n try {\n for (const f of fns) f(err);\n } catch (e) {\n handleError(e, owner && owner.owner || null);\n }\n}\nfunction handleError(err, owner = Owner) {\n const fns = ERROR && owner && owner.context && owner.context[ERROR];\n const error = castError(err);\n if (!fns) throw error;\n if (Effects) Effects.push({\n fn() {\n runErrors(error, fns, owner);\n },\n state: STALE\n });\n else runErrors(error, fns, owner);\n}\nvar FALLBACK = Symbol(\"fallback\");\nfunction dispose(d) {\n for (let i = 0; i < d.length; i++) d[i]();\n}\nfunction mapArray(list, mapFn, options = {}) {\n let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null;\n onCleanup(() => dispose(disposers));\n return () => {\n let newItems = list() || [], newLen = newItems.length, i, j;\n newItems[$TRACK];\n return untrack(() => {\n let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;\n if (newLen === 0) {\n if (len !== 0) {\n dispose(disposers);\n disposers = [];\n items = [];\n mapped = [];\n len = 0;\n indexes && (indexes = []);\n }\n if (options.fallback) {\n items = [FALLBACK];\n mapped[0] = createRoot((disposer) => {\n disposers[0] = disposer;\n return options.fallback();\n });\n len = 1;\n }\n } else if (len === 0) {\n mapped = new Array(newLen);\n for (j = 0; j < newLen; j++) {\n items[j] = newItems[j];\n mapped[j] = createRoot(mapper);\n }\n len = newLen;\n } else {\n temp = new Array(newLen);\n tempdisposers = new Array(newLen);\n indexes && (tempIndexes = new Array(newLen));\n for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++);\n for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {\n temp[newEnd] = mapped[end];\n tempdisposers[newEnd] = disposers[end];\n indexes && (tempIndexes[newEnd] = indexes[end]);\n }\n newIndices = /* @__PURE__ */ new Map();\n newIndicesNext = new Array(newEnd + 1);\n for (j = newEnd; j >= start; j--) {\n item = newItems[j];\n i = newIndices.get(item);\n newIndicesNext[j] = i === void 0 ? -1 : i;\n newIndices.set(item, j);\n }\n for (i = start; i <= end; i++) {\n item = items[i];\n j = newIndices.get(item);\n if (j !== void 0 && j !== -1) {\n temp[j] = mapped[i];\n tempdisposers[j] = disposers[i];\n indexes && (tempIndexes[j] = indexes[i]);\n j = newIndicesNext[j];\n newIndices.set(item, j);\n } else disposers[i]();\n }\n for (j = start; j < newLen; j++) if (j in temp) {\n mapped[j] = temp[j];\n disposers[j] = tempdisposers[j];\n if (indexes) {\n indexes[j] = tempIndexes[j];\n indexes[j](j);\n }\n } else mapped[j] = createRoot(mapper);\n mapped = mapped.slice(0, len = newLen);\n items = newItems.slice(0);\n }\n return mapped;\n });\n function mapper(disposer) {\n disposers[j] = disposer;\n if (indexes) {\n const [s, set] = createSignal(j);\n indexes[j] = set;\n return mapFn(newItems[j], s);\n }\n return mapFn(newItems[j]);\n }\n };\n}\nvar hydrationEnabled = false;\nfunction createComponent(Comp, props) {\n if (hydrationEnabled) {\n if (sharedConfig.context) {\n const c = sharedConfig.context;\n setHydrateContext(nextHydrateContext());\n const r = untrack(() => Comp(props || {}));\n setHydrateContext(c);\n return r;\n }\n }\n return untrack(() => Comp(props || {}));\n}\nvar narrowedError = (name) => `Stale read from <${name}>.`;\nfunction For(props) {\n const fallback = \"fallback\" in props && { fallback: () => props.fallback };\n return createMemo(mapArray(() => props.each, props.children, fallback || void 0));\n}\nfunction Show(props) {\n const keyed = props.keyed;\n const conditionValue = createMemo(() => props.when, void 0, void 0);\n const condition = keyed ? conditionValue : createMemo(conditionValue, void 0, { equals: (a, b) => !a === !b });\n return createMemo(() => {\n const c = condition();\n if (c) {\n const child = props.children;\n return typeof child === \"function\" && child.length > 0 ? untrack(() => child(keyed ? c : () => {\n if (!untrack(condition)) throw narrowedError(\"Show\");\n return conditionValue();\n })) : child;\n }\n return props.fallback;\n }, void 0, void 0);\n}\n//#endregion\n//#region node_modules/solid-js/web/dist/web.js\nvar memo = (fn) => createMemo(() => fn());\nfunction reconcileArrays(parentNode, a, b) {\n let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null;\n while (aStart < aEnd || bStart < bEnd) {\n if (a[aStart] === b[bStart]) {\n aStart++;\n bStart++;\n continue;\n }\n while (a[aEnd - 1] === b[bEnd - 1]) {\n aEnd--;\n bEnd--;\n }\n if (aEnd === aStart) {\n const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;\n while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node);\n } else if (bEnd === bStart) while (aStart < aEnd) {\n if (!map || !map.has(a[aStart])) a[aStart].remove();\n aStart++;\n }\n else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {\n const node = a[--aEnd].nextSibling;\n parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling);\n parentNode.insertBefore(b[--bEnd], node);\n a[aEnd] = b[bEnd];\n } else {\n if (!map) {\n map = /* @__PURE__ */ new Map();\n let i = bStart;\n while (i < bEnd) map.set(b[i], i++);\n }\n const index = map.get(a[aStart]);\n if (index != null) if (bStart < index && index < bEnd) {\n let i = aStart, sequence = 1, t;\n while (++i < aEnd && i < bEnd) {\n if ((t = map.get(a[i])) == null || t !== index + sequence) break;\n sequence++;\n }\n if (sequence > index - bStart) {\n const node = a[aStart];\n while (bStart < index) parentNode.insertBefore(b[bStart++], node);\n } else parentNode.replaceChild(b[bStart++], a[aStart++]);\n } else aStart++;\n else a[aStart++].remove();\n }\n }\n}\nvar $$EVENTS = \"_$DX_DELEGATE\";\nfunction render(code, element, init, options = {}) {\n let disposer;\n createRoot((dispose) => {\n disposer = dispose;\n element === document ? code() : insert(element, code(), element.firstChild ? null : void 0, init);\n }, options.owner);\n return () => {\n disposer();\n element.textContent = \"\";\n };\n}\nfunction template(html, isImportNode, isSVG, isMathML) {\n let node;\n const create = () => {\n const t = isMathML ? document.createElementNS(\"http://www.w3.org/1998/Math/MathML\", \"template\") : document.createElement(\"template\");\n t.innerHTML = html;\n return isSVG ? t.content.firstChild.firstChild : isMathML ? t.firstChild : t.content.firstChild;\n };\n const fn = isImportNode ? () => untrack(() => document.importNode(node || (node = create()), true)) : () => (node || (node = create())).cloneNode(true);\n fn.cloneNode = fn;\n return fn;\n}\nfunction delegateEvents(eventNames, document = window.document) {\n const e = document[$$EVENTS] || (document[$$EVENTS] = /* @__PURE__ */ new Set());\n for (let i = 0, l = eventNames.length; i < l; i++) {\n const name = eventNames[i];\n if (!e.has(name)) {\n e.add(name);\n document.addEventListener(name, eventHandler);\n }\n }\n}\nfunction setAttribute(node, name, value) {\n if (isHydrating(node)) return;\n if (value == null) node.removeAttribute(name);\n else node.setAttribute(name, value);\n}\nfunction addEventListener(node, name, handler, delegate) {\n if (delegate) if (Array.isArray(handler)) {\n node[`$$${name}`] = handler[0];\n node[`$$${name}Data`] = handler[1];\n } else node[`$$${name}`] = handler;\n else if (Array.isArray(handler)) {\n const handlerFn = handler[0];\n node.addEventListener(name, handler[0] = (e) => handlerFn.call(node, handler[1], e));\n } else node.addEventListener(name, handler, typeof handler !== \"function\" && handler);\n}\nfunction insert(parent, accessor, marker, initial) {\n if (marker !== void 0 && !initial) initial = [];\n if (typeof accessor !== \"function\") return insertExpression(parent, accessor, initial, marker);\n createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);\n}\nfunction isHydrating(node) {\n return !!sharedConfig.context && !sharedConfig.done && (!node || node.isConnected);\n}\nfunction eventHandler(e) {\n if (sharedConfig.registry && sharedConfig.events) {\n if (sharedConfig.events.find(([el, ev]) => ev === e)) return;\n }\n let node = e.target;\n const key = `$$${e.type}`;\n const oriTarget = e.target;\n const oriCurrentTarget = e.currentTarget;\n const retarget = (value) => Object.defineProperty(e, \"target\", {\n configurable: true,\n value\n });\n const handleNode = () => {\n const handler = node[key];\n if (handler && !node.disabled) {\n const data = node[`${key}Data`];\n data !== void 0 ? handler.call(node, data, e) : handler.call(node, e);\n if (e.cancelBubble) return;\n }\n node.host && typeof node.host !== \"string\" && !node.host._$host && node.contains(e.target) && retarget(node.host);\n return true;\n };\n const walkUpTree = () => {\n while (handleNode() && (node = node._$host || node.parentNode || node.host));\n };\n Object.defineProperty(e, \"currentTarget\", {\n configurable: true,\n get() {\n return node || document;\n }\n });\n if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;\n if (e.composedPath) {\n const path = e.composedPath();\n retarget(path[0]);\n for (let i = 0; i < path.length - 2; i++) {\n node = path[i];\n if (!handleNode()) break;\n if (node._$host) {\n node = node._$host;\n walkUpTree();\n break;\n }\n if (node.parentNode === oriCurrentTarget) break;\n }\n } else walkUpTree();\n retarget(oriTarget);\n}\nfunction insertExpression(parent, value, current, marker, unwrapArray) {\n const hydrating = isHydrating(parent);\n if (hydrating) {\n !current && (current = [...parent.childNodes]);\n let cleaned = [];\n for (let i = 0; i < current.length; i++) {\n const node = current[i];\n if (node.nodeType === 8 && node.data.slice(0, 2) === \"!$\") node.remove();\n else cleaned.push(node);\n }\n current = cleaned;\n }\n while (typeof current === \"function\") current = current();\n if (value === current) return current;\n const t = typeof value, multi = marker !== void 0;\n parent = multi && current[0] && current[0].parentNode || parent;\n if (t === \"string\" || t === \"number\") {\n if (hydrating) return current;\n if (t === \"number\") {\n value = value.toString();\n if (value === current) return current;\n }\n if (multi) {\n let node = current[0];\n if (node && node.nodeType === 3) node.data !== value && (node.data = value);\n else node = document.createTextNode(value);\n current = cleanChildren(parent, current, marker, node);\n } else if (current !== \"\" && typeof current === \"string\") current = parent.firstChild.data = value;\n else current = parent.textContent = value;\n } else if (value == null || t === \"boolean\") {\n if (hydrating) return current;\n current = cleanChildren(parent, current, marker);\n } else if (t === \"function\") {\n createRenderEffect(() => {\n let v = value();\n while (typeof v === \"function\") v = v();\n current = insertExpression(parent, v, current, marker);\n });\n return () => current;\n } else if (Array.isArray(value)) {\n const array = [];\n const currentArray = current && Array.isArray(current);\n if (normalizeIncomingArray(array, value, current, unwrapArray)) {\n createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));\n return () => current;\n }\n if (hydrating) {\n if (!array.length) return current;\n if (marker === void 0) return current = [...parent.childNodes];\n let node = array[0];\n if (node.parentNode !== parent) return current;\n const nodes = [node];\n while ((node = node.nextSibling) !== marker) nodes.push(node);\n return current = nodes;\n }\n if (array.length === 0) {\n current = cleanChildren(parent, current, marker);\n if (multi) return current;\n } else if (currentArray) if (current.length === 0) appendNodes(parent, array, marker);\n else reconcileArrays(parent, current, array);\n else {\n current && cleanChildren(parent);\n appendNodes(parent, array);\n }\n current = array;\n } else if (value.nodeType) {\n if (hydrating && value.parentNode) return current = multi ? [value] : value;\n if (Array.isArray(current)) {\n if (multi) return current = cleanChildren(parent, current, marker, value);\n cleanChildren(parent, current, null, value);\n } else if (current == null || current === \"\" || !parent.firstChild) parent.appendChild(value);\n else parent.replaceChild(value, parent.firstChild);\n current = value;\n }\n return current;\n}\nfunction normalizeIncomingArray(normalized, array, current, unwrap) {\n let dynamic = false;\n for (let i = 0, len = array.length; i < len; i++) {\n let item = array[i], prev = current && current[normalized.length], t;\n if (item == null || item === true || item === false);\n else if ((t = typeof item) === \"object\" && item.nodeType) normalized.push(item);\n else if (Array.isArray(item)) dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;\n else if (t === \"function\") if (unwrap) {\n while (typeof item === \"function\") item = item();\n dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;\n } else {\n normalized.push(item);\n dynamic = true;\n }\n else {\n const value = String(item);\n if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);\n else normalized.push(document.createTextNode(value));\n }\n }\n return dynamic;\n}\nfunction appendNodes(parent, array, marker = null) {\n for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);\n}\nfunction cleanChildren(parent, current, marker, replacement) {\n if (marker === void 0) return parent.textContent = \"\";\n const node = replacement || document.createTextNode(\"\");\n if (current.length) {\n let inserted = false;\n for (let i = current.length - 1; i >= 0; i--) {\n const el = current[i];\n if (node !== el) {\n const isParent = el.parentNode === parent;\n if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);\n else isParent && el.remove();\n } else inserted = true;\n }\n } else parent.insertBefore(node, marker);\n return [node];\n}\n//#endregion\n//#region node_modules/solid-js/store/dist/store.js\nvar $RAW = Symbol(\"store-raw\"), $NODE = Symbol(\"store-node\"), $HAS = Symbol(\"store-has\"), $SELF = Symbol(\"store-self\");\nfunction wrap$1(value) {\n let p = value[$PROXY];\n if (!p) {\n Object.defineProperty(value, $PROXY, { value: p = new Proxy(value, proxyTraps$1) });\n if (!Array.isArray(value)) {\n const keys = Object.keys(value), desc = Object.getOwnPropertyDescriptors(value);\n for (let i = 0, l = keys.length; i < l; i++) {\n const prop = keys[i];\n if (desc[prop].get) Object.defineProperty(value, prop, {\n enumerable: desc[prop].enumerable,\n get: desc[prop].get.bind(p)\n });\n }\n }\n }\n return p;\n}\nfunction isWrappable(obj) {\n let proto;\n return obj != null && typeof obj === \"object\" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj));\n}\nfunction unwrap(item, set = /* @__PURE__ */ new Set()) {\n let result, unwrapped, v, prop;\n if (result = item != null && item[$RAW]) return result;\n if (!isWrappable(item) || set.has(item)) return item;\n if (Array.isArray(item)) {\n if (Object.isFrozen(item)) item = item.slice(0);\n else set.add(item);\n for (let i = 0, l = item.length; i < l; i++) {\n v = item[i];\n if ((unwrapped = unwrap(v, set)) !== v) item[i] = unwrapped;\n }\n } else {\n if (Object.isFrozen(item)) item = Object.assign({}, item);\n else set.add(item);\n const keys = Object.keys(item), desc = Object.getOwnPropertyDescriptors(item);\n for (let i = 0, l = keys.length; i < l; i++) {\n prop = keys[i];\n if (desc[prop].get) continue;\n v = item[prop];\n if ((unwrapped = unwrap(v, set)) !== v) item[prop] = unwrapped;\n }\n }\n return item;\n}\nfunction getNodes(target, symbol) {\n let nodes = target[symbol];\n if (!nodes) Object.defineProperty(target, symbol, { value: nodes = Object.create(null) });\n return nodes;\n}\nfunction getNode(nodes, property, value) {\n if (nodes[property]) return nodes[property];\n const [s, set] = createSignal(value, {\n equals: false,\n internal: true\n });\n s.$ = set;\n return nodes[property] = s;\n}\nfunction proxyDescriptor$1(target, property) {\n const desc = Reflect.getOwnPropertyDescriptor(target, property);\n if (!desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE) return desc;\n delete desc.value;\n delete desc.writable;\n desc.get = () => target[$PROXY][property];\n return desc;\n}\nfunction trackSelf(target) {\n getListener() && getNode(getNodes(target, $NODE), $SELF)();\n}\nfunction ownKeys(target) {\n trackSelf(target);\n return Reflect.ownKeys(target);\n}\nvar proxyTraps$1 = {\n get(target, property, receiver) {\n if (property === $RAW) return target;\n if (property === $PROXY) return receiver;\n if (property === $TRACK) {\n trackSelf(target);\n return receiver;\n }\n const nodes = getNodes(target, $NODE);\n const tracked = nodes[property];\n let value = tracked ? tracked() : target[property];\n if (property === $NODE || property === $HAS || property === \"__proto__\") return value;\n if (!tracked) {\n const desc = Object.getOwnPropertyDescriptor(target, property);\n if (getListener() && (typeof value !== \"function\" || target.hasOwnProperty(property)) && !(desc && desc.get)) value = getNode(nodes, property, value)();\n }\n return isWrappable(value) ? wrap$1(value) : value;\n },\n has(target, property) {\n if (property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === $HAS || property === \"__proto__\") return true;\n getListener() && getNode(getNodes(target, $HAS), property)();\n return property in target;\n },\n set() {\n return true;\n },\n deleteProperty() {\n return true;\n },\n ownKeys,\n getOwnPropertyDescriptor: proxyDescriptor$1\n};\nfunction setProperty(state, property, value, deleting = false) {\n if (!deleting && state[property] === value) return;\n const prev = state[property], len = state.length;\n if (value === void 0) {\n delete state[property];\n if (state[$HAS] && state[$HAS][property] && prev !== void 0) state[$HAS][property].$();\n } else {\n state[property] = value;\n if (state[$HAS] && state[$HAS][property] && prev === void 0) state[$HAS][property].$();\n }\n let nodes = getNodes(state, $NODE), node;\n if (node = getNode(nodes, property, prev)) node.$(() => value);\n if (Array.isArray(state) && state.length !== len) {\n for (let i = state.length; i < len; i++) (node = nodes[i]) && node.$();\n (node = getNode(nodes, \"length\", len)) && node.$(state.length);\n }\n (node = nodes[$SELF]) && node.$();\n}\nfunction mergeStoreNode(state, value) {\n const keys = Object.keys(value);\n for (let i = 0; i < keys.length; i += 1) {\n const key = keys[i];\n setProperty(state, key, value[key]);\n }\n}\nfunction updateArray(current, next) {\n if (typeof next === \"function\") next = next(current);\n next = unwrap(next);\n if (Array.isArray(next)) {\n if (current === next) return;\n let i = 0, len = next.length;\n for (; i < len; i++) {\n const value = next[i];\n if (current[i] !== value) setProperty(current, i, value);\n }\n setProperty(current, \"length\", len);\n } else mergeStoreNode(current, next);\n}\nfunction updatePath(current, path, traversed = []) {\n let part, prev = current;\n if (path.length > 1) {\n part = path.shift();\n const partType = typeof part, isArray = Array.isArray(current);\n if (Array.isArray(part)) {\n for (let i = 0; i < part.length; i++) updatePath(current, [part[i]].concat(path), traversed);\n return;\n } else if (isArray && partType === \"function\") {\n for (let i = 0; i < current.length; i++) if (part(current[i], i)) updatePath(current, [i].concat(path), traversed);\n return;\n } else if (isArray && partType === \"object\") {\n const { from = 0, to = current.length - 1, by = 1 } = part;\n for (let i = from; i <= to; i += by) updatePath(current, [i].concat(path), traversed);\n return;\n } else if (path.length > 1) {\n updatePath(current[part], path, [part].concat(traversed));\n return;\n }\n prev = current[part];\n traversed = [part].concat(traversed);\n }\n let value = path[0];\n if (typeof value === \"function\") {\n value = value(prev, traversed);\n if (value === prev) return;\n }\n if (part === void 0 && value == void 0) return;\n value = unwrap(value);\n if (part === void 0 || isWrappable(prev) && isWrappable(value) && !Array.isArray(value)) mergeStoreNode(prev, value);\n else setProperty(current, part, value);\n}\nfunction createStore(...[store, options]) {\n const unwrappedStore = unwrap(store || {});\n const isArray = Array.isArray(unwrappedStore);\n const wrappedStore = wrap$1(unwrappedStore);\n function setStore(...args) {\n batch(() => {\n isArray && args.length === 1 ? updateArray(unwrappedStore, args[0]) : updatePath(unwrappedStore, args);\n });\n }\n return [wrappedStore, setStore];\n}\n//#endregion\n//#region src/transport.ts\nvar granolaTransportPaths = {\n authLock: \"/auth/lock\",\n authLogin: \"/auth/login\",\n authLogout: \"/auth/logout\",\n authMode: \"/auth/mode\",\n authRefresh: \"/auth/refresh\",\n authStatus: \"/auth/status\",\n authUnlock: \"/auth/unlock\",\n automationMatches: \"/automation/matches\",\n automationArtefacts: \"/automation/artefacts\",\n automationRules: \"/automation/rules\",\n automationRuns: \"/automation/runs\",\n events: \"/events\",\n exportJobs: \"/exports/jobs\",\n exportNotes: \"/exports/notes\",\n exportTranscripts: \"/exports/transcripts\",\n folderResolve: \"/folders/resolve\",\n folders: \"/folders\",\n health: \"/health\",\n meetingResolve: \"/meetings/resolve\",\n meetings: \"/meetings\",\n root: \"/\",\n serverInfo: \"/server/info\",\n syncRun: \"/sync\",\n syncEvents: \"/sync/events\",\n state: \"/state\"\n};\nfunction appendSearchParams(path, params) {\n const url = new URL(path, \"http://localhost\");\n for (const [key, value] of Object.entries(params)) {\n if (value === void 0 || value === false || value === \"\") continue;\n url.searchParams.set(key, String(value));\n }\n return `${url.pathname}${url.search}`;\n}\nfunction granolaMeetingPath(id) {\n return `${granolaTransportPaths.meetings}/${encodeURIComponent(id)}`;\n}\nfunction granolaMeetingResolvePath(query, options = {}) {\n return appendSearchParams(granolaTransportPaths.meetingResolve, {\n includeTranscript: options.includeTranscript ? \"true\" : void 0,\n q: query\n });\n}\nfunction granolaMeetingsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.meetings, {\n folderId: options.folderId,\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search,\n sort: options.sort,\n updatedFrom: options.updatedFrom,\n updatedTo: options.updatedTo\n });\n}\nfunction granolaFolderPath(id) {\n return `${granolaTransportPaths.folders}/${encodeURIComponent(id)}`;\n}\nfunction granolaFolderResolvePath(query) {\n return appendSearchParams(granolaTransportPaths.folderResolve, { q: query });\n}\nfunction granolaFoldersPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.folders, {\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search\n });\n}\nfunction granolaExportJobsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.exportJobs, { limit: options.limit });\n}\nfunction granolaAutomationRunsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationRuns, {\n limit: options.limit,\n status: options.status\n });\n}\nfunction granolaAutomationArtefactsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationArtefacts, {\n kind: options.kind,\n limit: options.limit,\n meetingId: options.meetingId,\n status: options.status\n });\n}\nfunction granolaAutomationRunDecisionPath(id, decision) {\n return `${granolaTransportPaths.automationRuns}/${encodeURIComponent(id)}/${decision}`;\n}\nfunction granolaAutomationArtefactRerunPath(id) {\n return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}/rerun`;\n}\nfunction 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 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 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 &lt;token&gt;</code>.</div><div class=auth-card__actions><input class=input placeholder=grn_... type=password><button class=\"button button--secondary\"type=button>Save API key</button><button class=\"button button--secondary\"type=button>Import desktop session</button><button class=\"button button--secondary\"type=button>Refresh stored session</button><button class=\"button button--secondary\"type=button>Use API key</button><button class=\"button button--secondary\"type=button>Use stored session</button><button class=\"button button--secondary\"type=button>Use supabase.json</button><button class=\"button button--secondary\"type=button>Sign out`), _tmpl$27 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Recent Export Jobs</h3><p>Tracked across CLI and web runs.</p></div><div class=jobs-list>`), _tmpl$28 = /* @__PURE__ */ template(`<div class=job-empty>No export jobs yet.`), _tmpl$29 = /* @__PURE__ */ template(`<div class=job-card__meta>`), _tmpl$30 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Rerun`), _tmpl$31 = /* @__PURE__ */ template(`<article class=job-card><div class=job-card__head><div><div class=job-card__title> export</div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta>Started: </div><div class=job-card__meta>Output: </div><div class=job-card__actions>`), _tmpl$32 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Automation Runs</h3><p>Recent action runs triggered by durable sync events.</p></div><div class=jobs-list>`), _tmpl$33 = /* @__PURE__ */ template(`<div class=job-empty>No automation runs yet.`), _tmpl$34 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Approve`), _tmpl$35 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Reject`), _tmpl$36 = /* @__PURE__ */ template(`<article class=job-card><div class=job-card__head><div><div class=job-card__title></div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta></div><div class=job-card__actions>`), _tmpl$37 = /* @__PURE__ */ template(`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle`), _tmpl$38 = /* @__PURE__ */ template(`<button class=workspace-tab type=button>`), _tmpl$39 = /* @__PURE__ */ template(`<div class=empty>`), _tmpl$40 = /* @__PURE__ */ template(`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>`), _tmpl$41 = /* @__PURE__ */ template(`<div class=detail-body><div class=workspace-grid><aside class=\"detail-section workspace-sidebar\"><h2>Meeting Metadata</h2><pre class=detail-pre></pre></aside><section class=\"detail-section workspace-main\"><h2></h2><pre class=detail-pre>`);\nfunction authModeLabel(mode) {\n switch (mode) {\n case \"api-key\": return \"API key\";\n case \"stored-session\": return \"Stored session\";\n default: return \"supabase.json\";\n }\n}\nfunction metadataLines(record) {\n return [\n `Title: ${record.meeting.title || record.meeting.id}`,\n `Created: ${record.meeting.createdAt}`,\n `Updated: ${record.meeting.updatedAt}`,\n `Folders: ${record.meeting.folders.length ? record.meeting.folders.map((folder) => folder.name).join(\", \") : \"none\"}`,\n `Tags: ${record.meeting.tags.length ? record.meeting.tags.join(\", \") : \"none\"}`,\n `Transcript loaded: ${record.meeting.transcriptLoaded ? \"yes\" : \"no\"}`\n ].join(\"\\n\");\n}\nfunction workspaceBody(bundle, record, tab) {\n switch (tab) {\n case \"transcript\": return {\n body: record.transcriptText || \"(Transcript unavailable)\",\n title: \"Transcript\"\n };\n case \"metadata\": return {\n body: metadataLines(record),\n title: \"Metadata\"\n };\n case \"raw\": return {\n body: JSON.stringify(bundle || record, null, 2),\n title: \"Raw Bundle\"\n };\n default: return {\n body: record.noteMarkdown || \"(No notes available)\",\n title: \"Notes\"\n };\n }\n}\nfunction scopeLabel(scope) {\n return exportScopeLabel(scope);\n}\nfunction ToolbarFilters(props) {\n return [(() => {\n var _el$ = _tmpl$$1(), _el$4 = _el$.firstChild.nextSibling.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$8 = _el$6.firstChild.nextSibling, _el$1 = _el$6.nextSibling.firstChild.nextSibling, _el$12 = _el$5.nextSibling.firstChild.nextSibling;\n _el$4.$$input = (event) => {\n props.onSearchInput(event.currentTarget.value);\n };\n _el$8.addEventListener(\"change\", (event) => {\n props.onSortChange(event.currentTarget.value);\n });\n _el$1.addEventListener(\"change\", (event) => {\n props.onUpdatedFromChange(event.currentTarget.value);\n });\n _el$12.addEventListener(\"change\", (event) => {\n props.onUpdatedToChange(event.currentTarget.value);\n });\n createRenderEffect(() => _el$4.value = props.search);\n createRenderEffect(() => _el$8.value = props.sort);\n createRenderEffect(() => _el$1.value = props.updatedFrom);\n createRenderEffect(() => _el$12.value = props.updatedTo);\n return _el$;\n })(), (() => {\n var _el$13 = _tmpl$2(), _el$16 = _el$13.firstChild.nextSibling.firstChild, _el$17 = _el$16.nextSibling;\n _el$16.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onQuickOpen();\n }\n };\n _el$16.$$input = (event) => {\n props.onQuickOpenInput(event.currentTarget.value);\n };\n addEventListener(_el$17, \"click\", props.onQuickOpen, true);\n createRenderEffect(() => _el$16.value = props.quickOpen);\n return _el$13;\n })()];\n}\nfunction FolderList(props) {\n return (() => {\n var _el$18 = _tmpl$4(), _el$20 = _el$18.firstChild.nextSibling;\n insert(_el$20, createComponent(Show, {\n get fallback() {\n return [\n (() => {\n var _el$22 = _tmpl$5();\n _el$22.$$click = () => {\n props.onSelect(null);\n };\n createRenderEffect(() => setAttribute(_el$22, \"data-selected\", !props.selectedFolderId ? \"true\" : void 0));\n return _el$22;\n })(),\n createComponent(For, {\n get each() {\n return props.folders;\n },\n children: (folder) => (() => {\n var _el$24 = _tmpl$7(), _el$25 = _el$24.firstChild, _el$26 = _el$25.nextSibling;\n _el$24.$$click = () => {\n props.onSelect(folder.id);\n };\n insert(_el$25, () => (folder.isFavourite ? \"★ \" : \"\") + (folder.name || folder.id));\n insert(_el$26, () => `${folder.documentCount} meetings`);\n createRenderEffect(() => setAttribute(_el$24, \"data-selected\", folder.id === props.selectedFolderId ? \"true\" : void 0));\n return _el$24;\n })()\n }),\n createComponent(Show, {\n get when() {\n return props.folders.length === 0;\n },\n get children() {\n return _tmpl$6();\n }\n })\n ];\n },\n get when() {\n return !props.error;\n },\n get children() {\n var _el$21 = _tmpl$3();\n insert(_el$21, () => props.error);\n return _el$21;\n }\n }));\n return _el$18;\n })();\n}\nfunction SavedFiltersPanel(props) {\n const canSaveCurrent = () => hasActiveFilters({\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n sort: props.sort,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$27 = _tmpl$8(), _el$29 = _el$27.firstChild.nextSibling, _el$30 = _el$29.firstChild, _el$31 = _el$29.nextSibling;\n _el$30.$$click = () => {\n props.onSaveCurrent();\n };\n insert(_el$31, createComponent(Show, {\n get when() {\n return props.savedFilters.length > 0;\n },\n get fallback() {\n return _tmpl$9();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.savedFilters;\n },\n children: (preset) => (() => {\n var _el$33 = _tmpl$0(), _el$34 = _el$33.firstChild, _el$35 = _el$34.firstChild, _el$36 = _el$35.nextSibling, _el$37 = _el$34.nextSibling;\n _el$34.$$click = () => {\n props.onApply(preset);\n };\n insert(_el$35, () => preset.label);\n insert(_el$36, () => currentFilterSummary({\n folders: props.folders,\n ...preset.filters\n }) || \"Saved workspace scope\");\n _el$37.$$click = () => {\n props.onRemove(preset.id);\n };\n return _el$33;\n })()\n });\n }\n }));\n createRenderEffect(() => _el$30.disabled = !canSaveCurrent());\n return _el$27;\n })();\n}\nfunction RecentMeetingsPanel(props) {\n return (() => {\n var _el$38 = _tmpl$1(), _el$40 = _el$38.firstChild.nextSibling;\n insert(_el$40, createComponent(Show, {\n get when() {\n return props.recentMeetings.length > 0;\n },\n get fallback() {\n return _tmpl$10();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.recentMeetings;\n },\n children: (meeting) => (() => {\n var _el$42 = _tmpl$7(), _el$43 = _el$42.firstChild, _el$44 = _el$43.nextSibling;\n _el$42.$$click = () => {\n props.onOpen(meeting);\n };\n insert(_el$43, () => meeting.title);\n insert(_el$44, () => meeting.updatedAt.slice(0, 10));\n return _el$42;\n })()\n });\n }\n }));\n return _el$38;\n })();\n}\nfunction MeetingList(props) {\n const summary = () => currentFilterSummary({\n folders: props.folders,\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$45 = _tmpl$12();\n insert(_el$45, createComponent(Show, {\n get fallback() {\n return createComponent(Show, {\n get fallback() {\n return (() => {\n var _el$47 = _tmpl$13();\n insert(_el$47, (() => {\n var _c$ = memo(() => !!summary());\n return () => _c$() ? `No meetings match ${summary()}.` : props.emptyHint || \"No meetings yet. Try Sync now.\";\n })());\n return _el$47;\n })();\n },\n get when() {\n return props.meetings.length > 0;\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.meetings;\n },\n children: (meeting) => (() => {\n var _el$48 = _tmpl$14(), _el$49 = _el$48.firstChild, _el$50 = _el$49.nextSibling, _el$51 = _el$50.nextSibling;\n _el$48.$$click = () => {\n props.onSelect(meeting.id);\n };\n insert(_el$49, () => meeting.title || meeting.id);\n insert(_el$50, (() => {\n var _c$2 = memo(() => !!meeting.tags.length);\n return () => _c$2() ? meeting.tags.map((tag) => `#${tag}`).join(\" \") : \"untagged\";\n })());\n insert(_el$51, (() => {\n var _c$3 = memo(() => !!meeting.updatedAt);\n return () => _c$3() ? meeting.updatedAt.slice(0, 10) : \"unknown\";\n })());\n createRenderEffect(() => setAttribute(_el$48, \"data-selected\", meeting.id === props.selectedMeetingId ? \"true\" : void 0));\n return _el$48;\n })()\n });\n }\n });\n },\n get when() {\n return props.error;\n },\n get children() {\n var _el$46 = _tmpl$11();\n insert(_el$46, () => props.error);\n return _el$46;\n }\n }));\n return _el$45;\n })();\n}\nfunction AppStatePanel(props) {\n const syncStatus = () => describeSyncStatus(props.appState?.sync ?? {});\n const authStatus = () => describeAuthStatus(props.appState?.auth);\n return (() => {\n var _el$52 = _tmpl$16(), _el$53 = _el$52.firstChild;\n _el$53.firstChild;\n var _el$56 = _el$53.nextSibling;\n insert(_el$53, createComponent(Show, {\n get fallback() {\n return _tmpl$17();\n },\n get when() {\n return props.appState;\n },\n children: (appState) => (() => {\n var _el$58 = _tmpl$18(), _el$59 = _el$58.firstChild, _el$61 = _el$59.firstChild.nextSibling, _el$62 = _el$59.nextSibling, _el$64 = _el$62.firstChild.nextSibling, _el$65 = _el$62.nextSibling, _el$67 = _el$65.firstChild.nextSibling, _el$68 = _el$65.nextSibling, _el$70 = _el$68.firstChild.nextSibling, _el$71 = _el$68.nextSibling, _el$73 = _el$71.firstChild.nextSibling, _el$74 = _el$71.nextSibling, _el$76 = _el$74.firstChild.nextSibling, _el$77 = _el$74.nextSibling, _el$79 = _el$77.firstChild.nextSibling, _el$80 = _el$77.nextSibling, _el$82 = _el$80.firstChild.nextSibling, _el$85 = _el$80.nextSibling.firstChild.nextSibling;\n insert(_el$61, () => appState().ui.surface);\n insert(_el$64, () => appState().ui.view);\n insert(_el$67, authStatus);\n insert(_el$70, syncStatus);\n insert(_el$73, (() => {\n var _c$4 = memo(() => !!appState().documents.loaded);\n return () => _c$4() ? String(appState().documents.count) : \"not loaded\";\n })());\n insert(_el$76, (() => {\n var _c$5 = memo(() => !!appState().folders.loaded);\n return () => _c$5() ? String(appState().folders.count) : \"not loaded\";\n })());\n insert(_el$79, (() => {\n var _c$6 = memo(() => !!appState().cache.loaded);\n return () => _c$6() ? `${appState().cache.transcriptCount} transcript sets` : appState().cache.configured ? \"configured\" : \"not configured\";\n })());\n insert(_el$82, (() => {\n var _c$7 = memo(() => !!appState().index.loaded);\n return () => _c$7() ? `${appState().index.meetingCount} meetings` : appState().index.available ? \"available\" : \"not built\";\n })());\n insert(_el$85, () => `${appState().automation.runCount} runs / ${appState().automation.pendingRunCount} pending`);\n return _el$58;\n })()\n }), null);\n insert(_el$53, createComponent(Show, {\n get when() {\n return props.appState?.auth.lastError;\n },\n get children() {\n var _el$55 = _tmpl$15();\n insert(_el$55, () => props.appState?.auth.lastError);\n return _el$55;\n }\n }), null);\n insert(_el$56, () => props.statusLabel);\n createRenderEffect(() => setAttribute(_el$56, \"data-tone\", props.statusTone));\n return _el$52;\n })();\n}\nfunction SecurityPanel(props) {\n return createComponent(Show, {\n get when() {\n return props.visible;\n },\n get children() {\n var _el$86 = _tmpl$19(), _el$89 = _el$86.firstChild.nextSibling.firstChild, _el$91 = _el$89.nextSibling.firstChild, _el$92 = _el$91.nextSibling;\n _el$89.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onUnlock();\n }\n };\n _el$89.$$input = (event) => {\n props.onPasswordChange(event.currentTarget.value);\n };\n addEventListener(_el$91, \"click\", props.onUnlock, true);\n addEventListener(_el$92, \"click\", props.onLock, true);\n createRenderEffect(() => _el$89.value = props.password);\n return _el$86;\n }\n });\n}\nfunction AuthPanel(props) {\n return (() => {\n var _el$93 = _tmpl$20(), _el$95 = _el$93.firstChild.nextSibling;\n insert(_el$95, createComponent(Show, {\n get fallback() {\n return _tmpl$21();\n },\n get when() {\n return props.auth;\n },\n children: (auth) => (() => {\n var _el$97 = _tmpl$26(), _el$98 = _el$97.firstChild, _el$99 = _el$98.firstChild, _el$101 = _el$99.firstChild.nextSibling, _el$102 = _el$99.nextSibling, _el$104 = _el$102.firstChild.nextSibling, _el$105 = _el$102.nextSibling, _el$107 = _el$105.firstChild.nextSibling, _el$108 = _el$105.nextSibling, _el$110 = _el$108.firstChild.nextSibling, _el$113 = _el$108.nextSibling.firstChild.nextSibling, _el$121 = _el$98.nextSibling, _el$123 = _el$121.nextSibling.firstChild, _el$124 = _el$123.nextSibling, _el$125 = _el$124.nextSibling, _el$126 = _el$125.nextSibling, _el$127 = _el$126.nextSibling, _el$128 = _el$127.nextSibling, _el$129 = _el$128.nextSibling, _el$130 = _el$129.nextSibling;\n insert(_el$101, () => authModeLabel(auth().mode));\n insert(_el$104, () => auth().apiKeyAvailable ? \"available\" : \"missing\");\n insert(_el$107, () => auth().storedSessionAvailable ? \"available\" : \"missing\");\n insert(_el$110, () => auth().supabaseAvailable ? \"available\" : \"missing\");\n insert(_el$113, () => auth().refreshAvailable ? \"available\" : \"missing\");\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().clientId;\n },\n get children() {\n var _el$114 = _tmpl$22();\n _el$114.firstChild;\n insert(_el$114, () => auth().clientId, null);\n return _el$114;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().signInMethod;\n },\n get children() {\n var _el$116 = _tmpl$23();\n _el$116.firstChild;\n insert(_el$116, () => auth().signInMethod, null);\n return _el$116;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().supabasePath;\n },\n get children() {\n var _el$118 = _tmpl$24();\n _el$118.firstChild;\n insert(_el$118, () => auth().supabasePath, null);\n return _el$118;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().lastError;\n },\n get children() {\n var _el$120 = _tmpl$25();\n insert(_el$120, () => auth().lastError);\n return _el$120;\n }\n }), _el$121);\n _el$123.$$input = (event) => {\n props.onApiKeyDraftChange(event.currentTarget.value);\n };\n addEventListener(_el$124, \"click\", props.onSaveApiKey, true);\n addEventListener(_el$125, \"click\", props.onImportDesktopSession, true);\n addEventListener(_el$126, \"click\", props.onRefresh, true);\n _el$127.$$click = () => {\n props.onSwitchMode(\"api-key\");\n };\n _el$128.$$click = () => {\n props.onSwitchMode(\"stored-session\");\n };\n _el$129.$$click = () => {\n props.onSwitchMode(\"supabase-file\");\n };\n addEventListener(_el$130, \"click\", props.onLogout, true);\n createRenderEffect((_p$) => {\n var _v$ = !auth().supabaseAvailable, _v$2 = !auth().storedSessionAvailable || !auth().refreshAvailable, _v$3 = !auth().apiKeyAvailable || auth().mode === \"api-key\", _v$4 = !auth().storedSessionAvailable || auth().mode === \"stored-session\", _v$5 = !auth().supabaseAvailable || auth().mode === \"supabase-file\", _v$6 = !auth().apiKeyAvailable && !auth().storedSessionAvailable;\n _v$ !== _p$.e && (_el$125.disabled = _p$.e = _v$);\n _v$2 !== _p$.t && (_el$126.disabled = _p$.t = _v$2);\n _v$3 !== _p$.a && (_el$127.disabled = _p$.a = _v$3);\n _v$4 !== _p$.o && (_el$128.disabled = _p$.o = _v$4);\n _v$5 !== _p$.i && (_el$129.disabled = _p$.i = _v$5);\n _v$6 !== _p$.n && (_el$130.disabled = _p$.n = _v$6);\n return _p$;\n }, {\n e: void 0,\n t: void 0,\n a: void 0,\n o: void 0,\n i: void 0,\n n: void 0\n });\n createRenderEffect(() => _el$123.value = props.apiKeyDraft);\n return _el$97;\n })()\n }));\n return _el$93;\n })();\n}\nfunction ExportJobsPanel(props) {\n return (() => {\n var _el$131 = _tmpl$27(), _el$133 = _el$131.firstChild.nextSibling;\n insert(_el$133, createComponent(Show, {\n get when() {\n return props.jobs.length > 0;\n },\n get fallback() {\n return _tmpl$28();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.jobs.slice(0, 6);\n },\n children: (job) => (() => {\n var _el$135 = _tmpl$31(), _el$136 = _el$135.firstChild, _el$137 = _el$136.firstChild, _el$138 = _el$137.firstChild, _el$139 = _el$138.firstChild, _el$140 = _el$138.nextSibling, _el$141 = _el$137.nextSibling, _el$142 = _el$136.nextSibling, _el$143 = _el$142.nextSibling;\n _el$143.firstChild;\n var _el$145 = _el$143.nextSibling;\n _el$145.firstChild;\n var _el$148 = _el$145.nextSibling;\n insert(_el$138, () => job.kind, _el$139);\n insert(_el$140, () => job.id);\n insert(_el$141, () => job.status);\n insert(_el$142, () => `Format: ${job.format} • ${scopeLabel(job.scope)} • ${job.itemCount > 0 ? `${job.completedCount}/${job.itemCount} items` : \"0 items\"} • Written: ${job.written}`);\n insert(_el$143, () => job.startedAt.slice(0, 19), null);\n insert(_el$145, () => job.outputDir, null);\n insert(_el$135, createComponent(Show, {\n get when() {\n return job.error;\n },\n get children() {\n var _el$147 = _tmpl$29();\n insert(_el$147, () => job.error);\n return _el$147;\n }\n }), _el$148);\n insert(_el$148, createComponent(Show, {\n get when() {\n return job.status !== \"running\";\n },\n get children() {\n var _el$149 = _tmpl$30();\n _el$149.$$click = () => {\n props.onRerun(job.id);\n };\n return _el$149;\n }\n }));\n createRenderEffect(() => setAttribute(_el$141, \"data-status\", job.status));\n return _el$135;\n })()\n });\n }\n }));\n return _el$131;\n })();\n}\nfunction AutomationRunsPanel(props) {\n return (() => {\n var _el$150 = _tmpl$32(), _el$152 = _el$150.firstChild.nextSibling;\n insert(_el$152, createComponent(Show, {\n get when() {\n return props.runs.length > 0;\n },\n get fallback() {\n return _tmpl$33();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.runs.slice(0, 6);\n },\n children: (run) => (() => {\n var _el$154 = _tmpl$36(), _el$155 = _el$154.firstChild, _el$156 = _el$155.firstChild, _el$157 = _el$156.firstChild, _el$158 = _el$157.nextSibling, _el$159 = _el$156.nextSibling, _el$160 = _el$155.nextSibling, _el$161 = _el$160.nextSibling, _el$165 = _el$161.nextSibling;\n insert(_el$157, () => run.actionName);\n insert(_el$158, () => `${run.ruleName} • ${run.id}`);\n insert(_el$159, () => run.status);\n insert(_el$160, () => `${run.title} • ${run.eventKind}`);\n insert(_el$161, () => `Started: ${run.startedAt.slice(0, 19)}`);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.prompt;\n },\n get children() {\n var _el$162 = _tmpl$29();\n insert(_el$162, () => run.prompt);\n return _el$162;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.result;\n },\n get children() {\n var _el$163 = _tmpl$29();\n insert(_el$163, () => run.result);\n return _el$163;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.error;\n },\n get children() {\n var _el$164 = _tmpl$29();\n insert(_el$164, () => run.error);\n return _el$164;\n }\n }), _el$165);\n insert(_el$165, createComponent(Show, {\n get when() {\n return run.status === \"pending\";\n },\n get children() {\n return [(() => {\n var _el$166 = _tmpl$34();\n _el$166.$$click = () => {\n props.onApprove(run.id);\n };\n return _el$166;\n })(), (() => {\n var _el$167 = _tmpl$35();\n _el$167.$$click = () => {\n props.onReject(run.id);\n };\n return _el$167;\n })()];\n }\n }));\n createRenderEffect(() => setAttribute(_el$159, \"data-status\", run.status));\n return _el$154;\n })()\n });\n }\n }));\n return _el$150;\n })();\n}\nfunction Workspace(props) {\n const parsedTab = () => parseWorkspaceTab(props.tab);\n const details = () => {\n if (!props.selectedMeeting) return null;\n return workspaceBody(props.bundle, props.selectedMeeting, parsedTab());\n };\n return [(() => {\n var _el$168 = _tmpl$37(), _el$169 = _el$168.firstChild;\n insert(_el$168, createComponent(For, {\n each: [\n \"notes\",\n \"transcript\",\n \"metadata\",\n \"raw\"\n ],\n children: (tab) => (() => {\n var _el$170 = _tmpl$38();\n _el$170.$$click = () => {\n props.onSelectTab(tab);\n };\n insert(_el$170, tab === \"notes\" ? \"Notes\" : tab === \"transcript\" ? \"Transcript\" : tab === \"metadata\" ? \"Metadata\" : \"Raw\");\n createRenderEffect(() => setAttribute(_el$170, \"data-selected\", parsedTab() === tab ? \"true\" : void 0));\n return _el$170;\n })()\n }), _el$169);\n return _el$168;\n })(), createComponent(Show, {\n get when() {\n return props.selectedMeeting;\n },\n get fallback() {\n return (() => {\n var _el$171 = _tmpl$39();\n insert(_el$171, () => props.detailError || \"Select a meeting to inspect its notes and transcript.\");\n return _el$171;\n })();\n },\n children: (meeting) => [(() => {\n var _el$172 = _tmpl$40(), _el$173 = _el$172.firstChild, _el$174 = _el$173.nextSibling, _el$175 = _el$174.nextSibling;\n insert(_el$173, () => `ID: ${meeting().meeting.id}`);\n insert(_el$174, () => `Source: ${meeting().meeting.noteContentSource}`);\n insert(_el$175, () => `Transcript: ${meeting().meeting.transcriptSegmentCount} segments`);\n return _el$172;\n })(), createComponent(Show, {\n get when() {\n return !props.detailError;\n },\n get fallback() {\n return (() => {\n var _el$184 = _tmpl$39();\n insert(_el$184, () => props.detailError);\n return _el$184;\n })();\n },\n get children() {\n var _el$176 = _tmpl$41(), _el$178 = _el$176.firstChild.firstChild, _el$180 = _el$178.firstChild.nextSibling, _el$182 = _el$178.nextSibling.firstChild, _el$183 = _el$182.nextSibling;\n insert(_el$180, () => metadataLines(meeting()));\n insert(_el$182, () => details()?.title);\n insert(_el$183, () => details()?.body);\n return _el$176;\n }\n })]\n })];\n}\ndelegateEvents([\n \"input\",\n \"keydown\",\n \"click\"\n]);\n//#endregion\n//#region src/web-app/App.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$ = /* @__PURE__ */ template(`<div class=shell><aside class=\"pane sidebar\"></aside><main class=\"pane detail\"><section class=toolbar><div class=toolbar-actions><button class=\"button button--primary\"type=button>Sync now</button><button class=\"button button--secondary\"type=button>Clear Filters</button><button class=\"button button--secondary\"type=button>Export Notes</button><button class=\"button button--secondary\"type=button>Export Transcripts</button></div><p>Solid-powered web workspace on top of the same local server, sync loop, and shared app contracts.`);\nfunction browserConfig() {\n return { passwordRequired: Boolean(window.__GRANOLA_SERVER__?.passwordRequired) };\n}\nasync function requestJson(path, init) {\n const response = await fetch(path, init);\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const error = typeof payload.error === \"string\" && payload.error.trim() ? payload.error : response.statusText || \"Request failed\";\n throw new Error(error);\n }\n return payload;\n}\nfunction App() {\n const startup = startupSelectionFromSearch(window.location.search);\n const initialPreferences = parseWorkspacePreferences(window.localStorage.getItem(granolaWebWorkspaceStorageKey));\n const [state, setState] = createStore({\n apiKeyDraft: \"\",\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n listError: \"\",\n meetingSource: \"live\",\n meetings: [],\n quickOpen: \"\",\n recentMeetings: initialPreferences.recentMeetings,\n savedFilters: initialPreferences.savedFilters,\n search: \"\",\n selectedFolderId: startup.folderId || null,\n selectedMeetingBundle: null,\n selectedMeetingId: startup.meetingId || null,\n selectedMeeting: null,\n serverLocked: browserConfig().passwordRequired,\n serverPassword: \"\",\n sort: \"updated-desc\",\n statusLabel: browserConfig().passwordRequired ? \"Server locked\" : \"Connecting…\",\n statusTone: browserConfig().passwordRequired ? \"error\" : \"idle\",\n updatedFrom: \"\",\n updatedTo: \"\",\n workspaceTab: parseWorkspaceTab(startup.workspaceTab)\n });\n let client = null;\n let unsubscribe;\n const setStatus = (label, tone = \"idle\") => {\n setState({\n statusLabel: label,\n statusTone: tone\n });\n };\n const updatePreferences = (updater) => {\n const next = updater({\n recentMeetings: state.recentMeetings,\n savedFilters: state.savedFilters\n });\n window.localStorage.setItem(granolaWebWorkspaceStorageKey, serialiseWorkspacePreferences(next));\n setState(\"recentMeetings\", next.recentMeetings);\n setState(\"savedFilters\", next.savedFilters);\n };\n const mergeAuthState = async (authState) => {\n if (!client) return;\n const nextState = client.getState();\n if (authState) {\n setState(\"appState\", {\n ...nextState,\n auth: authState\n });\n return;\n }\n try {\n setState(\"appState\", {\n ...nextState,\n auth: await client.inspectAuth()\n });\n } catch {\n setState(\"appState\", nextState);\n }\n };\n const detachClient = async () => {\n unsubscribe?.();\n unsubscribe = void 0;\n if (client) {\n await client.close().catch(() => void 0);\n client = null;\n }\n };\n const attachClient = async () => {\n await detachClient();\n client = await createGranolaServerClient(window.location.origin);\n setState(\"appState\", client.getState());\n unsubscribe = client.subscribe((event) => {\n setState(\"appState\", event.state);\n });\n await mergeAuthState();\n };\n const loadFolders = async (refresh = false) => {\n if (!client) return;\n try {\n setState(\"folderError\", \"\");\n const result = await client.listFolders({\n forceRefresh: refresh,\n limit: 100\n });\n setState(\"folders\", result.folders);\n if (state.selectedFolderId && !result.folders.some((folder) => folder.id === state.selectedFolderId)) setState(\"selectedFolderId\", null);\n } catch (error) {\n setState(\"folderError\", error instanceof Error ? error.message : String(error));\n setState(\"folders\", []);\n setState(\"selectedFolderId\", null);\n }\n };\n const loadAutomationRuns = async () => {\n if (!client) return;\n try {\n setState(\"automationRuns\", (await client.listAutomationRuns({ limit: 20 })).runs);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadMeeting = async (meetingId) => {\n if (!client) return;\n setState(\"selectedMeetingId\", meetingId);\n try {\n setState(\"detailError\", \"\");\n const bundle = await client.getMeeting(meetingId);\n setState(\"selectedMeetingBundle\", bundle);\n setState(\"selectedMeeting\", bundle.meeting);\n updatePreferences((preferences) => rememberRecentMeeting(preferences, bundle.meeting.meeting));\n } catch (error) {\n setState(\"selectedMeetingBundle\", null);\n setState(\"selectedMeeting\", null);\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadMeetings = async (options = {}) => {\n if (!client) return;\n try {\n setState(\"listError\", \"\");\n const result = await client.listMeetings({\n folderId: state.selectedFolderId || void 0,\n forceRefresh: options.refresh,\n limit: 100,\n search: state.search || void 0,\n sort: state.sort,\n updatedFrom: state.updatedFrom || void 0,\n updatedTo: state.updatedTo || void 0\n });\n const preferredMeetingId = options.preferredMeetingId ?? state.selectedMeetingId;\n const nextMeetingId = selectMeetingId(result.meetings, preferredMeetingId);\n setState(\"meetings\", result.meetings);\n setState(\"meetingSource\", result.source);\n setState(\"selectedMeetingId\", nextMeetingId);\n if (nextMeetingId) await loadMeeting(nextMeetingId);\n else {\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", \"\");\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n setState(\"listError\", message);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", message);\n }\n };\n const refreshAll = async (forceRefresh = false) => {\n if (!client) await attachClient();\n setStatus(forceRefresh ? \"Syncing…\" : \"Refreshing…\", \"busy\");\n if (forceRefresh) await client?.sync({\n forceRefresh: true,\n foreground: true\n });\n await Promise.all([\n loadFolders(forceRefresh),\n loadAutomationRuns(),\n mergeAuthState()\n ]);\n await loadMeetings({ refresh: forceRefresh });\n setState(\"serverLocked\", false);\n setStatus(forceRefresh ? \"Sync complete\" : state.meetingSource === \"index\" ? \"Loaded from index\" : \"Connected\", \"ok\");\n };\n const connectAndRefresh = async (forceRefresh = false) => {\n try {\n await refreshAll(forceRefresh);\n } catch (error) {\n setStatus(\"Connection failed\", \"error\");\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const quickOpenMeeting = async () => {\n if (!client) return;\n const query = state.quickOpen.trim();\n if (!query) {\n setStatus(\"Enter a title or id\", \"error\");\n return;\n }\n setStatus(\"Opening meeting…\", \"busy\");\n try {\n const bundle = await client.findMeeting(query);\n setState(\"selectedFolderId\", bundle.meeting.meeting.folders[0]?.id || null);\n setState(\"search\", \"\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n await loadMeetings({ preferredMeetingId: bundle.document.id });\n setStatus(\"Connected\", \"ok\");\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Quick open failed\", \"error\");\n }\n };\n const saveApiKey = async () => {\n if (!client) return;\n if (!state.apiKeyDraft.trim()) {\n setStatus(\"Enter a Granola API key\", \"error\");\n return;\n }\n setStatus(\"Saving API key…\", \"busy\");\n try {\n const auth = await client.loginAuth({ apiKey: state.apiKeyDraft.trim() });\n setState(\"apiKeyDraft\", \"\");\n await mergeAuthState(auth);\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"API key save failed\", \"error\");\n }\n };\n const importDesktopSession = async () => {\n if (!client) return;\n setStatus(\"Importing desktop session…\", \"busy\");\n try {\n await mergeAuthState(await client.loginAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Auth import failed\", \"error\");\n }\n };\n const refreshAuth = async () => {\n if (!client) return;\n setStatus(\"Refreshing session…\", \"busy\");\n try {\n await mergeAuthState(await client.refreshAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Refresh failed\", \"error\");\n }\n };\n const switchAuthMode = async (mode) => {\n if (!client) return;\n setStatus(\"Switching auth source…\", \"busy\");\n try {\n await mergeAuthState(await client.switchAuthMode(mode));\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Switch failed\", \"error\");\n }\n };\n const logout = async () => {\n if (!client) return;\n setStatus(\"Signing out…\", \"busy\");\n try {\n await mergeAuthState(await client.logoutAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Sign out failed\", \"error\");\n }\n };\n const exportNotes = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder notes…\" : \"Exporting notes…\", \"busy\");\n try {\n await client.exportNotes(\"markdown\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const clearFilters = async () => {\n setState(\"search\", \"\");\n setState(\"sort\", \"updated-desc\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n setState(\"selectedFolderId\", null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(\"Filters cleared\", \"ok\");\n };\n const saveCurrentFilter = () => {\n updatePreferences((preferences) => saveWorkspaceFilter(preferences, {\n folders: state.folders,\n search: state.search,\n selectedFolderId: state.selectedFolderId,\n sort: state.sort,\n updatedFrom: state.updatedFrom,\n updatedTo: state.updatedTo\n }, { idFactory: () => `filter-${Date.now()}` }));\n setStatus(\"Saved filter\", \"ok\");\n };\n const applySavedFilterPreset = async (id) => {\n const preset = state.savedFilters.find((candidate) => candidate.id === id);\n if (!preset) return;\n const nextFilters = applyWorkspaceFilter(preset);\n setState(\"search\", nextFilters.search);\n setState(\"selectedFolderId\", nextFilters.selectedFolderId);\n setState(\"sort\", nextFilters.sort);\n setState(\"updatedFrom\", nextFilters.updatedFrom);\n setState(\"updatedTo\", nextFilters.updatedTo);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(`Applied ${preset.label}`, \"ok\");\n };\n const removeSavedFilterPreset = (id) => {\n updatePreferences((preferences) => removeWorkspaceFilter(preferences, id));\n setStatus(\"Removed saved filter\", \"ok\");\n };\n const openRecentMeeting = async (meetingId, folderId) => {\n if (folderId !== void 0) {\n setState(\"selectedFolderId\", folderId || null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings({ preferredMeetingId: meetingId });\n return;\n }\n await loadMeeting(meetingId);\n };\n const meetingEmptyHint = () => {\n if (!state.appState) return \"Connect to the local server to load meetings.\";\n if (state.appState.auth.lastError) return \"Resolve auth first, then sync again.\";\n if (!state.appState.documents.loaded && !state.appState.sync.lastCompletedAt) return \"Run Sync now to populate your local meeting index.\";\n return \"Try a different folder or filter, or sync again.\";\n };\n const exportTranscripts = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder transcripts…\" : \"Exporting transcripts…\", \"busy\");\n try {\n await client.exportTranscripts(\"text\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const rerunJob = async (jobId) => {\n if (!client) return;\n setStatus(\"Rerunning export…\", \"busy\");\n try {\n await client.rerunExportJob(jobId);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Rerun failed\", \"error\");\n }\n };\n const resolveAutomationRun = async (id, decision) => {\n if (!client) return;\n setStatus(decision === \"approve\" ? \"Approving automation…\" : \"Rejecting automation…\", \"busy\");\n try {\n await client.resolveAutomationRun(id, decision);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Automation decision failed\", \"error\");\n }\n };\n const unlockServer = async () => {\n if (!state.serverPassword.trim()) {\n setStatus(\"Enter the server password\", \"error\");\n return;\n }\n setStatus(\"Unlocking server…\", \"busy\");\n try {\n await requestJson(\"/auth/unlock\", {\n body: JSON.stringify({ password: state.serverPassword }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n setState(\"serverPassword\", \"\");\n setState(\"serverLocked\", false);\n await connectAndRefresh(true);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unlock failed\", \"error\");\n }\n };\n const lockServer = async () => {\n try {\n await requestJson(\"/auth/lock\", { method: \"POST\" });\n } catch {}\n await detachClient();\n setState({\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n listError: \"\",\n meetings: [],\n selectedFolderId: null,\n selectedMeeting: null,\n selectedMeetingBundle: null,\n selectedMeetingId: null,\n serverLocked: true,\n serverPassword: \"\"\n });\n setStatus(\"Server locked\", \"error\");\n };\n createEffect(() => {\n const nextPath = buildBrowserUrlPath(window.location.href, {\n selectedFolderId: state.selectedFolderId,\n selectedMeetingId: state.selectedMeetingId,\n workspaceTab: state.workspaceTab\n });\n if (nextPath !== `${window.location.pathname}${window.location.search}${window.location.hash}`) history.replaceState(null, \"\", nextPath);\n });\n createEffect(() => {\n if (!state.appState?.automation.loaded || !client) return;\n loadAutomationRuns();\n });\n onMount(() => {\n const onKeyDown = (event) => {\n const target = event.target;\n if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return;\n const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);\n if (nextTab) setState(\"workspaceTab\", nextTab);\n };\n document.addEventListener(\"keydown\", onKeyDown);\n onCleanup(() => {\n document.removeEventListener(\"keydown\", onKeyDown);\n });\n if (!state.serverLocked) connectAndRefresh();\n });\n onCleanup(() => {\n detachClient();\n });\n return (() => {\n var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$6 = _el$4.firstChild.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling;\n insert(_el$2, createComponent(ToolbarFilters, {\n onQuickOpen: () => {\n quickOpenMeeting();\n },\n onQuickOpenInput: (value) => {\n setState(\"quickOpen\", value);\n },\n onSearchInput: (value) => {\n setState(\"search\", value.trim());\n loadMeetings();\n },\n onSortChange: (value) => {\n setState(\"sort\", value);\n loadMeetings();\n },\n onUpdatedFromChange: (value) => {\n setState(\"updatedFrom\", value);\n loadMeetings();\n },\n onUpdatedToChange: (value) => {\n setState(\"updatedTo\", value);\n loadMeetings();\n },\n get quickOpen() {\n return state.quickOpen;\n },\n get search() {\n return state.search;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(SavedFiltersPanel, {\n get folders() {\n return state.folders;\n },\n onApply: (preset) => {\n applySavedFilterPreset(preset.id);\n },\n onRemove: removeSavedFilterPreset,\n onSaveCurrent: saveCurrentFilter,\n get savedFilters() {\n return state.savedFilters;\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(RecentMeetingsPanel, {\n onOpen: (meeting) => {\n openRecentMeeting(meeting.id, meeting.folderId);\n },\n get recentMeetings() {\n return state.recentMeetings;\n }\n }), null);\n insert(_el$2, createComponent(FolderList, {\n get error() {\n return state.folderError;\n },\n get folders() {\n return state.folders;\n },\n onSelect: (folderId) => {\n setState(\"selectedFolderId\", folderId);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n loadMeetings();\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n }\n }), null);\n insert(_el$2, createComponent(MeetingList, {\n get error() {\n return state.listError;\n },\n get emptyHint() {\n return meetingEmptyHint();\n },\n get folders() {\n return state.folders;\n },\n get meetings() {\n return state.meetings;\n },\n onSelect: (meetingId) => {\n loadMeeting(meetingId);\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get selectedMeetingId() {\n return state.selectedMeetingId;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$3, createComponent(AppStatePanel, {\n get appState() {\n return state.appState;\n },\n get statusLabel() {\n return state.statusLabel;\n },\n get statusTone() {\n return state.statusTone;\n }\n }), _el$4);\n _el$6.$$click = () => {\n connectAndRefresh(true);\n };\n _el$7.$$click = () => {\n clearFilters();\n };\n _el$8.$$click = () => {\n exportNotes();\n };\n _el$9.$$click = () => {\n exportTranscripts();\n };\n insert(_el$3, createComponent(SecurityPanel, {\n onLock: () => {\n lockServer();\n },\n onPasswordChange: (value) => {\n setState(\"serverPassword\", value);\n },\n onUnlock: () => {\n unlockServer();\n },\n get password() {\n return state.serverPassword;\n },\n get visible() {\n return state.serverLocked;\n }\n }), null);\n insert(_el$3, createComponent(AuthPanel, {\n get apiKeyDraft() {\n return state.apiKeyDraft;\n },\n get auth() {\n return state.appState?.auth;\n },\n onApiKeyDraftChange: (value) => {\n setState(\"apiKeyDraft\", value);\n },\n onImportDesktopSession: () => {\n importDesktopSession();\n },\n onLogout: () => {\n logout();\n },\n onRefresh: () => {\n refreshAuth();\n },\n onSaveApiKey: () => {\n saveApiKey();\n },\n onSwitchMode: (mode) => {\n switchAuthMode(mode);\n }\n }), null);\n insert(_el$3, createComponent(ExportJobsPanel, {\n get jobs() {\n return state.appState?.exports.jobs || [];\n },\n onRerun: (jobId) => {\n rerunJob(jobId);\n }\n }), null);\n insert(_el$3, createComponent(AutomationRunsPanel, {\n onApprove: (runId) => {\n resolveAutomationRun(runId, \"approve\");\n },\n onReject: (runId) => {\n resolveAutomationRun(runId, \"reject\");\n },\n get runs() {\n return state.automationRuns;\n }\n }), null);\n insert(_el$3, createComponent(Workspace, {\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get detailError() {\n return state.detailError;\n },\n onSelectTab: (tab) => {\n setState(\"workspaceTab\", tab);\n },\n get selectedMeeting() {\n return state.selectedMeeting;\n },\n get tab() {\n return state.workspaceTab;\n }\n }), null);\n return _el$;\n })();\n}\ndelegateEvents([\"click\"]);\n//#endregion\n//#region src/web-app/main.tsx\n/** @jsxImportSource solid-js */\nvar root = document.getElementById(\"granola-web-root\");\nif (!root) throw new Error(\"Granola web root element not found\");\nrender(() => createComponent(App, {}), root);\n//#endregion\n";
6830
7582
  //#endregion
6831
7583
  //#region src/web/assets.ts
6832
7584
  const granolaWebAssetPaths = {
@@ -6906,6 +7658,26 @@ function parseAutomationRunStatus(value) {
6906
7658
  default: throw new Error("invalid automation status: expected completed, failed, pending, or skipped");
6907
7659
  }
6908
7660
  }
7661
+ function parseAutomationArtefactKind(value) {
7662
+ switch (value) {
7663
+ case null:
7664
+ case "": return;
7665
+ case "enrichment":
7666
+ case "notes": return value;
7667
+ default: throw new Error("invalid automation artefact kind: expected enrichment or notes");
7668
+ }
7669
+ }
7670
+ function parseAutomationArtefactStatus(value) {
7671
+ switch (value) {
7672
+ case null:
7673
+ case "": return;
7674
+ case "approved":
7675
+ case "generated":
7676
+ case "rejected":
7677
+ case "superseded": return value;
7678
+ default: throw new Error("invalid automation artefact status: expected approved, generated, rejected, or superseded");
7679
+ }
7680
+ }
6909
7681
  function folderIdFromBody(value) {
6910
7682
  return typeof value === "string" && value.trim() ? value.trim() : void 0;
6911
7683
  }
@@ -7161,6 +7933,15 @@ async function startGranolaServer(app, options = {}) {
7161
7933
  sendJson(response, await app.listAutomationMatches({ limit: parseInteger(url.searchParams.get("limit")) }), { headers: originHeaders });
7162
7934
  return;
7163
7935
  }
7936
+ if (method === "GET" && path === granolaTransportPaths.automationArtefacts) {
7937
+ sendJson(response, await app.listAutomationArtefacts({
7938
+ kind: parseAutomationArtefactKind(url.searchParams.get("kind")),
7939
+ limit: parseInteger(url.searchParams.get("limit")),
7940
+ meetingId: url.searchParams.get("meetingId")?.trim() || void 0,
7941
+ status: parseAutomationArtefactStatus(url.searchParams.get("status"))
7942
+ }), { headers: originHeaders });
7943
+ return;
7944
+ }
7164
7945
  if (method === "GET" && path === granolaTransportPaths.automationRuns) {
7165
7946
  sendJson(response, await app.listAutomationRuns({
7166
7947
  limit: parseInteger(url.searchParams.get("limit")),
@@ -7168,6 +7949,11 @@ async function startGranolaServer(app, options = {}) {
7168
7949
  }), { headers: originHeaders });
7169
7950
  return;
7170
7951
  }
7952
+ if (method === "POST" && path.endsWith("/rerun") && path.startsWith(`${granolaTransportPaths.automationArtefacts}/`)) {
7953
+ const id = decodeURIComponent(path.slice(`${granolaTransportPaths.automationArtefacts}/`.length, -6));
7954
+ sendJson(response, await app.rerunAutomationArtefact(id), { headers: originHeaders });
7955
+ return;
7956
+ }
7171
7957
  if (method === "POST" && (path.endsWith("/approve") || path.endsWith("/reject")) && path.startsWith(`${granolaTransportPaths.automationRuns}/`)) {
7172
7958
  const decision = path.endsWith("/approve") ? "approve" : "reject";
7173
7959
  const id = decodeURIComponent(path.slice(`${granolaTransportPaths.automationRuns}/`.length, -`/${decision}`.length));