granola-toolkit 0.58.0 → 0.60.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 (3) hide show
  1. package/README.md +9 -2
  2. package/dist/cli.js +482 -42
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { Input, ProcessTerminal, TUI, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
3
- import { existsSync } from "node:fs";
2
+ import { closeSync, existsSync, openSync } from "node:fs";
4
3
  import { access, appendFile, mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
4
+ import { execFile, spawn } from "node:child_process";
5
5
  import { homedir, platform, tmpdir } from "node:os";
6
6
  import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
7
7
  import { NodeHtmlMarkdown } from "node-html-markdown";
8
+ import { Input, ProcessTerminal, TUI, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
8
9
  import { createHash, randomUUID } from "node:crypto";
9
- import { execFile, spawn } from "node:child_process";
10
10
  import { promisify } from "node:util";
11
11
  import { createServer } from "node:http";
12
12
  //#region src/transport.ts
@@ -268,6 +268,13 @@ var GranolaServerClient = class GranolaServerClient {
268
268
  async listAutomationRules() {
269
269
  return await this.requestJson(granolaTransportPaths.automationRules);
270
270
  }
271
+ async saveAutomationRules(rules) {
272
+ return await this.requestJson(granolaTransportPaths.automationRules, {
273
+ body: JSON.stringify({ rules }),
274
+ headers: { "content-type": "application/json" },
275
+ method: "POST"
276
+ });
277
+ }
271
278
  async listAutomationMatches(options = {}) {
272
279
  const path = options.limit ? `${granolaTransportPaths.automationMatches}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.automationMatches;
273
280
  return await this.requestJson(path);
@@ -446,6 +453,34 @@ async function createGranolaServerClient(serverUrl, options = {}) {
446
453
  return await GranolaServerClient.connect(serverUrl, options);
447
454
  }
448
455
  //#endregion
456
+ //#region src/persistence/layout.ts
457
+ function defaultGranolaToolkitDataDirectory(targetPlatform = platform(), homeDirectory = homedir()) {
458
+ return targetPlatform === "darwin" ? join(homeDirectory, "Library", "Application Support", "granola-toolkit") : join(homeDirectory, ".config", "granola-toolkit");
459
+ }
460
+ function defaultGranolaToolkitPersistenceLayout(options = {}) {
461
+ const targetPlatform = options.platform ?? platform();
462
+ const dataDirectory = defaultGranolaToolkitDataDirectory(targetPlatform, options.homeDirectory ?? homedir());
463
+ return {
464
+ agentHarnessesFile: join(dataDirectory, "agent-harnesses.json"),
465
+ automationArtefactsFile: join(dataDirectory, "automation-artefacts.json"),
466
+ automationMatchesFile: join(dataDirectory, "automation-matches.jsonl"),
467
+ automationRulesFile: join(dataDirectory, "automation-rules.json"),
468
+ automationRunsFile: join(dataDirectory, "automation-runs.jsonl"),
469
+ apiKeyFile: join(dataDirectory, "api-key.txt"),
470
+ dataDirectory,
471
+ exportJobsFile: join(dataDirectory, "export-jobs.json"),
472
+ meetingIndexFile: join(dataDirectory, "meeting-index.json"),
473
+ pkmTargetsFile: join(dataDirectory, "pkm-targets.json"),
474
+ searchIndexFile: join(dataDirectory, "search-index.json"),
475
+ serviceLogFile: join(dataDirectory, "service.log"),
476
+ serviceStateFile: join(dataDirectory, "service.json"),
477
+ sessionFile: join(dataDirectory, "session.json"),
478
+ sessionStoreKind: targetPlatform === "darwin" ? "keychain" : "file",
479
+ syncEventsFile: join(dataDirectory, "sync-events.jsonl"),
480
+ syncStateFile: join(dataDirectory, "sync-state.json")
481
+ };
482
+ }
483
+ //#endregion
449
484
  //#region src/utils.ts
450
485
  const INVALID_FILENAME_CHARS = /[<>:"/\\|?*]/g;
451
486
  const CONTROL_CHARACTERS = /\p{Cc}/gu;
@@ -595,6 +630,159 @@ function transcriptSpeakerLabel(segment) {
595
630
  return source;
596
631
  }
597
632
  //#endregion
633
+ //#region src/service.ts
634
+ function defaultServiceStateFilePath() {
635
+ return defaultGranolaToolkitPersistenceLayout().serviceStateFile;
636
+ }
637
+ function defaultServiceLogFilePath() {
638
+ return defaultGranolaToolkitPersistenceLayout().serviceLogFile;
639
+ }
640
+ function createTimeoutSignal(timeoutMs) {
641
+ if (timeoutMs <= 0 || typeof AbortSignal.timeout !== "function") return;
642
+ return AbortSignal.timeout(timeoutMs);
643
+ }
644
+ function parseServiceRecord(value) {
645
+ if (!value || typeof value !== "object") return;
646
+ const candidate = value;
647
+ if (typeof candidate.hostname !== "string" || typeof candidate.logFile !== "string" || typeof candidate.passwordProtected !== "boolean" || typeof candidate.pid !== "number" || typeof candidate.port !== "number" || typeof candidate.protocolVersion !== "number" || typeof candidate.startedAt !== "string" || typeof candidate.syncEnabled !== "boolean" || typeof candidate.syncIntervalMs !== "number" || typeof candidate.url !== "string") return;
648
+ return candidate;
649
+ }
650
+ async function fetchServerInfo(serviceUrl, options = {}) {
651
+ const response = await (options.fetchImpl ?? globalThis.fetch)(new URL(granolaTransportPaths.serverInfo, serviceUrl), {
652
+ headers: { accept: "application/json" },
653
+ signal: createTimeoutSignal(options.timeoutMs ?? 1500)
654
+ });
655
+ if (!response.ok) throw new Error(`service responded with ${response.status} ${response.statusText}`.trim());
656
+ const info = await response.json();
657
+ if (info.product !== "granola-toolkit" || info.protocolVersion !== 2) throw new Error("service metadata did not match the expected Granola Toolkit protocol");
658
+ return info;
659
+ }
660
+ function currentGranolaCliInvocation(argv = process.argv, execPath = process.execPath) {
661
+ const entrypoint = argv[1];
662
+ return {
663
+ args: typeof entrypoint === "string" && /\.(?:[cm]?js|mjs|cjs|ts)$/.test(entrypoint) ? [entrypoint] : [],
664
+ file: execPath
665
+ };
666
+ }
667
+ function defaultGranolaServiceRecord() {
668
+ return {
669
+ logFile: defaultServiceLogFilePath(),
670
+ serviceStateFile: defaultServiceStateFilePath()
671
+ };
672
+ }
673
+ function isGranolaServiceProcessRunning(pid) {
674
+ if (!Number.isInteger(pid) || pid < 1) return false;
675
+ try {
676
+ process.kill(pid, 0);
677
+ return true;
678
+ } catch (error) {
679
+ if (error && typeof error === "object" && "code" in error && error.code === "EPERM") return true;
680
+ return false;
681
+ }
682
+ }
683
+ async function readGranolaServiceRecord(serviceStateFile = defaultServiceStateFilePath()) {
684
+ try {
685
+ const raw = await readUtf8(serviceStateFile);
686
+ return parseServiceRecord(JSON.parse(raw));
687
+ } catch {
688
+ return;
689
+ }
690
+ }
691
+ async function writeGranolaServiceRecord(record, serviceStateFile = defaultServiceStateFilePath()) {
692
+ await writeTextFile$1(serviceStateFile, `${JSON.stringify(record, null, 2)}\n`);
693
+ }
694
+ async function removeGranolaServiceRecord(serviceStateFile = defaultServiceStateFilePath()) {
695
+ await rm(serviceStateFile, { force: true });
696
+ }
697
+ async function inspectGranolaService(options = {}) {
698
+ const record = await readGranolaServiceRecord(options.serviceStateFile);
699
+ if (!record) return { kind: "missing" };
700
+ if (!(options.isProcessRunning ?? isGranolaServiceProcessRunning)(record.pid)) {
701
+ if (options.cleanupStale !== false) await removeGranolaServiceRecord(options.serviceStateFile);
702
+ return {
703
+ kind: "stale",
704
+ record
705
+ };
706
+ }
707
+ try {
708
+ return {
709
+ info: await fetchServerInfo(record.url, {
710
+ fetchImpl: options.fetchImpl,
711
+ timeoutMs: options.timeoutMs
712
+ }),
713
+ kind: "running",
714
+ record
715
+ };
716
+ } catch (error) {
717
+ return {
718
+ error: error instanceof Error ? error : new Error(String(error)),
719
+ kind: "unreachable",
720
+ record
721
+ };
722
+ }
723
+ }
724
+ async function discoverGranolaService(options = {}) {
725
+ const status = await inspectGranolaService(options);
726
+ return status.kind === "running" ? status.record : void 0;
727
+ }
728
+ async function waitForGranolaService(options = {}) {
729
+ const intervalMs = Math.max(100, options.intervalMs ?? 200);
730
+ const timeoutMs = Math.max(intervalMs, options.timeoutMs ?? 1e4);
731
+ const startedAt = Date.now();
732
+ let lastStatus = { kind: "missing" };
733
+ while (Date.now() - startedAt <= timeoutMs) {
734
+ lastStatus = await inspectGranolaService({
735
+ cleanupStale: false,
736
+ fetchImpl: options.fetchImpl,
737
+ isProcessRunning: options.isProcessRunning,
738
+ serviceStateFile: options.serviceStateFile,
739
+ timeoutMs: options.timeoutMs
740
+ });
741
+ if (lastStatus.kind === "running") return lastStatus;
742
+ await new Promise((resolve) => {
743
+ setTimeout(resolve, intervalMs);
744
+ });
745
+ }
746
+ return lastStatus;
747
+ }
748
+ async function readGranolaServiceLogTail(options = {}) {
749
+ const logFile = options.logFile ?? defaultServiceLogFilePath();
750
+ try {
751
+ await access(logFile);
752
+ } catch {
753
+ return;
754
+ }
755
+ const raw = await readFile(logFile, "utf8");
756
+ const byteLimit = Math.max(256, options.bytes ?? 4e3);
757
+ return raw.slice(-byteLimit).trim() || void 0;
758
+ }
759
+ async function spawnGranolaServiceProcess(options = {}) {
760
+ const cliInvocation = options.cliInvocation ?? currentGranolaCliInvocation();
761
+ const stdoutFd = openSync(options.logFile ?? defaultServiceLogFilePath(), "a");
762
+ const spawnImpl = options.spawnImpl ?? spawn;
763
+ try {
764
+ const child = spawnImpl(cliInvocation.file, [
765
+ ...cliInvocation.args,
766
+ "service",
767
+ "run",
768
+ ...options.commandArgs ?? []
769
+ ], {
770
+ cwd: options.cwd ?? process.cwd(),
771
+ detached: true,
772
+ env: options.env ?? process.env,
773
+ stdio: [
774
+ "ignore",
775
+ stdoutFd,
776
+ stdoutFd
777
+ ]
778
+ });
779
+ child.unref();
780
+ return child.pid ?? 0;
781
+ } finally {
782
+ closeSync(stdoutFd);
783
+ }
784
+ }
785
+ //#endregion
598
786
  //#region src/meeting-roles.ts
599
787
  function normaliseKey(value) {
600
788
  return (value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
@@ -2956,7 +3144,7 @@ function attachHelp() {
2956
3144
  return `Granola attach
2957
3145
 
2958
3146
  Usage:
2959
- granola attach <url> [options]
3147
+ granola attach [url] [options]
2960
3148
 
2961
3149
  Options:
2962
3150
  --meeting <id> Open the workspace focused on a specific meeting
@@ -2974,39 +3162,19 @@ const attachCommand = {
2974
3162
  help: attachHelp,
2975
3163
  name: "attach",
2976
3164
  async run({ commandArgs, commandFlags }) {
2977
- const serverUrl = commandArgs[0];
2978
- if (!serverUrl?.trim()) throw new Error("attach requires a server URL, for example http://127.0.0.1:4123");
3165
+ let serverUrl = commandArgs[0];
3166
+ if (!serverUrl?.trim()) {
3167
+ const discovered = await discoverGranolaService();
3168
+ if (!discovered) throw new Error("attach requires a server URL or a running background service. Start one with `granola service start`.");
3169
+ serverUrl = discovered.url;
3170
+ console.log(`Attaching to ${serverUrl}`);
3171
+ }
2979
3172
  const initialMeetingId = typeof commandFlags.meeting === "string" && commandFlags.meeting.trim() ? commandFlags.meeting.trim() : void 0;
2980
- return await runGranolaTui(await createGranolaServerClient(serverUrl, { password: typeof commandFlags.password === "string" && commandFlags.password.trim() ? commandFlags.password.trim() : void 0 }), { initialMeetingId });
3173
+ const password = typeof commandFlags.password === "string" && commandFlags.password.trim() ? commandFlags.password.trim() : void 0;
3174
+ return await runGranolaTui(await createGranolaServerClient(serverUrl, { password }), { initialMeetingId });
2981
3175
  }
2982
3176
  };
2983
3177
  //#endregion
2984
- //#region src/persistence/layout.ts
2985
- function defaultGranolaToolkitDataDirectory(targetPlatform = platform(), homeDirectory = homedir()) {
2986
- return targetPlatform === "darwin" ? join(homeDirectory, "Library", "Application Support", "granola-toolkit") : join(homeDirectory, ".config", "granola-toolkit");
2987
- }
2988
- function defaultGranolaToolkitPersistenceLayout(options = {}) {
2989
- const targetPlatform = options.platform ?? platform();
2990
- const dataDirectory = defaultGranolaToolkitDataDirectory(targetPlatform, options.homeDirectory ?? homedir());
2991
- return {
2992
- agentHarnessesFile: join(dataDirectory, "agent-harnesses.json"),
2993
- automationArtefactsFile: join(dataDirectory, "automation-artefacts.json"),
2994
- automationMatchesFile: join(dataDirectory, "automation-matches.jsonl"),
2995
- automationRulesFile: join(dataDirectory, "automation-rules.json"),
2996
- automationRunsFile: join(dataDirectory, "automation-runs.jsonl"),
2997
- apiKeyFile: join(dataDirectory, "api-key.txt"),
2998
- dataDirectory,
2999
- exportJobsFile: join(dataDirectory, "export-jobs.json"),
3000
- meetingIndexFile: join(dataDirectory, "meeting-index.json"),
3001
- pkmTargetsFile: join(dataDirectory, "pkm-targets.json"),
3002
- searchIndexFile: join(dataDirectory, "search-index.json"),
3003
- sessionFile: join(dataDirectory, "session.json"),
3004
- sessionStoreKind: targetPlatform === "darwin" ? "keychain" : "file",
3005
- syncEventsFile: join(dataDirectory, "sync-events.jsonl"),
3006
- syncStateFile: join(dataDirectory, "sync-state.json")
3007
- };
3008
- }
3009
- //#endregion
3010
3178
  //#region src/agent-harnesses.ts
3011
3179
  function stringArray$1(value) {
3012
3180
  if (!Array.isArray(value)) return;
@@ -4764,6 +4932,10 @@ var FileAutomationRuleStore = class {
4764
4932
  return [];
4765
4933
  }
4766
4934
  }
4935
+ async writeRules(rules) {
4936
+ await mkdir(dirname(this.filePath), { recursive: true });
4937
+ await writeFile(this.filePath, `${JSON.stringify({ rules }, null, 2)}\n`, "utf8");
4938
+ }
4767
4939
  };
4768
4940
  function defaultAutomationRulesFilePath() {
4769
4941
  return defaultGranolaToolkitPersistenceLayout().automationRulesFile;
@@ -7313,6 +7485,15 @@ var GranolaApp = class {
7313
7485
  this.setUiState({ view: "idle" });
7314
7486
  return { rules: rules.map((rule) => this.cloneAutomationRule(rule)) };
7315
7487
  }
7488
+ async saveAutomationRules(rules) {
7489
+ if (!this.deps.automationRuleStore) throw new Error("automation rule store is not configured");
7490
+ await this.deps.automationRuleStore.writeRules(rules.map((rule) => this.cloneAutomationRule(rule)));
7491
+ this.#automationRules = rules.map((rule) => this.cloneAutomationRule(rule));
7492
+ this.refreshAutomationState();
7493
+ this.setUiState({ view: "idle" });
7494
+ this.emitStateUpdate();
7495
+ return { rules: this.#automationRules.map((rule) => this.cloneAutomationRule(rule)) };
7496
+ }
7316
7497
  async listAutomationMatches(options = {}) {
7317
7498
  const limit = options.limit ?? 20;
7318
7499
  const matches = this.deps.automationMatchStore ? await this.deps.automationMatchStore.readMatches(limit) : this.#automationMatches.slice(-limit).reverse();
@@ -9247,12 +9428,17 @@ async function view$1(query, commandFlags, globalFlags) {
9247
9428
  return 0;
9248
9429
  }
9249
9430
  //#endregion
9250
- //#region src/init.ts
9251
- const DEFAULT_MODELS = {
9431
+ //#region src/agent-defaults.ts
9432
+ const defaultGranolaAgentModels = {
9252
9433
  codex: "gpt-5-codex",
9253
9434
  openai: "gpt-5-mini",
9254
9435
  openrouter: "openai/gpt-5-mini"
9255
9436
  };
9437
+ function defaultGranolaAgentModel(provider, explicitModel) {
9438
+ return explicitModel?.trim() || defaultGranolaAgentModels[provider];
9439
+ }
9440
+ //#endregion
9441
+ //#region src/init.ts
9256
9442
  const TEAM_PROMPT = `# Team Notes Harness
9257
9443
 
9258
9444
  Turn this meeting into concise internal notes for the team.
@@ -9283,7 +9469,7 @@ function quoteTomlString(value) {
9283
9469
  return JSON.stringify(value);
9284
9470
  }
9285
9471
  function defaultModel(provider, explicitModel) {
9286
- return explicitModel?.trim() || DEFAULT_MODELS[provider];
9472
+ return defaultGranolaAgentModel(provider, explicitModel);
9287
9473
  }
9288
9474
  function configTemplate(options) {
9289
9475
  return [
@@ -9542,8 +9728,8 @@ async function openExternalUrl(url, options = {}) {
9542
9728
  }
9543
9729
  //#endregion
9544
9730
  //#region src/web/generated.ts
9545
- const granolaWebClientCss = ":root {\n --bg: #f2ede2;\n --panel: rgba(255, 252, 247, 0.86);\n --panel-strong: #fffaf2;\n --line: rgba(36, 39, 44, 0.12);\n --ink: #1d242c;\n --muted: #5d6b77;\n --accent: #0d6a6d;\n --accent-soft: rgba(13, 106, 109, 0.12);\n --warm: #a34f2f;\n --ok: #246b4f;\n --error: #9d2c2c;\n --shadow: 0 24px 80px rgba(40, 32, 16, 0.12);\n --radius: 24px;\n --mono: \"SF Mono\", \"IBM Plex Mono\", \"Cascadia Code\", monospace;\n --serif: \"Iowan Old Style\", \"Palatino Linotype\", \"Book Antiqua\", Georgia, serif;\n --sans: \"Avenir Next\", \"Segoe UI\", sans-serif;\n}\n\n* {\n box-sizing: border-box;\n}\n\nbody {\n margin: 0;\n min-height: 100vh;\n font-family: var(--sans);\n color: var(--ink);\n background:\n radial-gradient(circle at top left, rgba(163, 79, 47, 0.18), transparent 32%),\n radial-gradient(circle at right 12%, rgba(13, 106, 109, 0.16), transparent 28%),\n linear-gradient(180deg, #f8f2e8 0%, var(--bg) 100%);\n}\n\nbutton,\ninput,\nselect {\n font: inherit;\n}\n\n#granola-web-root {\n min-height: 100vh;\n}\n\n.shell {\n display: grid;\n grid-template-columns: 320px minmax(0, 1fr);\n gap: 18px;\n min-height: 100vh;\n padding: 24px;\n}\n\n.pane {\n background: var(--panel);\n backdrop-filter: blur(18px);\n border: 1px solid var(--line);\n border-radius: var(--radius);\n box-shadow: var(--shadow);\n}\n\n.sidebar {\n display: grid;\n grid-template-rows: auto auto auto auto 1fr;\n overflow: hidden;\n}\n\n.hero,\n.toolbar,\n.detail-head,\n.folder-panel {\n padding: 22px 24px;\n border-bottom: 1px solid var(--line);\n}\n\n.hero h1 {\n margin: 0;\n font-family: var(--serif);\n font-size: clamp(2rem, 3vw, 2.8rem);\n font-weight: 600;\n letter-spacing: -0.04em;\n}\n\n.hero p,\n.toolbar p {\n margin: 8px 0 0;\n color: var(--muted);\n line-height: 1.5;\n}\n\n.search,\n.select,\n.field-input,\n.input {\n width: 100%;\n margin-top: 16px;\n padding: 12px 14px;\n border: 1px solid var(--line);\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.7);\n color: var(--ink);\n}\n\n.field-row {\n display: grid;\n gap: 10px;\n margin-top: 12px;\n}\n\n.field-row--inline {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n}\n\n.field-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.folder-panel {\n display: grid;\n gap: 14px;\n}\n\n.folder-panel__head h2 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.folder-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.folder-list,\n.jobs-list,\n.saved-filter-list {\n display: grid;\n gap: 10px;\n}\n\n.saved-filter-actions {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n\n.folder-row,\n.meeting-row,\n.saved-filter-card__main {\n width: 100%;\n display: grid;\n gap: 4px;\n text-align: left;\n padding: 12px 14px;\n border: 1px solid transparent;\n border-radius: 16px;\n background: rgba(255, 255, 255, 0.72);\n color: inherit;\n cursor: pointer;\n transition:\n transform 140ms ease,\n border-color 140ms ease,\n background 140ms ease;\n}\n\n.meeting-row {\n margin: 0 0 10px;\n padding: 14px 16px;\n border-radius: 18px;\n}\n\n.saved-filter-card {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px;\n align-items: start;\n}\n\n.saved-filter-card__main {\n margin: 0;\n}\n\n.saved-filter-card__remove {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 12px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n cursor: pointer;\n font-weight: 700;\n}\n\n.folder-row:hover,\n.folder-row[data-selected=\"true\"],\n.saved-filter-card__main:hover {\n transform: translateY(-1px);\n border-color: rgba(163, 79, 47, 0.26);\n background: var(--panel-strong);\n}\n\n.meeting-row:hover,\n.meeting-row[data-selected=\"true\"] {\n transform: translateY(-1px);\n border-color: rgba(13, 106, 109, 0.25);\n background: var(--panel-strong);\n}\n\n.folder-row__title,\n.job-card__title,\n.saved-filter-card__title {\n font-weight: 700;\n}\n\n.meeting-row__title {\n font-weight: 600;\n}\n\n.folder-row__meta,\n.meeting-row__meta,\n.auth-card__meta,\n.job-card__meta,\n.folder-empty,\n.job-empty,\n.meeting-empty,\n.saved-filter-card__meta {\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.folder-empty--error,\n.meeting-empty--error,\n.auth-card__error {\n color: var(--error);\n}\n\n.meeting-list {\n padding: 14px;\n overflow: auto;\n}\n\n.detail {\n display: grid;\n grid-template-rows: auto auto 1fr;\n min-width: 0;\n}\n\n.detail-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 18px;\n}\n\n.detail-head h2 {\n margin: 0;\n font-family: var(--serif);\n font-size: clamp(1.8rem, 2.4vw, 2.4rem);\n font-weight: 600;\n}\n\n.state-badge {\n padding: 10px 14px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.92rem;\n font-weight: 700;\n}\n\n.state-badge[data-tone=\"busy\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.state-badge[data-tone=\"error\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.state-badge[data-tone=\"ok\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 14px;\n}\n\n.toolbar-actions,\n.auth-card__actions,\n.job-card__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n\n.toolbar-form {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: 10px;\n width: min(440px, 100%);\n}\n\n.security-panel,\n.auth-panel,\n.jobs-panel {\n padding: 0 24px 18px;\n}\n\n.security-panel__head h3,\n.auth-panel__head h3,\n.jobs-panel__head h3 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.security-panel__head p,\n.auth-panel__head p,\n.jobs-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.security-panel__body,\n.auth-panel__body {\n display: grid;\n gap: 12px;\n margin-top: 14px;\n}\n\n.auth-card,\n.job-card {\n display: grid;\n gap: 12px;\n padding: 14px 16px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.72);\n}\n\n.job-card--button {\n width: 100%;\n text-align: left;\n cursor: pointer;\n}\n\n.job-card--button[data-selected=\"true\"] {\n border-color: rgba(13, 106, 109, 0.26);\n background: var(--panel-strong);\n}\n\n.job-card__head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n}\n\n.job-card__status {\n padding: 6px 10px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.82rem;\n font-weight: 700;\n}\n\n.job-card__status[data-status=\"running\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"failed\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"error\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"completed\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.job-card__status[data-status=\"warning\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"approved\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.job-card__status[data-status=\"generated\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"rejected\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"superseded\"] {\n background: rgba(93, 107, 119, 0.12);\n color: var(--muted);\n}\n\n.workspace-tabs {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.workspace-tab,\n.button {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 14px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--ink);\n cursor: pointer;\n font-weight: 700;\n}\n\n.button {\n padding: 12px 16px;\n}\n\n.workspace-tab[data-selected=\"true\"],\n.button--primary {\n background: var(--ink);\n color: #fff;\n border-color: var(--ink);\n}\n\n.button--secondary {\n background: rgba(255, 255, 255, 0.72);\n}\n\n.button:disabled {\n cursor: not-allowed;\n opacity: 0.56;\n}\n\n.workspace-hint {\n margin-left: auto;\n color: var(--muted);\n font-size: 0.86rem;\n}\n\n.status-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 14px;\n}\n\n.status-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.detail-chip {\n padding: 10px 12px;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n color: var(--muted);\n font-size: 0.88rem;\n}\n\n.detail-body {\n padding: 0 24px 24px;\n overflow: auto;\n}\n\n.review-panel {\n padding: 0 24px 24px;\n}\n\n.harness-panel {\n padding: 0 24px 24px;\n}\n\n.harness-grid {\n display: grid;\n grid-template-columns: minmax(260px, 320px) minmax(0, 1fr);\n gap: 18px;\n}\n\n.harness-toolbar,\n.harness-status-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 16px;\n}\n\n.harness-status-row h2 {\n margin: 0;\n font-size: 1.1rem;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n\n.harness-test-result {\n display: grid;\n gap: 14px;\n margin-top: 16px;\n}\n\n.review-body {\n display: grid;\n gap: 18px;\n}\n\n.review-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 18px;\n}\n\n.workspace-grid {\n display: grid;\n grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);\n gap: 18px;\n}\n\n.detail-section {\n margin-bottom: 20px;\n padding: 20px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n border-radius: 20px;\n}\n\n.detail-section h2 {\n margin: 0 0 14px;\n font-size: 1rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-section h3 {\n margin: 18px 0 10px;\n font-size: 0.95rem;\n}\n\n.detail-pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--mono);\n line-height: 1.55;\n}\n\n.detail-list {\n margin: 0;\n padding-left: 18px;\n display: grid;\n gap: 10px;\n}\n\n.field-input--plain,\n.review-textarea {\n width: 100%;\n margin-top: 0;\n padding: 12px 14px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.92);\n color: var(--ink);\n}\n\n.review-textarea {\n min-height: 220px;\n resize: vertical;\n}\n\n.review-textarea--summary {\n min-height: 96px;\n}\n\n.review-history {\n margin-bottom: 0;\n}\n\n.empty {\n margin: 24px;\n padding: 24px;\n border-radius: 20px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n}\n\n@media (max-width: 1024px) {\n .shell,\n .harness-grid,\n .review-grid,\n .workspace-grid {\n grid-template-columns: 1fr;\n }\n}\n/*$vite$:1*/";
9546
- 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 automationEvaluate: \"/automation/evaluate\",\n automationHarnesses: \"/automation/harnesses\",\n automationHarnessExplain: \"/automation/harnesses/explain\",\n automationMatches: \"/automation/matches\",\n automationArtefacts: \"/automation/artefacts\",\n automationRules: \"/automation/rules\",\n automationRuns: \"/automation/runs\",\n events: \"/events\",\n exportJobs: \"/exports/jobs\",\n exportNotes: \"/exports/notes\",\n exportTranscripts: \"/exports/transcripts\",\n folderResolve: \"/folders/resolve\",\n folders: \"/folders\",\n health: \"/health\",\n meetingResolve: \"/meetings/resolve\",\n meetings: \"/meetings\",\n processingIssues: \"/processing/issues\",\n root: \"/\",\n serverInfo: \"/server/info\",\n syncRun: \"/sync\",\n syncEvents: \"/sync/events\",\n state: \"/state\"\n};\nfunction appendSearchParams(path, params) {\n const url = new URL(path, \"http://localhost\");\n for (const [key, value] of Object.entries(params)) {\n if (value === void 0 || value === false || value === \"\") continue;\n url.searchParams.set(key, String(value));\n }\n return `${url.pathname}${url.search}`;\n}\nfunction granolaMeetingPath(id) {\n return `${granolaTransportPaths.meetings}/${encodeURIComponent(id)}`;\n}\nfunction granolaMeetingResolvePath(query, options = {}) {\n return appendSearchParams(granolaTransportPaths.meetingResolve, {\n includeTranscript: options.includeTranscript ? \"true\" : void 0,\n q: query\n });\n}\nfunction granolaMeetingsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.meetings, {\n folderId: options.folderId,\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search,\n sort: options.sort,\n updatedFrom: options.updatedFrom,\n updatedTo: options.updatedTo\n });\n}\nfunction granolaFolderPath(id) {\n return `${granolaTransportPaths.folders}/${encodeURIComponent(id)}`;\n}\nfunction granolaFolderResolvePath(query) {\n return appendSearchParams(granolaTransportPaths.folderResolve, { q: query });\n}\nfunction granolaFoldersPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.folders, {\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search\n });\n}\nfunction granolaExportJobsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.exportJobs, { limit: options.limit });\n}\nfunction granolaAutomationRunsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationRuns, {\n limit: options.limit,\n status: options.status\n });\n}\nfunction granolaAutomationHarnessExplainPath(meetingId) {\n return appendSearchParams(granolaTransportPaths.automationHarnessExplain, { meetingId });\n}\nfunction granolaAutomationArtefactsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationArtefacts, {\n kind: options.kind,\n limit: options.limit,\n meetingId: options.meetingId,\n status: options.status\n });\n}\nfunction granolaAutomationRunDecisionPath(id, decision) {\n return `${granolaTransportPaths.automationRuns}/${encodeURIComponent(id)}/${decision}`;\n}\nfunction granolaAutomationArtefactRerunPath(id) {\n return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}/rerun`;\n}\nfunction granolaAutomationArtefactPath(id) {\n return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}`;\n}\nfunction granolaAutomationArtefactDecisionPath(id, decision) {\n return `${granolaAutomationArtefactPath(id)}/${decision}`;\n}\nfunction granolaAutomationArtefactUpdatePath(id) {\n return `${granolaAutomationArtefactPath(id)}/update`;\n}\nfunction granolaProcessingIssuesPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.processingIssues, {\n limit: options.limit,\n meetingId: options.meetingId,\n severity: options.severity\n });\n}\nfunction granolaProcessingIssueRecoverPath(id) {\n return `${granolaTransportPaths.processingIssues}/${encodeURIComponent(id)}/recover`;\n}\nfunction granolaExportJobRerunPath(id) {\n return `${granolaTransportPaths.exportJobs}/${encodeURIComponent(id)}/rerun`;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/checkPrivateRedeclaration.js\nfunction _checkPrivateRedeclaration(e, t) {\n if (t.has(e)) throw new TypeError(\"Cannot initialize the same private elements twice on an object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldInitSpec.js\nfunction _classPrivateFieldInitSpec(e, t, a) {\n _checkPrivateRedeclaration(e, t), t.set(e, a);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/typeof.js\nfunction _typeof(o) {\n \"@babel/helpers - typeof\";\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function(o) {\n return typeof o;\n } : function(o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/defineProperty.js\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/assertClassBrand.js\nfunction _assertClassBrand(e, t, n) {\n if (\"function\" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;\n throw new TypeError(\"Private element is not present on this object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldSet2.js\nfunction _classPrivateFieldSet2(s, a, r) {\n return s.set(_assertClassBrand(s, a), r), r;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldGet2.js\nfunction _classPrivateFieldGet2(s, a) {\n return s.get(_assertClassBrand(s, a));\n}\n//#endregion\n//#region src/server/client.ts\nfunction resolveFetchImpl(fetchImpl) {\n if (fetchImpl) return ((input, init) => fetchImpl(input, init));\n return ((input, init) => globalThis.fetch(input, init));\n}\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, resolveFetchImpl(options.fetchImpl));\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 = resolveFetchImpl(options.fetchImpl);\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 listAgentHarnesses() {\n return await this.requestJson(granolaTransportPaths.automationHarnesses);\n }\n async saveAgentHarnesses(harnesses) {\n return await this.requestJson(granolaTransportPaths.automationHarnesses, {\n body: JSON.stringify({ harnesses }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async explainAgentHarnesses(meetingId) {\n return await this.requestJson(granolaAutomationHarnessExplainPath(meetingId));\n }\n async listAutomationArtefacts(options = {}) {\n return await this.requestJson(granolaAutomationArtefactsPath(options));\n }\n async evaluateAutomationCases(cases, options) {\n return await this.requestJson(granolaTransportPaths.automationEvaluate, {\n body: JSON.stringify({\n cases,\n options\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async listProcessingIssues(options = {}) {\n return await this.requestJson(granolaProcessingIssuesPath(options));\n }\n async getAutomationArtefact(id) {\n return await this.requestJson(granolaAutomationArtefactPath(id));\n }\n async listAutomationRules() {\n return await this.requestJson(granolaTransportPaths.automationRules);\n }\n async listAutomationMatches(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.automationMatches}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.automationMatches;\n return await this.requestJson(path);\n }\n async listAutomationRuns(options = {}) {\n return await this.requestJson(granolaAutomationRunsPath(options));\n }\n async resolveAutomationRun(id, decision, options = {}) {\n return await this.requestJson(granolaAutomationRunDecisionPath(id, decision), {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async resolveAutomationArtefact(id, decision, options = {}) {\n return await this.requestJson(granolaAutomationArtefactDecisionPath(id, decision), {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async recoverProcessingIssue(id) {\n return await this.requestJson(granolaProcessingIssueRecoverPath(id), { method: \"POST\" });\n }\n async updateAutomationArtefact(id, patch) {\n return await this.requestJson(granolaAutomationArtefactUpdatePath(id), {\n body: JSON.stringify(patch),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async rerunAutomationArtefact(id) {\n return await this.requestJson(granolaAutomationArtefactRerunPath(id), { method: \"POST\" });\n }\n async inspectSync() {\n return cloneValue(_classPrivateFieldGet2(_state, this).sync);\n }\n async listSyncEvents(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.syncEvents}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.syncEvents;\n return await this.requestJson(path);\n }\n async loginAuth(options = {}) {\n return await this.requestJson(granolaTransportPaths.authLogin, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async logoutAuth() {\n return await this.requestJson(granolaTransportPaths.authLogout, { method: \"POST\" });\n }\n async refreshAuth() {\n return await this.requestJson(granolaTransportPaths.authRefresh, { method: \"POST\" });\n }\n async switchAuthMode(mode) {\n return await this.requestJson(granolaTransportPaths.authMode, {\n body: JSON.stringify({ mode }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async sync(options = {}) {\n return await this.requestJson(granolaTransportPaths.syncRun, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async listFolders(options = {}) {\n return await this.requestJson(granolaFoldersPath(options));\n }\n async getFolder(id) {\n return await this.requestJson(granolaFolderPath(id));\n }\n async findFolder(query) {\n return await this.requestJson(granolaFolderResolvePath(query));\n }\n async listMeetings(options = {}) {\n return await this.requestJson(granolaMeetingsPath(options));\n }\n async getMeeting(id, options = {}) {\n return await this.requestJson(`${granolaMeetingPath(id)}${options.requireCache ? \"?includeTranscript=true\" : \"\"}`);\n }\n async findMeeting(query, options = {}) {\n return await this.requestJson(granolaMeetingResolvePath(query, { includeTranscript: options.requireCache }));\n }\n async listExportJobs(options = {}) {\n return await this.requestJson(granolaExportJobsPath(options));\n }\n async exportNotes(format = \"markdown\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportNotes, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async exportTranscripts(format = \"text\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportTranscripts, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async rerunExportJob(id) {\n return await this.requestJson(granolaExportJobRerunPath(id), { method: \"POST\" });\n }\n async request(path, init = {}) {\n const response = await _classPrivateFieldGet2(_fetchImpl, this).call(this, new URL(path, this.url), {\n ...init,\n headers: mergeHeaders({\n ..._classPrivateFieldGet2(_password, this) ? { \"x-granola-password\": _classPrivateFieldGet2(_password, this) } : {},\n accept: \"application/json\"\n }, init.headers)\n });\n if (!response.ok) throw await responseError(response);\n return response;\n }\n async requestJson(path, init = {}) {\n return cloneValue(await (await this.request(path, init)).json());\n }\n emit(event) {\n _classPrivateFieldSet2(_state, this, cloneValue(event.state));\n const nextEvent = cloneValue(event);\n for (const listener of _classPrivateFieldGet2(_listeners, this)) listener(nextEvent);\n }\n startEvents() {\n if (_classPrivateFieldGet2(_eventLoop, this)) return;\n _classPrivateFieldSet2(_eventLoop, this, this.runEventsLoop());\n }\n async runEventsLoop() {\n while (!_classPrivateFieldGet2(_closed, this)) {\n const controller = new AbortController();\n _classPrivateFieldSet2(_streamAbortController, this, controller);\n try {\n const response = await this.request(granolaTransportPaths.events, {\n headers: { accept: \"text/event-stream\" },\n signal: controller.signal\n });\n await this.consumeEventStream(response);\n } catch {\n if (_classPrivateFieldGet2(_closed, this) || controller.signal.aborted) break;\n await new Promise((resolve) => {\n setTimeout(resolve, _classPrivateFieldGet2(_reconnectDelayMs, this));\n });\n }\n }\n }\n async consumeEventStream(response) {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"server did not provide an event stream\");\n const decoder = new TextDecoder();\n let buffer = \"\";\n while (!_classPrivateFieldGet2(_closed, this)) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n buffer = buffer.replaceAll(\"\\r\\n\", \"\\n\");\n while (true) {\n const boundary = buffer.indexOf(\"\\n\\n\");\n if (boundary < 0) break;\n const chunk = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n const event = parseSseEvent(chunk);\n if (event) this.emit(event);\n }\n }\n }\n};\nasync function createGranolaServerClient(serverUrl, options = {}) {\n return await GranolaServerClient.connect(serverUrl, options);\n}\n//#endregion\n//#region src/web/client-state.ts\nvar granolaWebWorkspaceStorageKey = \"granola-toolkit.web-workspace\";\nvar maxRecentMeetings = 6;\nvar maxSavedFilters = 6;\nfunction normaliseFilterValue(value) {\n const trimmed = value?.trim();\n return trimmed ? trimmed : void 0;\n}\nfunction normaliseFilters(filters) {\n const selectedFolderId = normaliseFilterValue(filters.selectedFolderId);\n return {\n search: normaliseFilterValue(filters.search),\n selectedFolderId,\n sort: normaliseFilterValue(filters.sort) ?? \"updated-desc\",\n updatedFrom: normaliseFilterValue(filters.updatedFrom),\n updatedTo: normaliseFilterValue(filters.updatedTo)\n };\n}\nfunction filtersKey(filters) {\n return JSON.stringify(normaliseFilters(filters));\n}\nfunction defaultWorkspacePreferences() {\n return {\n recentMeetings: [],\n savedFilters: []\n };\n}\nfunction parseWorkspacePreferences(raw) {\n if (!raw) return defaultWorkspacePreferences();\n try {\n const parsed = JSON.parse(raw);\n return {\n recentMeetings: Array.isArray(parsed?.recentMeetings) ? parsed.recentMeetings.map((entry) => ({\n folderId: normaliseFilterValue(entry?.folderId),\n id: normaliseFilterValue(entry?.id) || \"\",\n title: normaliseFilterValue(entry?.title) || \"\",\n updatedAt: normaliseFilterValue(entry?.updatedAt) || \"\"\n })).filter((entry) => entry.id && entry.title).slice(0, maxRecentMeetings) : [],\n savedFilters: Array.isArray(parsed?.savedFilters) ? parsed.savedFilters.map((preset) => ({\n filters: normaliseFilters(preset?.filters ?? {}),\n id: normaliseFilterValue(preset?.id) || \"\",\n label: normaliseFilterValue(preset?.label) || \"\"\n })).filter((preset) => preset.id && preset.label).slice(0, maxSavedFilters) : []\n };\n } catch {\n return defaultWorkspacePreferences();\n }\n}\nfunction serialiseWorkspacePreferences(preferences) {\n return JSON.stringify({\n recentMeetings: preferences.recentMeetings.slice(0, maxRecentMeetings),\n savedFilters: preferences.savedFilters.slice(0, maxSavedFilters)\n });\n}\nfunction hasActiveFilters(filters) {\n const normalised = normaliseFilters(filters);\n return Boolean(normalised.search || normalised.selectedFolderId || normalised.updatedFrom || normalised.updatedTo || normalised.sort !== \"updated-desc\");\n}\nfunction filterLabel(filters) {\n const summary = currentFilterSummary(filters);\n if (!summary) return \"Current workspace\";\n return summary;\n}\nfunction rememberRecentMeeting(preferences, meeting) {\n const nextEntry = {\n folderId: meeting.folders?.[0]?.id,\n id: meeting.id,\n title: meeting.title?.trim() || meeting.id,\n updatedAt: meeting.updatedAt\n };\n return {\n ...preferences,\n recentMeetings: [nextEntry, ...preferences.recentMeetings.filter((entry) => entry.id !== nextEntry.id)].slice(0, maxRecentMeetings)\n };\n}\nfunction saveWorkspaceFilter(preferences, filters, options = {}) {\n const nextFilters = normaliseFilters(filters);\n if (!hasActiveFilters(nextFilters)) return preferences;\n const key = filtersKey(nextFilters);\n const nextPreset = {\n filters: nextFilters,\n id: preferences.savedFilters.find((preset) => filtersKey(preset.filters) === key)?.id ?? options.idFactory?.() ?? `filter-${preferences.savedFilters.length + 1}`,\n label: filterLabel(filters)\n };\n return {\n ...preferences,\n savedFilters: [nextPreset, ...preferences.savedFilters.filter((preset) => preset.id !== nextPreset.id)].slice(0, maxSavedFilters)\n };\n}\nfunction removeWorkspaceFilter(preferences, id) {\n return {\n ...preferences,\n savedFilters: preferences.savedFilters.filter((preset) => preset.id !== id)\n };\n}\nfunction applyWorkspaceFilter(preset) {\n return {\n search: preset.filters.search ?? \"\",\n selectedFolderId: preset.filters.selectedFolderId ?? null,\n sort: preset.filters.sort ?? \"updated-desc\",\n updatedFrom: preset.filters.updatedFrom ?? \"\",\n updatedTo: preset.filters.updatedTo ?? \"\"\n };\n}\nfunction parseWorkspaceTab(value) {\n switch (value) {\n case \"metadata\":\n case \"raw\":\n case \"transcript\": return value;\n default: return \"notes\";\n }\n}\nfunction startupSelectionFromSearch(search) {\n const params = new URLSearchParams(search);\n return {\n folderId: params.get(\"folder\")?.trim() || \"\",\n meetingId: params.get(\"meeting\")?.trim() || \"\",\n workspaceTab: parseWorkspaceTab(params.get(\"tab\"))\n };\n}\nfunction buildBrowserUrlPath(currentHref, selection) {\n const url = new URL(currentHref);\n if (selection.selectedFolderId) url.searchParams.set(\"folder\", selection.selectedFolderId);\n else url.searchParams.delete(\"folder\");\n if (selection.selectedMeetingId) url.searchParams.set(\"meeting\", selection.selectedMeetingId);\n else url.searchParams.delete(\"meeting\");\n if (parseWorkspaceTab(selection.workspaceTab) !== \"notes\") url.searchParams.set(\"tab\", parseWorkspaceTab(selection.workspaceTab));\n else url.searchParams.delete(\"tab\");\n return `${url.pathname}${url.search}${url.hash}`;\n}\nfunction exportScopeLabel(scope) {\n return scope && scope.mode === \"folder\" ? `Folder: ${scope.folderName || scope.folderId}` : \"Scope: All meetings\";\n}\nfunction currentFilterSummary(filters) {\n const parts = [];\n if (filters.selectedFolderId) {\n const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);\n parts.push(`folder \"${folder ? folder.name : filters.selectedFolderId}\"`);\n }\n if (filters.search) parts.push(`search \"${filters.search}\"`);\n if (filters.updatedFrom) parts.push(`from ${filters.updatedFrom}`);\n if (filters.updatedTo) parts.push(`to ${filters.updatedTo}`);\n if (filters.sort && filters.sort !== \"updated-desc\") parts.push(filters.sort === \"updated-asc\" ? \"oldest first\" : filters.sort === \"title-asc\" ? \"title A-Z\" : \"title Z-A\");\n return parts.join(\", \");\n}\nfunction selectMeetingId(meetings, selectedMeetingId) {\n if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;\n return meetings[0]?.id ?? null;\n}\nfunction nextWorkspaceTab(currentTab, key) {\n const current = parseWorkspaceTab(currentTab);\n switch (key) {\n case \"1\": return \"notes\";\n case \"2\": return \"transcript\";\n case \"3\": return \"metadata\";\n case \"4\": return \"raw\";\n case \"]\":\n switch (current) {\n case \"notes\": return \"transcript\";\n case \"transcript\": return \"metadata\";\n case \"metadata\": return \"raw\";\n case \"raw\": return \"notes\";\n }\n break;\n case \"[\":\n switch (current) {\n case \"notes\": return \"raw\";\n case \"transcript\": return \"notes\";\n case \"metadata\": return \"transcript\";\n case \"raw\": return \"metadata\";\n }\n break;\n default: return;\n }\n}\nfunction describeSyncStatus(sync) {\n if (sync.running) return \"Sync running\";\n if (sync.lastError) return \"Sync needs attention\";\n if (sync.lastCompletedAt) {\n const suffix = sync.summary?.changedCount ? ` · ${sync.summary.changedCount} changes` : \"\";\n return `Synced ${sync.lastCompletedAt.slice(11, 19)}${suffix}`;\n }\n return \"Sync idle\";\n}\nfunction describeAuthStatus(auth) {\n if (!auth) return \"Waiting for auth\";\n if (auth.lastError) return \"Auth needs attention\";\n switch (auth.mode) {\n case \"api-key\": return \"API key active\";\n case \"stored-session\": return \"Stored session active\";\n default: return \"supabase.json active\";\n }\n}\n//#endregion\n//#region src/web-app/components.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$$2 = /* @__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$1 = /* @__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$1 = /* @__PURE__ */ template(`<div class=\"folder-empty folder-empty--error\">`), _tmpl$4$1 = /* @__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$1 = /* @__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$1 = /* @__PURE__ */ template(`<div class=folder-empty>No folders found.`), _tmpl$7$1 = /* @__PURE__ */ template(`<button class=folder-row type=button><span class=folder-row__title></span><span class=folder-row__meta>`), _tmpl$8$1 = /* @__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$1 = /* @__PURE__ */ template(`<div class=folder-empty>No saved filters yet.`), _tmpl$0$1 = /* @__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$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$1 = /* @__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(`<section class=jobs-panel><div class=jobs-panel__head><h3>Processing Health</h3><p>Catch stale syncs, missing transcripts, and failed or outdated note pipelines.</p></div><div class=jobs-list>`), _tmpl$38 = /* @__PURE__ */ template(`<div class=job-empty>No processing issues detected.`), _tmpl$39 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Open Meeting`), _tmpl$40 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Recover`), _tmpl$41 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Review Queue</h3><p>Generated note and enrichment candidates waiting for review or follow-up.</p></div><div class=jobs-list>`), _tmpl$42 = /* @__PURE__ */ template(`<div class=job-empty>No automation artefacts yet.`), _tmpl$43 = /* @__PURE__ */ template(`<button class=\"job-card job-card--button\"type=button><div class=job-card__head><div><div class=job-card__title></div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta>`), _tmpl$44 = /* @__PURE__ */ template(`<section class=review-panel><div class=jobs-panel__head><h3>Artefact Review</h3><p>Review generated candidate notes, compare them to the current meeting, then approve, reject, edit, or rerun.`), _tmpl$45 = /* @__PURE__ */ template(`<div class=job-empty>`), _tmpl$46 = /* @__PURE__ */ template(`<div class=detail-section><h3>Action Items</h3><ul class=detail-list>`), _tmpl$47 = /* @__PURE__ */ template(`<div class=detail-section><h3>Participant Summaries</h3><ul class=detail-list>`), _tmpl$48 = /* @__PURE__ */ template(`<div class=review-grid><section class=detail-section><h2>Current Meeting Notes</h2><pre class=detail-pre></pre></section><section class=detail-section><h2>Candidate</h2><label class=field-row><span class=field-label>Title</span><input class=\"field-input field-input--plain\"></label><label class=field-row><span class=field-label>Summary</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><label class=field-row><span class=field-label>Markdown</span><textarea class=review-textarea></textarea></label><label class=field-row><span class=field-label>Review Note</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><div class=job-card__actions><button class=\"button button--secondary\"type=button>Save edits</button><button class=\"button button--secondary\"type=button>Approve</button><button class=\"button button--secondary\"type=button>Reject</button><button class=\"button button--secondary\"type=button>Rerun`), _tmpl$49 = /* @__PURE__ */ template(`<div class=review-body><div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip></div></div><section class=\"detail-section review-history\"><h2>History</h2><div class=jobs-list>`), _tmpl$50 = /* @__PURE__ */ template(`<div class=empty>`), _tmpl$51 = /* @__PURE__ */ template(`<span>`), _tmpl$52 = /* @__PURE__ */ template(`<li><strong>`), _tmpl$53 = /* @__PURE__ */ template(`<li><strong></strong><div>`), _tmpl$54 = /* @__PURE__ */ template(`<div class=job-card><div class=job-card__head><div class=job-card__title></div><div class=job-card__meta>`), _tmpl$55 = /* @__PURE__ */ template(`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle`), _tmpl$56 = /* @__PURE__ */ template(`<button class=workspace-tab type=button>`), _tmpl$57 = /* @__PURE__ */ template(`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>`), _tmpl$58 = /* @__PURE__ */ template(`<div class=detail-body><div class=workspace-grid><aside class=\"detail-section workspace-sidebar\"><h2>Meeting Metadata</h2><pre class=detail-pre></pre></aside><section class=\"detail-section workspace-main\"><h2></h2><pre class=detail-pre>`);\nfunction authModeLabel(mode) {\n switch (mode) {\n case \"api-key\": return \"API key\";\n case \"stored-session\": return \"Stored session\";\n default: return \"supabase.json\";\n }\n}\nfunction metadataLines(record) {\n return [\n `Title: ${record.meeting.title || record.meeting.id}`,\n `Created: ${record.meeting.createdAt}`,\n `Updated: ${record.meeting.updatedAt}`,\n `Folders: ${record.meeting.folders.length ? record.meeting.folders.map((folder) => folder.name).join(\", \") : \"none\"}`,\n `Tags: ${record.meeting.tags.length ? record.meeting.tags.join(\", \") : \"none\"}`,\n `Transcript loaded: ${record.meeting.transcriptLoaded ? \"yes\" : \"no\"}`,\n `Owner candidates: ${record.roleHelpers.ownerCandidates.length ? record.roleHelpers.ownerCandidates.map((candidate) => candidate.label).join(\", \") : \"none\"}`,\n `Speakers: ${record.roleHelpers.speakers.length ? record.roleHelpers.speakers.map((speaker) => `${speaker.label} (${speaker.segmentCount})`).join(\", \") : \"none\"}`\n ].join(\"\\n\");\n}\nfunction workspaceBody(bundle, record, tab) {\n switch (tab) {\n case \"transcript\": return {\n body: record.transcriptText || \"(Transcript unavailable)\",\n title: \"Transcript\"\n };\n case \"metadata\": return {\n body: metadataLines(record),\n title: \"Metadata\"\n };\n case \"raw\": return {\n body: JSON.stringify(bundle || record, null, 2),\n title: \"Raw Bundle\"\n };\n default: return {\n body: record.noteMarkdown || \"(No notes available)\",\n title: \"Notes\"\n };\n }\n}\nfunction scopeLabel(scope) {\n return exportScopeLabel(scope);\n}\nfunction ToolbarFilters(props) {\n return [(() => {\n var _el$ = _tmpl$$2(), _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$1(), _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$1(), _el$20 = _el$18.firstChild.nextSibling;\n insert(_el$20, createComponent(Show, {\n get fallback() {\n return [\n (() => {\n var _el$22 = _tmpl$5$1();\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$1(), _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$1();\n }\n })\n ];\n },\n get when() {\n return !props.error;\n },\n get children() {\n var _el$21 = _tmpl$3$1();\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$1(), _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$1();\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$1(), _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$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$1();\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$1(), _el$43 = _el$42.firstChild, _el$44 = _el$43.nextSibling;\n _el$42.$$click = () => {\n props.onOpen(meeting);\n };\n insert(_el$43, () => meeting.title);\n insert(_el$44, () => meeting.updatedAt.slice(0, 10));\n return _el$42;\n })()\n });\n }\n }));\n return _el$38;\n })();\n}\nfunction MeetingList(props) {\n const summary = () => currentFilterSummary({\n folders: props.folders,\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$45 = _tmpl$12();\n insert(_el$45, createComponent(Show, {\n get fallback() {\n return createComponent(Show, {\n get fallback() {\n return (() => {\n var _el$47 = _tmpl$13();\n insert(_el$47, (() => {\n var _c$ = memo(() => !!summary());\n return () => _c$() ? `No meetings match ${summary()}.` : props.emptyHint || \"No meetings yet. Try Sync now.\";\n })());\n return _el$47;\n })();\n },\n get when() {\n return props.meetings.length > 0;\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.meetings;\n },\n children: (meeting) => (() => {\n var _el$48 = _tmpl$14(), _el$49 = _el$48.firstChild, _el$50 = _el$49.nextSibling, _el$51 = _el$50.nextSibling;\n _el$48.$$click = () => {\n props.onSelect(meeting.id);\n };\n insert(_el$49, () => meeting.title || meeting.id);\n insert(_el$50, (() => {\n var _c$2 = memo(() => !!meeting.tags.length);\n return () => _c$2() ? meeting.tags.map((tag) => `#${tag}`).join(\" \") : \"untagged\";\n })());\n insert(_el$51, (() => {\n var _c$3 = memo(() => !!meeting.updatedAt);\n return () => _c$3() ? meeting.updatedAt.slice(0, 10) : \"unknown\";\n })());\n createRenderEffect(() => setAttribute(_el$48, \"data-selected\", meeting.id === props.selectedMeetingId ? \"true\" : void 0));\n return _el$48;\n })()\n });\n }\n });\n },\n get when() {\n return props.error;\n },\n get children() {\n var _el$46 = _tmpl$11();\n insert(_el$46, () => props.error);\n return _el$46;\n }\n }));\n return _el$45;\n })();\n}\nfunction AppStatePanel(props) {\n const syncStatus = () => describeSyncStatus(props.appState?.sync ?? {});\n const authStatus = () => describeAuthStatus(props.appState?.auth);\n return (() => {\n var _el$52 = _tmpl$16(), _el$53 = _el$52.firstChild;\n _el$53.firstChild;\n var _el$56 = _el$53.nextSibling;\n insert(_el$53, createComponent(Show, {\n get fallback() {\n return _tmpl$17();\n },\n get when() {\n return props.appState;\n },\n children: (appState) => (() => {\n var _el$58 = _tmpl$18(), _el$59 = _el$58.firstChild, _el$61 = _el$59.firstChild.nextSibling, _el$62 = _el$59.nextSibling, _el$64 = _el$62.firstChild.nextSibling, _el$65 = _el$62.nextSibling, _el$67 = _el$65.firstChild.nextSibling, _el$68 = _el$65.nextSibling, _el$70 = _el$68.firstChild.nextSibling, _el$71 = _el$68.nextSibling, _el$73 = _el$71.firstChild.nextSibling, _el$74 = _el$71.nextSibling, _el$76 = _el$74.firstChild.nextSibling, _el$77 = _el$74.nextSibling, _el$79 = _el$77.firstChild.nextSibling, _el$80 = _el$77.nextSibling, _el$82 = _el$80.firstChild.nextSibling, _el$85 = _el$80.nextSibling.firstChild.nextSibling;\n insert(_el$61, () => appState().ui.surface);\n insert(_el$64, () => appState().ui.view);\n insert(_el$67, authStatus);\n insert(_el$70, syncStatus);\n insert(_el$73, (() => {\n var _c$4 = memo(() => !!appState().documents.loaded);\n return () => _c$4() ? String(appState().documents.count) : \"not loaded\";\n })());\n insert(_el$76, (() => {\n var _c$5 = memo(() => !!appState().folders.loaded);\n return () => _c$5() ? String(appState().folders.count) : \"not loaded\";\n })());\n insert(_el$79, (() => {\n var _c$6 = memo(() => !!appState().cache.loaded);\n return () => _c$6() ? `${appState().cache.transcriptCount} transcript sets` : appState().cache.configured ? \"configured\" : \"not configured\";\n })());\n insert(_el$82, (() => {\n var _c$7 = memo(() => !!appState().index.loaded);\n return () => _c$7() ? `${appState().index.meetingCount} meetings` : appState().index.available ? \"available\" : \"not built\";\n })());\n insert(_el$85, () => `${appState().automation.runCount} runs / ${appState().automation.pendingRunCount} pending runs / ${appState().automation.pendingArtefactCount} pending artefacts`);\n return _el$58;\n })()\n }), null);\n insert(_el$53, createComponent(Show, {\n get when() {\n return props.appState?.auth.lastError;\n },\n get children() {\n var _el$55 = _tmpl$15();\n insert(_el$55, () => props.appState?.auth.lastError);\n return _el$55;\n }\n }), null);\n insert(_el$56, () => props.statusLabel);\n createRenderEffect(() => setAttribute(_el$56, \"data-tone\", props.statusTone));\n return _el$52;\n })();\n}\nfunction SecurityPanel(props) {\n return createComponent(Show, {\n get when() {\n return props.visible;\n },\n get children() {\n var _el$86 = _tmpl$19(), _el$89 = _el$86.firstChild.nextSibling.firstChild, _el$91 = _el$89.nextSibling.firstChild, _el$92 = _el$91.nextSibling;\n _el$89.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onUnlock();\n }\n };\n _el$89.$$input = (event) => {\n props.onPasswordChange(event.currentTarget.value);\n };\n addEventListener(_el$91, \"click\", props.onUnlock, true);\n addEventListener(_el$92, \"click\", props.onLock, true);\n createRenderEffect(() => _el$89.value = props.password);\n return _el$86;\n }\n });\n}\nfunction AuthPanel(props) {\n return (() => {\n var _el$93 = _tmpl$20(), _el$95 = _el$93.firstChild.nextSibling;\n insert(_el$95, createComponent(Show, {\n get fallback() {\n return _tmpl$21();\n },\n get when() {\n return props.auth;\n },\n children: (auth) => (() => {\n var _el$97 = _tmpl$26(), _el$98 = _el$97.firstChild, _el$99 = _el$98.firstChild, _el$101 = _el$99.firstChild.nextSibling, _el$102 = _el$99.nextSibling, _el$104 = _el$102.firstChild.nextSibling, _el$105 = _el$102.nextSibling, _el$107 = _el$105.firstChild.nextSibling, _el$108 = _el$105.nextSibling, _el$110 = _el$108.firstChild.nextSibling, _el$113 = _el$108.nextSibling.firstChild.nextSibling, _el$121 = _el$98.nextSibling, _el$123 = _el$121.nextSibling.firstChild, _el$124 = _el$123.nextSibling, _el$125 = _el$124.nextSibling, _el$126 = _el$125.nextSibling, _el$127 = _el$126.nextSibling, _el$128 = _el$127.nextSibling, _el$129 = _el$128.nextSibling, _el$130 = _el$129.nextSibling;\n insert(_el$101, () => authModeLabel(auth().mode));\n insert(_el$104, () => auth().apiKeyAvailable ? \"available\" : \"missing\");\n insert(_el$107, () => auth().storedSessionAvailable ? \"available\" : \"missing\");\n insert(_el$110, () => auth().supabaseAvailable ? \"available\" : \"missing\");\n insert(_el$113, () => auth().refreshAvailable ? \"available\" : \"missing\");\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().clientId;\n },\n get children() {\n var _el$114 = _tmpl$22();\n _el$114.firstChild;\n insert(_el$114, () => auth().clientId, null);\n return _el$114;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().signInMethod;\n },\n get children() {\n var _el$116 = _tmpl$23();\n _el$116.firstChild;\n insert(_el$116, () => auth().signInMethod, null);\n return _el$116;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().supabasePath;\n },\n get children() {\n var _el$118 = _tmpl$24();\n _el$118.firstChild;\n insert(_el$118, () => auth().supabasePath, null);\n return _el$118;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().lastError;\n },\n get children() {\n var _el$120 = _tmpl$25();\n insert(_el$120, () => auth().lastError);\n return _el$120;\n }\n }), _el$121);\n _el$123.$$input = (event) => {\n props.onApiKeyDraftChange(event.currentTarget.value);\n };\n addEventListener(_el$124, \"click\", props.onSaveApiKey, true);\n addEventListener(_el$125, \"click\", props.onImportDesktopSession, true);\n addEventListener(_el$126, \"click\", props.onRefresh, true);\n _el$127.$$click = () => {\n props.onSwitchMode(\"api-key\");\n };\n _el$128.$$click = () => {\n props.onSwitchMode(\"stored-session\");\n };\n _el$129.$$click = () => {\n props.onSwitchMode(\"supabase-file\");\n };\n addEventListener(_el$130, \"click\", props.onLogout, true);\n createRenderEffect((_p$) => {\n var _v$ = !auth().supabaseAvailable, _v$2 = !auth().storedSessionAvailable || !auth().refreshAvailable, _v$3 = !auth().apiKeyAvailable || auth().mode === \"api-key\", _v$4 = !auth().storedSessionAvailable || auth().mode === \"stored-session\", _v$5 = !auth().supabaseAvailable || auth().mode === \"supabase-file\", _v$6 = !auth().apiKeyAvailable && !auth().storedSessionAvailable;\n _v$ !== _p$.e && (_el$125.disabled = _p$.e = _v$);\n _v$2 !== _p$.t && (_el$126.disabled = _p$.t = _v$2);\n _v$3 !== _p$.a && (_el$127.disabled = _p$.a = _v$3);\n _v$4 !== _p$.o && (_el$128.disabled = _p$.o = _v$4);\n _v$5 !== _p$.i && (_el$129.disabled = _p$.i = _v$5);\n _v$6 !== _p$.n && (_el$130.disabled = _p$.n = _v$6);\n return _p$;\n }, {\n e: void 0,\n t: void 0,\n a: void 0,\n o: void 0,\n i: void 0,\n n: void 0\n });\n createRenderEffect(() => _el$123.value = props.apiKeyDraft);\n return _el$97;\n })()\n }));\n return _el$93;\n })();\n}\nfunction ExportJobsPanel(props) {\n return (() => {\n var _el$131 = _tmpl$27(), _el$133 = _el$131.firstChild.nextSibling;\n insert(_el$133, createComponent(Show, {\n get when() {\n return props.jobs.length > 0;\n },\n get fallback() {\n return _tmpl$28();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.jobs.slice(0, 6);\n },\n children: (job) => (() => {\n var _el$135 = _tmpl$31(), _el$136 = _el$135.firstChild, _el$137 = _el$136.firstChild, _el$138 = _el$137.firstChild, _el$139 = _el$138.firstChild, _el$140 = _el$138.nextSibling, _el$141 = _el$137.nextSibling, _el$142 = _el$136.nextSibling, _el$143 = _el$142.nextSibling;\n _el$143.firstChild;\n var _el$145 = _el$143.nextSibling;\n _el$145.firstChild;\n var _el$148 = _el$145.nextSibling;\n insert(_el$138, () => job.kind, _el$139);\n insert(_el$140, () => job.id);\n insert(_el$141, () => job.status);\n insert(_el$142, () => `Format: ${job.format} • ${scopeLabel(job.scope)} • ${job.itemCount > 0 ? `${job.completedCount}/${job.itemCount} items` : \"0 items\"} • Written: ${job.written}`);\n insert(_el$143, () => job.startedAt.slice(0, 19), null);\n insert(_el$145, () => job.outputDir, null);\n insert(_el$135, createComponent(Show, {\n get when() {\n return job.error;\n },\n get children() {\n var _el$147 = _tmpl$29();\n insert(_el$147, () => job.error);\n return _el$147;\n }\n }), _el$148);\n insert(_el$148, createComponent(Show, {\n get when() {\n return job.status !== \"running\";\n },\n get children() {\n var _el$149 = _tmpl$30();\n _el$149.$$click = () => {\n props.onRerun(job.id);\n };\n return _el$149;\n }\n }));\n createRenderEffect(() => setAttribute(_el$141, \"data-status\", job.status));\n return _el$135;\n })()\n });\n }\n }));\n return _el$131;\n })();\n}\nfunction AutomationRunsPanel(props) {\n return (() => {\n var _el$150 = _tmpl$32(), _el$152 = _el$150.firstChild.nextSibling;\n insert(_el$152, createComponent(Show, {\n get when() {\n return props.runs.length > 0;\n },\n get fallback() {\n return _tmpl$33();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.runs.slice(0, 6);\n },\n children: (run) => (() => {\n var _el$154 = _tmpl$36(), _el$155 = _el$154.firstChild, _el$156 = _el$155.firstChild, _el$157 = _el$156.firstChild, _el$158 = _el$157.nextSibling, _el$159 = _el$156.nextSibling, _el$160 = _el$155.nextSibling, _el$161 = _el$160.nextSibling, _el$165 = _el$161.nextSibling;\n insert(_el$157, () => run.actionName);\n insert(_el$158, () => `${run.ruleName} • ${run.id}`);\n insert(_el$159, () => run.status);\n insert(_el$160, () => `${run.title} • ${run.eventKind}`);\n insert(_el$161, () => `Started: ${run.startedAt.slice(0, 19)}`);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.prompt;\n },\n get children() {\n var _el$162 = _tmpl$29();\n insert(_el$162, () => run.prompt);\n return _el$162;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.result;\n },\n get children() {\n var _el$163 = _tmpl$29();\n insert(_el$163, () => run.result);\n return _el$163;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.error;\n },\n get children() {\n var _el$164 = _tmpl$29();\n insert(_el$164, () => run.error);\n return _el$164;\n }\n }), _el$165);\n insert(_el$165, createComponent(Show, {\n get when() {\n return run.status === \"pending\";\n },\n get children() {\n return [(() => {\n var _el$166 = _tmpl$34();\n _el$166.$$click = () => {\n props.onApprove(run.id);\n };\n return _el$166;\n })(), (() => {\n var _el$167 = _tmpl$35();\n _el$167.$$click = () => {\n props.onReject(run.id);\n };\n return _el$167;\n })()];\n }\n }));\n createRenderEffect(() => setAttribute(_el$159, \"data-status\", run.status));\n return _el$154;\n })()\n });\n }\n }));\n return _el$150;\n })();\n}\nfunction ProcessingIssuesPanel(props) {\n return (() => {\n var _el$168 = _tmpl$37(), _el$170 = _el$168.firstChild.nextSibling;\n insert(_el$170, createComponent(Show, {\n get when() {\n return props.issues.length > 0;\n },\n get fallback() {\n return _tmpl$38();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.issues.slice(0, 8);\n },\n children: (issue) => (() => {\n var _el$172 = _tmpl$36(), _el$173 = _el$172.firstChild, _el$174 = _el$173.firstChild, _el$175 = _el$174.firstChild, _el$176 = _el$175.nextSibling, _el$177 = _el$174.nextSibling, _el$178 = _el$173.nextSibling, _el$179 = _el$178.nextSibling, _el$180 = _el$179.nextSibling;\n insert(_el$175, () => issue.title);\n insert(_el$176, () => issue.id);\n insert(_el$177, () => issue.severity);\n insert(_el$178, () => issue.kind);\n insert(_el$179, () => issue.detail);\n insert(_el$180, createComponent(Show, {\n get when() {\n return issue.meetingId;\n },\n get children() {\n var _el$181 = _tmpl$39();\n _el$181.$$click = () => {\n props.onOpenMeeting(issue.meetingId);\n };\n return _el$181;\n }\n }), null);\n insert(_el$180, createComponent(Show, {\n get when() {\n return issue.recoverable;\n },\n get children() {\n var _el$182 = _tmpl$40();\n _el$182.$$click = () => {\n props.onRecover(issue.id);\n };\n return _el$182;\n }\n }), null);\n createRenderEffect(() => setAttribute(_el$177, \"data-status\", issue.severity));\n return _el$172;\n })()\n });\n }\n }));\n return _el$168;\n })();\n}\nfunction AutomationArtefactsPanel(props) {\n return (() => {\n var _el$183 = _tmpl$41(), _el$185 = _el$183.firstChild.nextSibling;\n insert(_el$185, createComponent(Show, {\n get when() {\n return props.artefacts.length > 0;\n },\n get fallback() {\n return _tmpl$42();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.artefacts.slice(0, 10);\n },\n children: (artefact) => (() => {\n var _el$187 = _tmpl$43(), _el$188 = _el$187.firstChild, _el$189 = _el$188.firstChild, _el$190 = _el$189.firstChild, _el$191 = _el$190.nextSibling, _el$192 = _el$189.nextSibling, _el$193 = _el$188.nextSibling, _el$195 = _el$193.nextSibling;\n _el$187.$$click = () => {\n props.onSelect(artefact.id);\n };\n insert(_el$190, () => artefact.structured.title);\n insert(_el$191, () => `${artefact.kind} • ${artefact.ruleName}`);\n insert(_el$192, () => artefact.status);\n insert(_el$193, () => artefact.meetingId);\n insert(_el$187, createComponent(Show, {\n get when() {\n return artefact.structured.summary;\n },\n get children() {\n var _el$194 = _tmpl$29();\n insert(_el$194, () => artefact.structured.summary);\n return _el$194;\n }\n }), _el$195);\n insert(_el$195, () => `Updated: ${artefact.updatedAt.slice(0, 19)}`);\n createRenderEffect((_p$) => {\n var _v$7 = artefact.id === props.selectedArtefactId ? \"true\" : void 0, _v$8 = artefact.status;\n _v$7 !== _p$.e && setAttribute(_el$187, \"data-selected\", _p$.e = _v$7);\n _v$8 !== _p$.t && setAttribute(_el$192, \"data-status\", _p$.t = _v$8);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n return _el$187;\n })()\n });\n }\n }));\n return _el$183;\n })();\n}\nfunction ArtefactReviewPanel(props) {\n return (() => {\n var _el$196 = _tmpl$44();\n _el$196.firstChild;\n insert(_el$196, createComponent(Show, {\n get when() {\n return props.artefact;\n },\n get fallback() {\n return (() => {\n var _el$198 = _tmpl$45();\n insert(_el$198, () => props.error || \"Select an automation artefact to review it.\");\n return _el$198;\n })();\n },\n children: (artefact) => (() => {\n var _el$199 = _tmpl$49(), _el$200 = _el$199.firstChild, _el$201 = _el$200.firstChild, _el$202 = _el$201.nextSibling, _el$203 = _el$202.nextSibling, _el$204 = _el$203.nextSibling, _el$234 = _el$200.nextSibling, _el$236 = _el$234.firstChild.nextSibling;\n insert(_el$201, () => `Status: ${artefact().status}`);\n insert(_el$202, () => `Kind: ${artefact().kind}`);\n insert(_el$203, () => `Meeting: ${artefact().meetingId}`);\n insert(_el$204, () => `Provider: ${artefact().provider}/${artefact().model}`);\n insert(_el$199, createComponent(Show, {\n get when() {\n return !props.error;\n },\n get fallback() {\n return (() => {\n var _el$237 = _tmpl$50();\n insert(_el$237, () => props.error);\n return _el$237;\n })();\n },\n get children() {\n var _el$205 = _tmpl$48(), _el$206 = _el$205.firstChild, _el$208 = _el$206.firstChild.nextSibling, _el$209 = _el$206.nextSibling, _el$211 = _el$209.firstChild.nextSibling, _el$213 = _el$211.firstChild.nextSibling, _el$214 = _el$211.nextSibling, _el$216 = _el$214.firstChild.nextSibling, _el$217 = _el$214.nextSibling, _el$219 = _el$217.firstChild.nextSibling, _el$220 = _el$217.nextSibling, _el$222 = _el$220.firstChild.nextSibling, _el$224 = _el$220.nextSibling.firstChild, _el$225 = _el$224.nextSibling, _el$226 = _el$225.nextSibling, _el$227 = _el$226.nextSibling;\n insert(_el$208, () => props.bundle?.meeting.noteMarkdown || \"(No existing meeting notes)\");\n _el$213.$$input = (event) => {\n props.onDraftTitleChange(event.currentTarget.value);\n };\n _el$216.$$input = (event) => {\n props.onDraftSummaryChange(event.currentTarget.value);\n };\n insert(_el$216, () => props.draftSummary);\n _el$219.$$input = (event) => {\n props.onDraftMarkdownChange(event.currentTarget.value);\n };\n insert(_el$219, () => props.draftMarkdown);\n _el$222.$$input = (event) => {\n props.onReviewNoteChange(event.currentTarget.value);\n };\n insert(_el$222, () => props.reviewNote);\n addEventListener(_el$224, \"click\", props.onSave, true);\n addEventListener(_el$225, \"click\", props.onApprove, true);\n addEventListener(_el$226, \"click\", props.onReject, true);\n addEventListener(_el$227, \"click\", props.onRerun, true);\n insert(_el$209, createComponent(Show, {\n get when() {\n return artefact().structured.actionItems.length > 0;\n },\n get children() {\n var _el$228 = _tmpl$46(), _el$230 = _el$228.firstChild.nextSibling;\n insert(_el$230, createComponent(For, {\n get each() {\n return artefact().structured.actionItems;\n },\n children: (item) => (() => {\n var _el$238 = _tmpl$52(), _el$239 = _el$238.firstChild;\n insert(_el$239, () => item.title);\n insert(_el$238, createComponent(Show, {\n get when() {\n return item.owner;\n },\n get children() {\n var _el$240 = _tmpl$51();\n insert(_el$240, () => ` • ${item.owner}`);\n return _el$240;\n }\n }), null);\n insert(_el$238, createComponent(Show, {\n get when() {\n return item.dueDate;\n },\n get children() {\n var _el$241 = _tmpl$51();\n insert(_el$241, () => ` • due ${item.dueDate}`);\n return _el$241;\n }\n }), null);\n return _el$238;\n })()\n }));\n return _el$228;\n }\n }), null);\n insert(_el$209, createComponent(Show, {\n get when() {\n return (artefact().structured.participantSummaries?.length ?? 0) > 0;\n },\n get children() {\n var _el$231 = _tmpl$47(), _el$233 = _el$231.firstChild.nextSibling;\n insert(_el$233, createComponent(For, {\n get each() {\n return artefact().structured.participantSummaries;\n },\n children: (summary) => (() => {\n var _el$242 = _tmpl$53(), _el$243 = _el$242.firstChild, _el$245 = _el$243.nextSibling;\n insert(_el$243, () => summary.speaker);\n insert(_el$242, createComponent(Show, {\n get when() {\n return summary.role;\n },\n get children() {\n var _el$244 = _tmpl$51();\n insert(_el$244, () => ` • ${summary.role}`);\n return _el$244;\n }\n }), _el$245);\n insert(_el$245, () => summary.summary);\n return _el$242;\n })()\n }));\n return _el$231;\n }\n }), null);\n createRenderEffect((_p$) => {\n var _v$9 = artefact().status === \"superseded\", _v$0 = artefact().status === \"superseded\";\n _v$9 !== _p$.e && (_el$225.disabled = _p$.e = _v$9);\n _v$0 !== _p$.t && (_el$226.disabled = _p$.t = _v$0);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n createRenderEffect(() => _el$213.value = props.draftTitle);\n return _el$205;\n }\n }), _el$234);\n insert(_el$236, createComponent(For, {\n get each() {\n return artefact().history.slice().reverse();\n },\n children: (entry) => (() => {\n var _el$246 = _tmpl$54(), _el$248 = _el$246.firstChild.firstChild, _el$249 = _el$248.nextSibling;\n insert(_el$248, () => entry.action);\n insert(_el$249, () => entry.at.slice(0, 19));\n insert(_el$246, createComponent(Show, {\n get when() {\n return entry.note;\n },\n get children() {\n var _el$250 = _tmpl$29();\n insert(_el$250, () => entry.note);\n return _el$250;\n }\n }), null);\n return _el$246;\n })()\n }));\n return _el$199;\n })()\n }), null);\n return _el$196;\n })();\n}\nfunction Workspace(props) {\n const parsedTab = () => parseWorkspaceTab(props.tab);\n const details = () => {\n if (!props.selectedMeeting) return null;\n return workspaceBody(props.bundle, props.selectedMeeting, parsedTab());\n };\n return [(() => {\n var _el$251 = _tmpl$55(), _el$252 = _el$251.firstChild;\n insert(_el$251, createComponent(For, {\n each: [\n \"notes\",\n \"transcript\",\n \"metadata\",\n \"raw\"\n ],\n children: (tab) => (() => {\n var _el$253 = _tmpl$56();\n _el$253.$$click = () => {\n props.onSelectTab(tab);\n };\n insert(_el$253, tab === \"notes\" ? \"Notes\" : tab === \"transcript\" ? \"Transcript\" : tab === \"metadata\" ? \"Metadata\" : \"Raw\");\n createRenderEffect(() => setAttribute(_el$253, \"data-selected\", parsedTab() === tab ? \"true\" : void 0));\n return _el$253;\n })()\n }), _el$252);\n return _el$251;\n })(), createComponent(Show, {\n get when() {\n return props.selectedMeeting;\n },\n get fallback() {\n return (() => {\n var _el$254 = _tmpl$50();\n insert(_el$254, () => props.detailError || \"Select a meeting to inspect its notes and transcript.\");\n return _el$254;\n })();\n },\n children: (meeting) => [(() => {\n var _el$255 = _tmpl$57(), _el$256 = _el$255.firstChild, _el$257 = _el$256.nextSibling, _el$258 = _el$257.nextSibling;\n insert(_el$256, () => `ID: ${meeting().meeting.id}`);\n insert(_el$257, () => `Source: ${meeting().meeting.noteContentSource}`);\n insert(_el$258, () => `Transcript: ${meeting().meeting.transcriptSegmentCount} segments`);\n return _el$255;\n })(), createComponent(Show, {\n get when() {\n return !props.detailError;\n },\n get fallback() {\n return (() => {\n var _el$267 = _tmpl$50();\n insert(_el$267, () => props.detailError);\n return _el$267;\n })();\n },\n get children() {\n var _el$259 = _tmpl$58(), _el$261 = _el$259.firstChild.firstChild, _el$263 = _el$261.firstChild.nextSibling, _el$265 = _el$261.nextSibling.firstChild, _el$266 = _el$265.nextSibling;\n insert(_el$263, () => metadataLines(meeting()));\n insert(_el$265, () => details()?.title);\n insert(_el$266, () => details()?.body);\n return _el$259;\n }\n })]\n })];\n}\ndelegateEvents([\n \"input\",\n \"keydown\",\n \"click\"\n]);\n//#endregion\n//#region src/web-app/harness-editor.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$$1 = /* @__PURE__ */ template(`<div class=empty>No harnesses configured yet. Create one here and save it to the shared harness file.`), _tmpl$2 = /* @__PURE__ */ template(`<section class=harness-panel><div class=jobs-panel__head><h3>Harness Editor</h3><p>Manage meeting playbooks, inspect why they match the selected meeting, and run the selected harness through the current pipeline before changing live automation.</p></div><div class=harness-grid><div class=detail-section><div class=harness-toolbar><button class=\"button button--primary\"type=button>New Harness</button><button class=\"button button--secondary\"type=button>Duplicate</button><button class=\"button button--secondary\"type=button>Remove</button><button class=\"button button--secondary\"type=button>Reload</button><button class=\"button button--primary\"type=button>Save Harnesses</button></div><div class=jobs-list>`), _tmpl$3 = /* @__PURE__ */ template(`<button class=\"job-card job-card--button\"type=button><div class=job-card__head><span class=job-card__title></span><span class=job-card__status></span></div><div class=job-card__meta>`), _tmpl$4 = /* @__PURE__ */ template(`<div class=detail-section><div class=empty>Select a harness to edit its prompts, rules, and test output.`), _tmpl$5 = /* @__PURE__ */ template(`<div class=auth-card__error>`), _tmpl$6 = /* @__PURE__ */ template(`<div class=detail-section><div class=harness-status-row><h2></h2><span class=state-badge></span></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Id</span><input class=\"field-input field-input--plain\"></label><label><span class=field-label>Name</span><input class=\"field-input field-input--plain\"></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Provider</span><select class=select><option value>Use default</option><option value=codex>Codex</option><option value=openai>OpenAI</option><option value=openrouter>OpenRouter</option></select></label><label><span class=field-label>Model</span><input class=\"field-input field-input--plain\"placeholder=\"gpt-5-codex or openai/gpt-5-mini\"></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Priority</span><input class=\"field-input field-input--plain\"placeholder=0></label><label><span class=field-label>Working Directory</span><input class=\"field-input field-input--plain\"placeholder=/path/to/project></label></div><div class=field-row><label><span class=field-label>Prompt</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><label><span class=field-label>Prompt File</span><input class=\"field-input field-input--plain\"placeholder=./agents/customer-call/AGENT.md></label></div><div class=field-row><label><span class=field-label>System Prompt</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><label><span class=field-label>System Prompt File</span><input class=\"field-input field-input--plain\"placeholder=./agents/customer-call/SYSTEM.md></label></div><div class=field-row><label><span class=field-label>Fallback Harnesses</span><input class=\"field-input field-input--plain\"placeholder=\"fallback-a, fallback-b\"></label></div><div class=field-row><label><span class=field-label>Event Kinds</span><input class=\"field-input field-input--plain\"placeholder=\"transcript.ready, meeting.created\"></label><label><span class=field-label>Meeting Ids</span><input class=\"field-input field-input--plain\"placeholder=doc-alpha-1111></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Folder Ids</span><input class=\"field-input field-input--plain\"placeholder=folder-team-1111></label><label><span class=field-label>Folder Names</span><input class=\"field-input field-input--plain\"placeholder=\"Team, Customers\"></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Tags</span><input class=\"field-input field-input--plain\"placeholder=\"customer, weekly\"></label><label><span class=field-label>Title Includes</span><input class=\"field-input field-input--plain\"placeholder=\"sync, review\"></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Title Regex</span><input class=\"field-input field-input--plain\"placeholder=customer.*sync></label><label><span class=field-label>Transcript Loaded</span><select class=select><option value>Either</option><option value=true>Must be loaded</option><option value=false>Must be missing</option></select></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Calendar Event Ids</span><input class=\"field-input field-input--plain\"placeholder=event-123></label><label><span class=field-label>Recurring Event Ids</span><input class=\"field-input field-input--plain\"placeholder=recurring-456></label></div><div class=detail-section><h3>Selected Meeting Test</h3><div class=job-card__meta></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Pipeline Kind</span><select class=select><option value=notes>Notes</option><option value=enrichment>Enrichment</option></select></label><label><span class=field-label>Rule Explainer Event</span><input class=\"field-input field-input--plain\"disabled></label></div><div class=harness-toolbar><button class=\"button button--primary\"type=button>Test Harness`), _tmpl$7 = /* @__PURE__ */ template(`<div class=job-card__status>`), _tmpl$8 = /* @__PURE__ */ template(`<ul class=detail-list>`), _tmpl$9 = /* @__PURE__ */ template(`<li>`), _tmpl$0 = /* @__PURE__ */ template(`<div class=field-row><label><span class=field-label>Resolved Prompt Preview</span><textarea class=review-textarea readonly></textarea></label><label><span class=field-label>Raw Output</span><textarea class=\"review-textarea review-textarea--summary\"readonly>`), _tmpl$1 = /* @__PURE__ */ template(`<div class=harness-test-result><h3>Latest Test Run</h3><div class=job-card__meta>`), _tmpl$10 = /* @__PURE__ */ template(`<div class=field-row><label><span class=field-label>Structured Title</span><input class=\"field-input field-input--plain\"disabled></label><label><span class=field-label>Structured Summary</span><textarea class=\"review-textarea review-textarea--summary\"readonly>`);\nfunction csvValues(values) {\n return values?.join(\", \") ?? \"\";\n}\nfunction parseCsvValues(value) {\n const items = value.split(\",\").map((item) => item.trim()).filter(Boolean);\n return items.length > 0 ? [...new Set(items)] : void 0;\n}\nfunction compactMatch(match) {\n if (!match) return;\n const next = {\n calendarEventIds: match.calendarEventIds,\n eventKinds: match.eventKinds,\n folderIds: match.folderIds,\n folderNames: match.folderNames,\n meetingIds: match.meetingIds,\n recurringEventIds: match.recurringEventIds,\n tags: match.tags,\n titleIncludes: match.titleIncludes,\n titleMatches: match.titleMatches?.trim() || void 0,\n transcriptLoaded: match.transcriptLoaded\n };\n return Object.values(next).some((value) => Array.isArray(value) ? value.length > 0 : value !== void 0 && value !== \"\") ? next : void 0;\n}\nfunction sanitiseHarnessSeed(value) {\n return value.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"new-harness\";\n}\nfunction nextUniqueHarnessId(existing, seed) {\n const base = sanitiseHarnessSeed(seed);\n const ids = new Set(existing.map((harness) => harness.id));\n if (!ids.has(base)) return base;\n let index = 2;\n while (ids.has(`${base}-${index}`)) index += 1;\n return `${base}-${index}`;\n}\nfunction createHarnessTemplate(existing) {\n return {\n id: nextUniqueHarnessId(existing, \"new harness\"),\n name: \"New Harness\",\n prompt: \"Describe the structure and output you want for this meeting type.\",\n provider: \"codex\"\n };\n}\nfunction duplicateHarnessTemplate(existing, harness) {\n return {\n ...structuredClone(harness),\n id: nextUniqueHarnessId(existing, `${harness.id} copy`),\n name: `${harness.name} Copy`\n };\n}\nfunction patchHarness(harness, patch) {\n return {\n ...harness,\n ...patch,\n match: compactMatch(patch.match ?? harness.match)\n };\n}\nfunction patchHarnessMatch(harness, patch) {\n return patchHarness(harness, { match: {\n ...harness.match,\n ...patch\n } });\n}\nfunction HarnessEditorPanel(props) {\n const selectedExplanation = () => props.explanations.find((explanation) => explanation.harness.id === props.selectedHarnessId) ?? null;\n const selectedResult = () => props.testResult;\n const selectedHarness = () => props.selectedHarness;\n const updateHarness = (patch) => {\n const harness = selectedHarness();\n if (!harness) return;\n props.onChange(patchHarness(harness, patch));\n };\n const updateMatch = (patch) => {\n const harness = selectedHarness();\n if (!harness) return;\n props.onChange(patchHarnessMatch(harness, patch));\n };\n return (() => {\n var _el$ = _tmpl$2(), _el$3 = _el$.firstChild.nextSibling, _el$5 = _el$3.firstChild.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling, _el$0 = _el$9.nextSibling, _el$1 = _el$5.nextSibling;\n addEventListener(_el$6, \"click\", props.onNew, true);\n addEventListener(_el$7, \"click\", props.onDuplicate, true);\n addEventListener(_el$8, \"click\", props.onRemove, true);\n addEventListener(_el$9, \"click\", props.onReload, true);\n addEventListener(_el$0, \"click\", props.onSave, true);\n insert(_el$1, createComponent(For, {\n get each() {\n return props.harnesses;\n },\n children: (harness) => {\n const explanation = () => props.explanations.find((candidate) => candidate.harness.id === harness.id) ?? null;\n return (() => {\n var _el$11 = _tmpl$3(), _el$12 = _el$11.firstChild, _el$13 = _el$12.firstChild, _el$14 = _el$13.nextSibling, _el$15 = _el$12.nextSibling;\n _el$11.$$click = () => {\n props.onSelect(harness.id);\n };\n insert(_el$13, () => harness.name);\n insert(_el$14, () => explanation()?.matched ? \"Matched\" : \"Check\");\n insert(_el$15, () => harness.id, null);\n insert(_el$15, (() => {\n var _c$ = memo(() => !!harness.provider);\n return () => _c$() ? ` • ${harness.provider}` : \"\";\n })(), null);\n insert(_el$15, (() => {\n var _c$2 = memo(() => !!harness.model);\n return () => _c$2() ? `/${harness.model}` : \"\";\n })(), null);\n createRenderEffect((_p$) => {\n var _v$4 = props.selectedHarnessId === harness.id, _v$5 = explanation()?.matched ? \"completed\" : \"warning\";\n _v$4 !== _p$.e && setAttribute(_el$11, \"data-selected\", _p$.e = _v$4);\n _v$5 !== _p$.t && setAttribute(_el$14, \"data-status\", _p$.t = _v$5);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n return _el$11;\n })();\n }\n }), null);\n insert(_el$1, createComponent(Show, {\n get when() {\n return props.harnesses.length === 0;\n },\n get children() {\n return _tmpl$$1();\n }\n }), null);\n insert(_el$3, createComponent(Show, {\n get when() {\n return selectedHarness();\n },\n get fallback() {\n return _tmpl$4();\n },\n children: (harness) => (() => {\n var _el$17 = _tmpl$6(), _el$18 = _el$17.firstChild, _el$19 = _el$18.firstChild, _el$20 = _el$19.nextSibling, _el$21 = _el$18.nextSibling, _el$22 = _el$21.firstChild, _el$24 = _el$22.firstChild.nextSibling, _el$27 = _el$22.nextSibling.firstChild.nextSibling, _el$28 = _el$21.nextSibling, _el$29 = _el$28.firstChild, _el$31 = _el$29.firstChild.nextSibling, _el$34 = _el$29.nextSibling.firstChild.nextSibling, _el$35 = _el$28.nextSibling, _el$36 = _el$35.firstChild, _el$38 = _el$36.firstChild.nextSibling, _el$41 = _el$36.nextSibling.firstChild.nextSibling, _el$42 = _el$35.nextSibling, _el$43 = _el$42.firstChild, _el$45 = _el$43.firstChild.nextSibling, _el$48 = _el$43.nextSibling.firstChild.nextSibling, _el$49 = _el$42.nextSibling, _el$50 = _el$49.firstChild, _el$52 = _el$50.firstChild.nextSibling, _el$55 = _el$50.nextSibling.firstChild.nextSibling, _el$56 = _el$49.nextSibling, _el$59 = _el$56.firstChild.firstChild.nextSibling, _el$60 = _el$56.nextSibling, _el$61 = _el$60.firstChild, _el$63 = _el$61.firstChild.nextSibling, _el$66 = _el$61.nextSibling.firstChild.nextSibling, _el$67 = _el$60.nextSibling, _el$68 = _el$67.firstChild, _el$70 = _el$68.firstChild.nextSibling, _el$73 = _el$68.nextSibling.firstChild.nextSibling, _el$74 = _el$67.nextSibling, _el$75 = _el$74.firstChild, _el$77 = _el$75.firstChild.nextSibling, _el$80 = _el$75.nextSibling.firstChild.nextSibling, _el$81 = _el$74.nextSibling, _el$82 = _el$81.firstChild, _el$84 = _el$82.firstChild.nextSibling, _el$87 = _el$82.nextSibling.firstChild.nextSibling, _el$88 = _el$81.nextSibling, _el$89 = _el$88.firstChild, _el$91 = _el$89.firstChild.nextSibling, _el$94 = _el$89.nextSibling.firstChild.nextSibling, _el$95 = _el$88.nextSibling, _el$97 = _el$95.firstChild.nextSibling, _el$98 = _el$97.nextSibling, _el$99 = _el$98.firstChild, _el$101 = _el$99.firstChild.nextSibling, _el$104 = _el$99.nextSibling.firstChild.nextSibling, _el$106 = _el$98.nextSibling.firstChild;\n insert(_el$19, () => harness().name);\n insert(_el$20, () => props.dirty ? \"Unsaved changes\" : \"Saved\");\n _el$24.$$input = (event) => {\n updateHarness({ id: event.currentTarget.value.trim() });\n };\n _el$27.$$input = (event) => {\n updateHarness({ name: event.currentTarget.value });\n };\n _el$31.addEventListener(\"change\", (event) => {\n updateHarness({ provider: event.currentTarget.value === \"\" ? void 0 : event.currentTarget.value });\n });\n _el$34.$$input = (event) => {\n updateHarness({ model: event.currentTarget.value.trim() || void 0 });\n };\n _el$38.$$input = (event) => {\n const value = event.currentTarget.value.trim();\n updateHarness({ priority: value ? Number(value) : void 0 });\n };\n _el$41.$$input = (event) => {\n updateHarness({ cwd: event.currentTarget.value.trim() || void 0 });\n };\n _el$45.$$input = (event) => {\n updateHarness({ prompt: event.currentTarget.value.trim() || void 0 });\n };\n _el$48.$$input = (event) => {\n updateHarness({ promptFile: event.currentTarget.value.trim() || void 0 });\n };\n _el$52.$$input = (event) => {\n updateHarness({ systemPrompt: event.currentTarget.value.trim() || void 0 });\n };\n _el$55.$$input = (event) => {\n updateHarness({ systemPromptFile: event.currentTarget.value.trim() || void 0 });\n };\n _el$59.$$input = (event) => {\n updateHarness({ fallbackHarnessIds: parseCsvValues(event.currentTarget.value) });\n };\n _el$63.$$input = (event) => {\n updateMatch({ eventKinds: parseCsvValues(event.currentTarget.value) });\n };\n _el$66.$$input = (event) => {\n updateMatch({ meetingIds: parseCsvValues(event.currentTarget.value) });\n };\n _el$70.$$input = (event) => {\n updateMatch({ folderIds: parseCsvValues(event.currentTarget.value) });\n };\n _el$73.$$input = (event) => {\n updateMatch({ folderNames: parseCsvValues(event.currentTarget.value) });\n };\n _el$77.$$input = (event) => {\n updateMatch({ tags: parseCsvValues(event.currentTarget.value) });\n };\n _el$80.$$input = (event) => {\n updateMatch({ titleIncludes: parseCsvValues(event.currentTarget.value) });\n };\n _el$84.$$input = (event) => {\n updateMatch({ titleMatches: event.currentTarget.value.trim() || void 0 });\n };\n _el$87.addEventListener(\"change\", (event) => {\n updateMatch({ transcriptLoaded: event.currentTarget.value === \"\" ? void 0 : event.currentTarget.value === \"true\" });\n });\n _el$91.$$input = (event) => {\n updateMatch({ calendarEventIds: parseCsvValues(event.currentTarget.value) });\n };\n _el$94.$$input = (event) => {\n updateMatch({ recurringEventIds: parseCsvValues(event.currentTarget.value) });\n };\n insert(_el$97, (() => {\n var _c$3 = memo(() => !!props.selectedMeeting);\n return () => _c$3() ? `Run ${harness().name} against ${props.selectedMeeting.meeting.title || props.selectedMeeting.meeting.id}.` : \"Select a meeting to explain and test this harness.\";\n })());\n _el$101.addEventListener(\"change\", (event) => {\n props.onTestKindChange(event.currentTarget.value);\n });\n addEventListener(_el$106, \"click\", props.onTest, true);\n insert(_el$95, createComponent(Show, {\n get when() {\n return selectedExplanation();\n },\n children: (explanation) => [(() => {\n var _el$108 = _tmpl$7();\n insert(_el$108, () => explanation().matched ? \"Matched selected meeting\" : \"Did not match selected meeting\");\n createRenderEffect(() => setAttribute(_el$108, \"data-status\", explanation().matched ? \"completed\" : \"warning\"));\n return _el$108;\n })(), (() => {\n var _el$109 = _tmpl$8();\n insert(_el$109, createComponent(For, {\n get each() {\n return explanation().reasons;\n },\n children: (reason) => (() => {\n var _el$110 = _tmpl$9();\n insert(_el$110, reason);\n return _el$110;\n })()\n }));\n return _el$109;\n })()]\n }), null);\n insert(_el$95, createComponent(Show, {\n get when() {\n return selectedResult();\n },\n children: (result) => (() => {\n var _el$111 = _tmpl$1(), _el$113 = _el$111.firstChild.nextSibling;\n insert(_el$113, () => result().status, null);\n insert(_el$113, (() => {\n var _c$4 = memo(() => !!result().provider);\n return () => _c$4() ? ` • ${result().provider}` : \"\";\n })(), null);\n insert(_el$113, (() => {\n var _c$5 = memo(() => !!result().model);\n return () => _c$5() ? `/${result().model}` : \"\";\n })(), null);\n insert(_el$113, (() => {\n var _c$6 = memo(() => !!result().parseMode);\n return () => _c$6() ? ` • ${result().parseMode}` : \"\";\n })(), null);\n insert(_el$111, createComponent(Show, {\n get when() {\n return result().status === \"failed\";\n },\n get children() {\n var _el$114 = _tmpl$5();\n insert(_el$114, () => result().error);\n return _el$114;\n }\n }), null);\n insert(_el$111, createComponent(Show, {\n get when() {\n return result().status === \"completed\";\n },\n get children() {\n return [(() => {\n var _el$115 = _tmpl$0(), _el$116 = _el$115.firstChild, _el$118 = _el$116.firstChild.nextSibling, _el$121 = _el$116.nextSibling.firstChild.nextSibling;\n createRenderEffect(() => _el$118.value = result().prompt);\n createRenderEffect(() => _el$121.value = result().rawOutput ?? \"\");\n return _el$115;\n })(), createComponent(Show, {\n get when() {\n return result().structured;\n },\n children: (structured) => (() => {\n var _el$122 = _tmpl$10(), _el$123 = _el$122.firstChild, _el$125 = _el$123.firstChild.nextSibling, _el$128 = _el$123.nextSibling.firstChild.nextSibling;\n createRenderEffect(() => _el$125.value = structured().title);\n createRenderEffect(() => _el$128.value = structured().summary ?? \"\");\n return _el$122;\n })()\n })];\n }\n }), null);\n return _el$111;\n })()\n }), null);\n insert(_el$17, createComponent(Show, {\n get when() {\n return props.error;\n },\n get children() {\n var _el$107 = _tmpl$5();\n insert(_el$107, () => props.error);\n return _el$107;\n }\n }), null);\n createRenderEffect((_p$) => {\n var _v$6 = props.dirty ? \"busy\" : \"ok\", _v$7 = !props.selectedMeeting;\n _v$6 !== _p$.e && setAttribute(_el$20, \"data-tone\", _p$.e = _v$6);\n _v$7 !== _p$.t && (_el$106.disabled = _p$.t = _v$7);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n createRenderEffect(() => _el$24.value = harness().id);\n createRenderEffect(() => _el$27.value = harness().name);\n createRenderEffect(() => _el$31.value = harness().provider ?? \"\");\n createRenderEffect(() => _el$34.value = harness().model ?? \"\");\n createRenderEffect(() => _el$38.value = harness().priority?.toString() ?? \"\");\n createRenderEffect(() => _el$41.value = harness().cwd ?? \"\");\n createRenderEffect(() => _el$45.value = harness().prompt ?? \"\");\n createRenderEffect(() => _el$48.value = harness().promptFile ?? \"\");\n createRenderEffect(() => _el$52.value = harness().systemPrompt ?? \"\");\n createRenderEffect(() => _el$55.value = harness().systemPromptFile ?? \"\");\n createRenderEffect(() => _el$59.value = csvValues(harness().fallbackHarnessIds));\n createRenderEffect(() => _el$63.value = csvValues(harness().match?.eventKinds));\n createRenderEffect(() => _el$66.value = csvValues(harness().match?.meetingIds));\n createRenderEffect(() => _el$70.value = csvValues(harness().match?.folderIds));\n createRenderEffect(() => _el$73.value = csvValues(harness().match?.folderNames));\n createRenderEffect(() => _el$77.value = csvValues(harness().match?.tags));\n createRenderEffect(() => _el$80.value = csvValues(harness().match?.titleIncludes));\n createRenderEffect(() => _el$84.value = harness().match?.titleMatches ?? \"\");\n createRenderEffect(() => _el$87.value = harness().match?.transcriptLoaded === void 0 ? \"\" : String(harness().match?.transcriptLoaded));\n createRenderEffect(() => _el$91.value = csvValues(harness().match?.calendarEventIds));\n createRenderEffect(() => _el$94.value = csvValues(harness().match?.recurringEventIds));\n createRenderEffect(() => _el$101.value = props.testKind);\n createRenderEffect(() => _el$104.value = props.explanationEventKind ?? \"Select a meeting\");\n return _el$17;\n })()\n }), null);\n createRenderEffect((_p$) => {\n var _v$ = !selectedHarness(), _v$2 = !selectedHarness(), _v$3 = !props.dirty;\n _v$ !== _p$.e && (_el$7.disabled = _p$.e = _v$);\n _v$2 !== _p$.t && (_el$8.disabled = _p$.t = _v$2);\n _v$3 !== _p$.a && (_el$0.disabled = _p$.a = _v$3);\n return _p$;\n }, {\n e: void 0,\n t: void 0,\n a: void 0\n });\n return _el$;\n })();\n}\ndelegateEvents([\"click\", \"input\"]);\n//#endregion\n//#region src/web-app/App.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$ = /* @__PURE__ */ template(`<div class=shell><aside class=\"pane sidebar\"></aside><main class=\"pane detail\"><section class=toolbar><div class=toolbar-actions><button class=\"button button--primary\"type=button>Sync now</button><button class=\"button button--secondary\"type=button>Clear Filters</button><button class=\"button button--secondary\"type=button>Export Notes</button><button class=\"button button--secondary\"type=button>Export Transcripts</button></div><p>Solid-powered web workspace on top of the same local server, sync loop, and shared app contracts.`);\nfunction browserConfig() {\n return { passwordRequired: Boolean(window.__GRANOLA_SERVER__?.passwordRequired) };\n}\nasync function requestJson(path, init) {\n const response = await fetch(path, init);\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const error = typeof payload.error === \"string\" && payload.error.trim() ? payload.error : response.statusText || \"Request failed\";\n throw new Error(error);\n }\n return payload;\n}\nfunction App() {\n const startup = startupSelectionFromSearch(window.location.search);\n const initialPreferences = parseWorkspacePreferences(window.localStorage.getItem(granolaWebWorkspaceStorageKey));\n const [state, setState] = createStore({\n apiKeyDraft: \"\",\n automationArtefactDraftMarkdown: \"\",\n automationArtefactDraftSummary: \"\",\n automationArtefactDraftTitle: \"\",\n automationArtefactError: \"\",\n automationArtefacts: [],\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n harnessDirty: false,\n harnessError: \"\",\n harnessExplainEventKind: null,\n harnessExplanations: [],\n harnessTestKind: \"notes\",\n harnessTestResult: null,\n harnesses: [],\n listError: \"\",\n meetingSource: \"live\",\n meetings: [],\n processingIssueError: \"\",\n processingIssues: [],\n quickOpen: \"\",\n recentMeetings: initialPreferences.recentMeetings,\n savedFilters: initialPreferences.savedFilters,\n search: \"\",\n selectedAutomationArtefactId: null,\n selectedFolderId: startup.folderId || null,\n selectedHarnessId: null,\n selectedMeetingBundle: null,\n selectedMeetingId: startup.meetingId || null,\n selectedMeeting: null,\n reviewNote: \"\",\n serverLocked: browserConfig().passwordRequired,\n serverPassword: \"\",\n sort: \"updated-desc\",\n statusLabel: browserConfig().passwordRequired ? \"Server locked\" : \"Connecting…\",\n statusTone: browserConfig().passwordRequired ? \"error\" : \"idle\",\n updatedFrom: \"\",\n updatedTo: \"\",\n workspaceTab: parseWorkspaceTab(startup.workspaceTab)\n });\n let client = null;\n let unsubscribe;\n const setStatus = (label, tone = \"idle\") => {\n setState({\n statusLabel: label,\n statusTone: tone\n });\n };\n const updatePreferences = (updater) => {\n const next = updater({\n recentMeetings: state.recentMeetings,\n savedFilters: state.savedFilters\n });\n window.localStorage.setItem(granolaWebWorkspaceStorageKey, serialiseWorkspacePreferences(next));\n setState(\"recentMeetings\", next.recentMeetings);\n setState(\"savedFilters\", next.savedFilters);\n };\n const mergeAuthState = async (authState) => {\n if (!client) return;\n const nextState = client.getState();\n if (authState) {\n setState(\"appState\", {\n ...nextState,\n auth: authState\n });\n return;\n }\n try {\n setState(\"appState\", {\n ...nextState,\n auth: await client.inspectAuth()\n });\n } catch {\n setState(\"appState\", nextState);\n }\n };\n const detachClient = async () => {\n unsubscribe?.();\n unsubscribe = void 0;\n if (client) {\n await client.close().catch(() => void 0);\n client = null;\n }\n };\n const attachClient = async () => {\n await detachClient();\n client = await createGranolaServerClient(window.location.origin);\n setState(\"appState\", client.getState());\n unsubscribe = client.subscribe((event) => {\n setState(\"appState\", event.state);\n });\n await mergeAuthState();\n };\n const loadFolders = async (refresh = false) => {\n if (!client) return;\n try {\n setState(\"folderError\", \"\");\n const result = await client.listFolders({\n forceRefresh: refresh,\n limit: 100\n });\n setState(\"folders\", result.folders);\n if (state.selectedFolderId && !result.folders.some((folder) => folder.id === state.selectedFolderId)) setState(\"selectedFolderId\", null);\n } catch (error) {\n setState(\"folderError\", error instanceof Error ? error.message : String(error));\n setState(\"folders\", []);\n setState(\"selectedFolderId\", null);\n }\n };\n const loadAutomationRuns = async () => {\n if (!client) return;\n try {\n setState(\"automationRuns\", (await client.listAutomationRuns({ limit: 20 })).runs);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const syncSelectedArtefact = (artefacts, options = {}) => {\n const preferred = (options.preferredId ? artefacts.find((candidate) => candidate.id === options.preferredId) : void 0) ?? (options.preferredMeetingId ? artefacts.find((candidate) => candidate.meetingId === options.preferredMeetingId && candidate.status === \"generated\") : void 0) ?? artefacts.find((candidate) => candidate.status === \"generated\") ?? artefacts[0];\n setState(\"selectedAutomationArtefactId\", preferred?.id ?? null);\n setState(\"automationArtefactDraftTitle\", preferred?.structured.title ?? \"\");\n setState(\"automationArtefactDraftSummary\", preferred?.structured.summary ?? \"\");\n setState(\"automationArtefactDraftMarkdown\", preferred?.structured.markdown ?? \"\");\n setState(\"reviewNote\", \"\");\n };\n const loadAutomationArtefacts = async (options = {}) => {\n if (!client) return;\n try {\n setState(\"automationArtefactError\", \"\");\n const result = await client.listAutomationArtefacts({ limit: 30 });\n setState(\"automationArtefacts\", result.artefacts);\n syncSelectedArtefact(result.artefacts, {\n preferredId: options.preferredId ?? state.selectedAutomationArtefactId,\n preferredMeetingId: options.preferredMeetingId ?? state.selectedMeetingId\n });\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setState(\"automationArtefacts\", []);\n syncSelectedArtefact([]);\n }\n };\n const loadProcessingIssues = async () => {\n if (!client) return;\n try {\n setState(\"processingIssueError\", \"\");\n setState(\"processingIssues\", (await client.listProcessingIssues({ limit: 20 })).issues);\n } catch (error) {\n setState(\"processingIssueError\", error instanceof Error ? error.message : String(error));\n setState(\"processingIssues\", []);\n }\n };\n const selectHarnessId = (harnesses, preferredId) => {\n if (preferredId && harnesses.some((harness) => harness.id === preferredId)) return preferredId;\n return harnesses[0]?.id ?? null;\n };\n const selectedHarness = () => state.harnesses.find((harness) => harness.id === state.selectedHarnessId) ?? null;\n const loadHarnessExplanations = async (meetingId) => {\n if (!client || !meetingId) {\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\n return;\n }\n try {\n const result = await client.explainAgentHarnesses(meetingId);\n setState(\"harnessExplainEventKind\", result.eventKind);\n setState(\"harnessExplanations\", result.harnesses);\n } catch (error) {\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\n setState(\"harnessError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadHarnesses = async (preferredId) => {\n if (!client) return;\n try {\n setState(\"harnessError\", \"\");\n const result = await client.listAgentHarnesses();\n const nextSelectedHarnessId = selectHarnessId(result.harnesses, preferredId ?? state.selectedHarnessId);\n setState(\"harnesses\", result.harnesses);\n setState(\"selectedHarnessId\", nextSelectedHarnessId);\n setState(\"harnessDirty\", false);\n setState(\"harnessTestResult\", null);\n await loadHarnessExplanations(state.selectedMeetingId);\n } catch (error) {\n setState(\"harnessError\", error instanceof Error ? error.message : String(error));\n setState(\"harnesses\", []);\n setState(\"selectedHarnessId\", null);\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\n setState(\"harnessTestResult\", null);\n }\n };\n const updateHarness = (nextHarness) => {\n setState(\"harnesses\", state.harnesses.map((harness) => harness.id === nextHarness.id ? nextHarness : harness));\n setState(\"selectedHarnessId\", nextHarness.id);\n setState(\"harnessDirty\", true);\n setState(\"harnessTestResult\", null);\n };\n const createHarness = () => {\n const nextHarness = createHarnessTemplate(state.harnesses);\n setState(\"harnesses\", [...state.harnesses, nextHarness]);\n setState(\"selectedHarnessId\", nextHarness.id);\n setState(\"harnessDirty\", true);\n setState(\"harnessTestResult\", null);\n };\n const duplicateHarness = () => {\n const harness = selectedHarness();\n if (!harness) return;\n const nextHarness = duplicateHarnessTemplate(state.harnesses, harness);\n setState(\"harnesses\", [...state.harnesses, nextHarness]);\n setState(\"selectedHarnessId\", nextHarness.id);\n setState(\"harnessDirty\", true);\n setState(\"harnessTestResult\", null);\n };\n const removeHarness = () => {\n if (!state.selectedHarnessId) return;\n const nextHarnesses = state.harnesses.filter((harness) => harness.id !== state.selectedHarnessId);\n setState(\"harnesses\", nextHarnesses);\n setState(\"selectedHarnessId\", selectHarnessId(nextHarnesses, null));\n setState(\"harnessDirty\", true);\n setState(\"harnessTestResult\", null);\n };\n const saveHarnesses = async () => {\n if (!client) return;\n setStatus(\"Saving harnesses…\", \"busy\");\n try {\n const result = await client.saveAgentHarnesses(state.harnesses);\n const nextSelectedHarnessId = selectHarnessId(result.harnesses, state.selectedHarnessId);\n setState(\"harnesses\", result.harnesses);\n setState(\"selectedHarnessId\", nextSelectedHarnessId);\n setState(\"harnessDirty\", false);\n setState(\"harnessError\", \"\");\n await loadHarnessExplanations(state.selectedMeetingId);\n setStatus(\"Harnesses saved\", \"ok\");\n } catch (error) {\n setState(\"harnessError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Harness save failed\", \"error\");\n }\n };\n const reloadHarnesses = async () => {\n setStatus(\"Reloading harnesses…\", \"busy\");\n await loadHarnesses(state.selectedHarnessId);\n setStatus(\"Harnesses reloaded\", \"ok\");\n };\n const testHarness = async () => {\n if (!client) return;\n const harness = selectedHarness();\n if (!harness) {\n setStatus(\"Select a harness first\", \"error\");\n return;\n }\n const meetingId = state.selectedMeetingId;\n if (!meetingId) {\n setStatus(\"Select a meeting first\", \"error\");\n return;\n }\n setStatus(\"Testing harness…\", \"busy\");\n try {\n const bundle = state.selectedMeetingBundle?.document.id === meetingId ? state.selectedMeetingBundle : await client.getMeeting(meetingId, { requireCache: true });\n setState(\"harnessTestResult\", (await client.evaluateAutomationCases([{\n bundle,\n id: `web:${meetingId}`,\n title: bundle.meeting.meeting.title || bundle.document.title || bundle.document.id\n }], {\n harnessIds: [harness.id],\n kind: state.harnessTestKind,\n model: harness.model,\n provider: harness.provider\n })).results[0] ?? null);\n setStatus(\"Harness test complete\", \"ok\");\n } catch (error) {\n setState(\"harnessTestResult\", {\n caseId: `web:${meetingId}`,\n caseTitle: state.selectedMeeting?.meeting.title || meetingId,\n error: error instanceof Error ? error.message : String(error),\n harnessId: harness.id,\n harnessName: harness.name,\n prompt: \"\",\n status: \"failed\"\n });\n setStatus(\"Harness test failed\", \"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 await loadHarnessExplanations(bundle.document.id);\n await loadAutomationArtefacts({\n preferredId: state.selectedAutomationArtefactId,\n preferredMeetingId: bundle.document.id\n });\n } catch (error) {\n setState(\"selectedMeetingBundle\", null);\n setState(\"selectedMeeting\", null);\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\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 setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\n await loadAutomationArtefacts({\n preferredId: state.selectedAutomationArtefactId,\n preferredMeetingId: null\n });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n setState(\"listError\", message);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", message);\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\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 loadHarnesses(),\n loadAutomationRuns(),\n loadAutomationArtefacts(),\n loadProcessingIssues(),\n mergeAuthState()\n ]);\n await loadMeetings({ refresh: forceRefresh });\n setState(\"serverLocked\", false);\n setStatus(forceRefresh ? \"Sync complete\" : state.meetingSource === \"index\" ? \"Loaded from index\" : \"Connected\", \"ok\");\n };\n const connectAndRefresh = async (forceRefresh = false) => {\n try {\n await refreshAll(forceRefresh);\n } catch (error) {\n setStatus(\"Connection failed\", \"error\");\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const quickOpenMeeting = async () => {\n if (!client) return;\n const query = state.quickOpen.trim();\n if (!query) {\n setStatus(\"Enter a title or id\", \"error\");\n return;\n }\n setStatus(\"Opening meeting…\", \"busy\");\n try {\n const bundle = await client.findMeeting(query);\n setState(\"selectedFolderId\", bundle.meeting.meeting.folders[0]?.id || null);\n setState(\"search\", \"\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n await loadMeetings({ preferredMeetingId: bundle.document.id });\n setStatus(\"Connected\", \"ok\");\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Quick open failed\", \"error\");\n }\n };\n const saveApiKey = async () => {\n if (!client) return;\n if (!state.apiKeyDraft.trim()) {\n setStatus(\"Enter a Granola API key\", \"error\");\n return;\n }\n setStatus(\"Saving API key…\", \"busy\");\n try {\n const auth = await client.loginAuth({ apiKey: state.apiKeyDraft.trim() });\n setState(\"apiKeyDraft\", \"\");\n await mergeAuthState(auth);\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"API key save failed\", \"error\");\n }\n };\n const importDesktopSession = async () => {\n if (!client) return;\n setStatus(\"Importing desktop session…\", \"busy\");\n try {\n await mergeAuthState(await client.loginAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Auth import failed\", \"error\");\n }\n };\n const refreshAuth = async () => {\n if (!client) return;\n setStatus(\"Refreshing session…\", \"busy\");\n try {\n await mergeAuthState(await client.refreshAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Refresh failed\", \"error\");\n }\n };\n const switchAuthMode = async (mode) => {\n if (!client) return;\n setStatus(\"Switching auth source…\", \"busy\");\n try {\n await mergeAuthState(await client.switchAuthMode(mode));\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Switch failed\", \"error\");\n }\n };\n const logout = async () => {\n if (!client) return;\n setStatus(\"Signing out…\", \"busy\");\n try {\n await mergeAuthState(await client.logoutAuth());\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Sign out failed\", \"error\");\n }\n };\n const exportNotes = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder notes…\" : \"Exporting notes…\", \"busy\");\n try {\n await client.exportNotes(\"markdown\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const clearFilters = async () => {\n setState(\"search\", \"\");\n setState(\"sort\", \"updated-desc\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n setState(\"selectedFolderId\", null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(\"Filters cleared\", \"ok\");\n };\n const saveCurrentFilter = () => {\n updatePreferences((preferences) => saveWorkspaceFilter(preferences, {\n folders: state.folders,\n search: state.search,\n selectedFolderId: state.selectedFolderId,\n sort: state.sort,\n updatedFrom: state.updatedFrom,\n updatedTo: state.updatedTo\n }, { idFactory: () => `filter-${Date.now()}` }));\n setStatus(\"Saved filter\", \"ok\");\n };\n const applySavedFilterPreset = async (id) => {\n const preset = state.savedFilters.find((candidate) => candidate.id === id);\n if (!preset) return;\n const nextFilters = applyWorkspaceFilter(preset);\n setState(\"search\", nextFilters.search);\n setState(\"selectedFolderId\", nextFilters.selectedFolderId);\n setState(\"sort\", nextFilters.sort);\n setState(\"updatedFrom\", nextFilters.updatedFrom);\n setState(\"updatedTo\", nextFilters.updatedTo);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(`Applied ${preset.label}`, \"ok\");\n };\n const removeSavedFilterPreset = (id) => {\n updatePreferences((preferences) => removeWorkspaceFilter(preferences, id));\n setStatus(\"Removed saved filter\", \"ok\");\n };\n const openRecentMeeting = async (meetingId, folderId) => {\n if (folderId !== void 0) {\n setState(\"selectedFolderId\", folderId || null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings({ preferredMeetingId: meetingId });\n return;\n }\n await loadMeeting(meetingId);\n };\n const meetingEmptyHint = () => {\n if (!state.appState) return \"Connect to the local server to load meetings.\";\n if (state.appState.auth.lastError) return \"Resolve auth first, then sync again.\";\n if (!state.appState.documents.loaded && !state.appState.sync.lastCompletedAt) return \"Run Sync now to populate your local meeting index.\";\n return \"Try a different folder or filter, or sync again.\";\n };\n const exportTranscripts = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder transcripts…\" : \"Exporting transcripts…\", \"busy\");\n try {\n await client.exportTranscripts(\"text\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const rerunJob = async (jobId) => {\n if (!client) return;\n setStatus(\"Rerunning export…\", \"busy\");\n try {\n await client.rerunExportJob(jobId);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Rerun failed\", \"error\");\n }\n };\n const selectedAutomationArtefact = () => state.automationArtefacts.find((artefact) => artefact.id === state.selectedAutomationArtefactId) || null;\n const resolveAutomationRun = async (id, decision) => {\n if (!client) return;\n setStatus(decision === \"approve\" ? \"Approving automation…\" : \"Rejecting automation…\", \"busy\");\n try {\n await client.resolveAutomationRun(id, decision);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Automation decision failed\", \"error\");\n }\n };\n const recoverProcessingIssue = async (id) => {\n if (!client) return;\n setStatus(\"Recovering processing issue…\", \"busy\");\n try {\n const result = await client.recoverProcessingIssue(id);\n await refreshAll();\n setStatus(result.runCount > 0 ? `Recovered ${result.issue.kind} and re-ran ${result.runCount} pipeline${result.runCount === 1 ? \"\" : \"s\"}` : `Recovered ${result.issue.kind}`, \"ok\");\n } catch (error) {\n setState(\"processingIssueError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Recovery failed\", \"error\");\n }\n };\n const selectAutomationArtefact = async (id) => {\n if (!client) return;\n try {\n const artefact = state.automationArtefacts.find((candidate) => candidate.id === id) ?? await client.getAutomationArtefact(id);\n setState(\"selectedAutomationArtefactId\", artefact.id);\n setState(\"automationArtefactDraftTitle\", artefact.structured.title);\n setState(\"automationArtefactDraftSummary\", artefact.structured.summary ?? \"\");\n setState(\"automationArtefactDraftMarkdown\", artefact.structured.markdown);\n setState(\"reviewNote\", \"\");\n if (artefact.meetingId !== state.selectedMeetingId) await loadMeeting(artefact.meetingId);\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unable to open artefact\", \"error\");\n }\n };\n const saveAutomationArtefact = async () => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(\"Saving artefact edits…\", \"busy\");\n try {\n const artefact = await client.updateAutomationArtefact(state.selectedAutomationArtefactId, {\n markdown: state.automationArtefactDraftMarkdown,\n note: state.reviewNote || void 0,\n summary: state.automationArtefactDraftSummary,\n title: state.automationArtefactDraftTitle\n });\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(\"Artefact updated\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact save failed\", \"error\");\n }\n };\n const resolveAutomationArtefact = async (decision) => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(decision === \"approve\" ? \"Approving artefact…\" : \"Rejecting artefact…\", \"busy\");\n try {\n const artefact = await client.resolveAutomationArtefact(state.selectedAutomationArtefactId, decision, { note: state.reviewNote || void 0 });\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(decision === \"approve\" ? \"Artefact approved\" : \"Artefact rejected\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact decision failed\", \"error\");\n }\n };\n const rerunAutomationArtefact = async () => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(\"Rerunning artefact pipeline…\", \"busy\");\n try {\n const artefact = await client.rerunAutomationArtefact(state.selectedAutomationArtefactId);\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(\"Artefact rerun complete\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact rerun failed\", \"error\");\n }\n };\n const unlockServer = async () => {\n if (!state.serverPassword.trim()) {\n setStatus(\"Enter the server password\", \"error\");\n return;\n }\n setStatus(\"Unlocking server…\", \"busy\");\n try {\n await requestJson(\"/auth/unlock\", {\n body: JSON.stringify({ password: state.serverPassword }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n setState(\"serverPassword\", \"\");\n setState(\"serverLocked\", false);\n await connectAndRefresh(true);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unlock failed\", \"error\");\n }\n };\n const lockServer = async () => {\n try {\n await requestJson(\"/auth/lock\", { method: \"POST\" });\n } catch {}\n await detachClient();\n setState({\n appState: null,\n automationArtefactDraftMarkdown: \"\",\n automationArtefactDraftSummary: \"\",\n automationArtefactDraftTitle: \"\",\n automationArtefactError: \"\",\n automationArtefacts: [],\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n harnessDirty: false,\n harnessError: \"\",\n harnessExplainEventKind: null,\n harnessExplanations: [],\n harnessTestResult: null,\n harnesses: [],\n listError: \"\",\n meetings: [],\n processingIssueError: \"\",\n processingIssues: [],\n reviewNote: \"\",\n selectedAutomationArtefactId: null,\n selectedFolderId: null,\n selectedHarnessId: null,\n selectedMeeting: null,\n selectedMeetingBundle: null,\n selectedMeetingId: null,\n serverLocked: true,\n serverPassword: \"\"\n });\n setStatus(\"Server locked\", \"error\");\n };\n createEffect(() => {\n const nextPath = buildBrowserUrlPath(window.location.href, {\n selectedFolderId: state.selectedFolderId,\n selectedMeetingId: state.selectedMeetingId,\n workspaceTab: state.workspaceTab\n });\n if (nextPath !== `${window.location.pathname}${window.location.search}${window.location.hash}`) history.replaceState(null, \"\", nextPath);\n });\n createEffect(() => {\n if (!state.appState?.automation.loaded || !client) return;\n loadAutomationRuns();\n loadAutomationArtefacts();\n loadProcessingIssues();\n });\n onMount(() => {\n const onKeyDown = (event) => {\n const target = event.target;\n if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return;\n const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);\n if (nextTab) setState(\"workspaceTab\", nextTab);\n };\n document.addEventListener(\"keydown\", onKeyDown);\n onCleanup(() => {\n document.removeEventListener(\"keydown\", onKeyDown);\n });\n if (!state.serverLocked) connectAndRefresh();\n });\n onCleanup(() => {\n detachClient();\n });\n return (() => {\n var _el$ = _tmpl$(), _el$2 = _el$.firstChild, _el$3 = _el$2.nextSibling, _el$4 = _el$3.firstChild, _el$6 = _el$4.firstChild.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling;\n insert(_el$2, createComponent(ToolbarFilters, {\n onQuickOpen: () => {\n quickOpenMeeting();\n },\n onQuickOpenInput: (value) => {\n setState(\"quickOpen\", value);\n },\n onSearchInput: (value) => {\n setState(\"search\", value.trim());\n loadMeetings();\n },\n onSortChange: (value) => {\n setState(\"sort\", value);\n loadMeetings();\n },\n onUpdatedFromChange: (value) => {\n setState(\"updatedFrom\", value);\n loadMeetings();\n },\n onUpdatedToChange: (value) => {\n setState(\"updatedTo\", value);\n loadMeetings();\n },\n get quickOpen() {\n return state.quickOpen;\n },\n get search() {\n return state.search;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(SavedFiltersPanel, {\n get folders() {\n return state.folders;\n },\n onApply: (preset) => {\n applySavedFilterPreset(preset.id);\n },\n onRemove: removeSavedFilterPreset,\n onSaveCurrent: saveCurrentFilter,\n get savedFilters() {\n return state.savedFilters;\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get sort() {\n return state.sort;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$2, createComponent(RecentMeetingsPanel, {\n onOpen: (meeting) => {\n openRecentMeeting(meeting.id, meeting.folderId);\n },\n get recentMeetings() {\n return state.recentMeetings;\n }\n }), null);\n insert(_el$2, createComponent(FolderList, {\n get error() {\n return state.folderError;\n },\n get folders() {\n return state.folders;\n },\n onSelect: (folderId) => {\n setState(\"selectedFolderId\", folderId);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n loadMeetings();\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n }\n }), null);\n insert(_el$2, createComponent(MeetingList, {\n get error() {\n return state.listError;\n },\n get emptyHint() {\n return meetingEmptyHint();\n },\n get folders() {\n return state.folders;\n },\n get meetings() {\n return state.meetings;\n },\n onSelect: (meetingId) => {\n loadMeeting(meetingId);\n },\n get search() {\n return state.search;\n },\n get selectedFolderId() {\n return state.selectedFolderId;\n },\n get selectedMeetingId() {\n return state.selectedMeetingId;\n },\n get updatedFrom() {\n return state.updatedFrom;\n },\n get updatedTo() {\n return state.updatedTo;\n }\n }), null);\n insert(_el$3, createComponent(AppStatePanel, {\n get appState() {\n return state.appState;\n },\n get statusLabel() {\n return state.statusLabel;\n },\n get statusTone() {\n return state.statusTone;\n }\n }), _el$4);\n _el$6.$$click = () => {\n connectAndRefresh(true);\n };\n _el$7.$$click = () => {\n clearFilters();\n };\n _el$8.$$click = () => {\n exportNotes();\n };\n _el$9.$$click = () => {\n exportTranscripts();\n };\n insert(_el$3, createComponent(SecurityPanel, {\n onLock: () => {\n lockServer();\n },\n onPasswordChange: (value) => {\n setState(\"serverPassword\", value);\n },\n onUnlock: () => {\n unlockServer();\n },\n get password() {\n return state.serverPassword;\n },\n get visible() {\n return state.serverLocked;\n }\n }), null);\n insert(_el$3, createComponent(AuthPanel, {\n get apiKeyDraft() {\n return state.apiKeyDraft;\n },\n get auth() {\n return state.appState?.auth;\n },\n onApiKeyDraftChange: (value) => {\n setState(\"apiKeyDraft\", value);\n },\n onImportDesktopSession: () => {\n importDesktopSession();\n },\n onLogout: () => {\n logout();\n },\n onRefresh: () => {\n refreshAuth();\n },\n onSaveApiKey: () => {\n saveApiKey();\n },\n onSwitchMode: (mode) => {\n switchAuthMode(mode);\n }\n }), null);\n insert(_el$3, createComponent(HarnessEditorPanel, {\n get dirty() {\n return state.harnessDirty;\n },\n get error() {\n return state.harnessError;\n },\n get explanations() {\n return state.harnessExplanations;\n },\n get explanationEventKind() {\n return state.harnessExplainEventKind;\n },\n get harnesses() {\n return state.harnesses;\n },\n onChange: updateHarness,\n onDuplicate: duplicateHarness,\n onNew: createHarness,\n onReload: () => {\n reloadHarnesses();\n },\n onRemove: removeHarness,\n onSave: () => {\n saveHarnesses();\n },\n onSelect: (id) => {\n setState(\"selectedHarnessId\", id);\n setState(\"harnessTestResult\", null);\n },\n onTest: () => {\n testHarness();\n },\n onTestKindChange: (kind) => {\n setState(\"harnessTestKind\", kind);\n },\n get selectedHarness() {\n return selectedHarness();\n },\n get selectedHarnessId() {\n return state.selectedHarnessId;\n },\n get selectedMeeting() {\n return state.selectedMeeting;\n },\n get testKind() {\n return state.harnessTestKind;\n },\n get testResult() {\n return state.harnessTestResult;\n }\n }), null);\n insert(_el$3, createComponent(ExportJobsPanel, {\n get jobs() {\n return state.appState?.exports.jobs || [];\n },\n onRerun: (jobId) => {\n rerunJob(jobId);\n }\n }), null);\n insert(_el$3, createComponent(AutomationRunsPanel, {\n onApprove: (runId) => {\n resolveAutomationRun(runId, \"approve\");\n },\n onReject: (runId) => {\n resolveAutomationRun(runId, \"reject\");\n },\n get runs() {\n return state.automationRuns;\n }\n }), null);\n insert(_el$3, createComponent(ProcessingIssuesPanel, {\n get issues() {\n return state.processingIssues;\n },\n onOpenMeeting: (meetingId) => {\n loadMeeting(meetingId);\n },\n onRecover: (issueId) => {\n recoverProcessingIssue(issueId);\n }\n }), null);\n insert(_el$3, createComponent(AutomationArtefactsPanel, {\n get artefacts() {\n return state.automationArtefacts;\n },\n onSelect: (artefactId) => {\n selectAutomationArtefact(artefactId);\n },\n get selectedArtefactId() {\n return state.selectedAutomationArtefactId;\n }\n }), null);\n insert(_el$3, createComponent(ArtefactReviewPanel, {\n get artefact() {\n return selectedAutomationArtefact();\n },\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get draftMarkdown() {\n return state.automationArtefactDraftMarkdown;\n },\n get draftSummary() {\n return state.automationArtefactDraftSummary;\n },\n get draftTitle() {\n return state.automationArtefactDraftTitle;\n },\n get error() {\n return state.automationArtefactError;\n },\n onApprove: () => {\n resolveAutomationArtefact(\"approve\");\n },\n onDraftMarkdownChange: (value) => {\n setState(\"automationArtefactDraftMarkdown\", value);\n },\n onDraftSummaryChange: (value) => {\n setState(\"automationArtefactDraftSummary\", value);\n },\n onDraftTitleChange: (value) => {\n setState(\"automationArtefactDraftTitle\", value);\n },\n onReject: () => {\n resolveAutomationArtefact(\"reject\");\n },\n onRerun: () => {\n rerunAutomationArtefact();\n },\n onReviewNoteChange: (value) => {\n setState(\"reviewNote\", value);\n },\n onSave: () => {\n saveAutomationArtefact();\n },\n get reviewNote() {\n return state.reviewNote;\n }\n }), null);\n insert(_el$3, createComponent(Workspace, {\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get detailError() {\n return state.detailError;\n },\n onSelectTab: (tab) => {\n setState(\"workspaceTab\", tab);\n },\n get selectedMeeting() {\n return state.selectedMeeting;\n },\n get tab() {\n return state.workspaceTab;\n }\n }), null);\n return _el$;\n })();\n}\ndelegateEvents([\"click\"]);\n//#endregion\n//#region src/web-app/main.tsx\n/** @jsxImportSource solid-js */\nvar root = document.getElementById(\"granola-web-root\");\nif (!root) throw new Error(\"Granola web root element not found\");\nrender(() => createComponent(App, {}), root);\n//#endregion\n";
9731
+ 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.shell--onboarding {\n grid-template-columns: minmax(0, 960px);\n justify-content: center;\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--onboarding {\n align-content: start;\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.onboarding-panel {\n display: grid;\n gap: 20px;\n padding: 0 24px 24px;\n}\n\n.onboarding-panel__hero {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 280px;\n gap: 18px;\n padding: 24px;\n border: 1px solid var(--line);\n border-radius: 24px;\n background:\n radial-gradient(circle at top right, rgba(13, 106, 109, 0.14), transparent 35%),\n linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(255, 255, 255, 0.72));\n}\n\n.onboarding-panel__eyebrow {\n margin: 0 0 10px;\n color: var(--warm);\n font-size: 0.82rem;\n font-weight: 700;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\n.onboarding-panel__hero h2 {\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.onboarding-panel__hero p {\n margin: 10px 0 0;\n color: var(--muted);\n line-height: 1.55;\n}\n\n.onboarding-panel__summary {\n display: grid;\n align-content: start;\n gap: 10px;\n padding: 18px;\n border: 1px solid var(--line);\n border-radius: 20px;\n background: rgba(255, 255, 255, 0.82);\n}\n\n.onboarding-steps {\n display: grid;\n gap: 16px;\n}\n\n.onboarding-step {\n display: grid;\n gap: 14px;\n padding: 22px 24px;\n border: 1px solid var(--line);\n border-radius: 24px;\n background: rgba(255, 255, 255, 0.76);\n}\n\n.onboarding-step[data-complete=\"true\"] {\n background: rgba(255, 255, 255, 0.9);\n}\n\n.onboarding-step__head {\n display: flex;\n flex-wrap: wrap;\n align-items: start;\n justify-content: space-between;\n gap: 12px;\n}\n\n.onboarding-step__number {\n display: block;\n margin-bottom: 8px;\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.onboarding-step h3 {\n margin: 0;\n font-size: 1.15rem;\n}\n\n.onboarding-step p {\n margin: 0;\n color: var(--muted);\n line-height: 1.55;\n}\n\n.onboarding-step__body {\n display: grid;\n gap: 14px;\n}\n\n.onboarding-providers {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n\n.onboarding-provider {\n width: 100%;\n display: grid;\n gap: 8px;\n text-align: left;\n padding: 16px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.78);\n color: inherit;\n cursor: pointer;\n}\n\n.onboarding-provider[data-selected=\"true\"] {\n border-color: rgba(13, 106, 109, 0.26);\n background: var(--panel-strong);\n box-shadow: inset 0 0 0 1px rgba(13, 106, 109, 0.16);\n}\n\n.onboarding-provider__title {\n font-weight: 700;\n}\n\n.onboarding-provider__body {\n color: var(--muted);\n font-size: 0.9rem;\n line-height: 1.45;\n}\n\n.security-panel__head h3,\n.auth-panel__head h3,\n.jobs-panel__head h3 {\n margin: 0;\n font-size: 0.92rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.security-panel__head p,\n.auth-panel__head p,\n.jobs-panel__head p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: 0.9rem;\n}\n\n.security-panel__body,\n.auth-panel__body {\n display: grid;\n gap: 12px;\n margin-top: 14px;\n}\n\n.auth-card,\n.job-card {\n display: grid;\n gap: 12px;\n padding: 14px 16px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.72);\n}\n\n.job-card--button {\n width: 100%;\n text-align: left;\n cursor: pointer;\n}\n\n.job-card--button[data-selected=\"true\"] {\n border-color: rgba(13, 106, 109, 0.26);\n background: var(--panel-strong);\n}\n\n.job-card__head {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n}\n\n.job-card__status {\n padding: 6px 10px;\n border-radius: 999px;\n background: var(--accent-soft);\n color: var(--accent);\n font-size: 0.82rem;\n font-weight: 700;\n}\n\n.job-card__status[data-status=\"running\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"failed\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"error\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"completed\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.job-card__status[data-status=\"warning\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"approved\"] {\n background: rgba(36, 107, 79, 0.12);\n color: var(--ok);\n}\n\n.job-card__status[data-status=\"generated\"] {\n background: rgba(163, 79, 47, 0.12);\n color: var(--warm);\n}\n\n.job-card__status[data-status=\"rejected\"] {\n background: rgba(157, 44, 44, 0.12);\n color: var(--error);\n}\n\n.job-card__status[data-status=\"superseded\"] {\n background: rgba(93, 107, 119, 0.12);\n color: var(--muted);\n}\n\n.workspace-tabs {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.workspace-tab,\n.button {\n border: 1px solid var(--line);\n border-radius: 999px;\n padding: 10px 14px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--ink);\n cursor: pointer;\n font-weight: 700;\n}\n\n.button {\n padding: 12px 16px;\n}\n\n.workspace-tab[data-selected=\"true\"],\n.button--primary {\n background: var(--ink);\n color: #fff;\n border-color: var(--ink);\n}\n\n.button--secondary {\n background: rgba(255, 255, 255, 0.72);\n}\n\n.button:disabled {\n cursor: not-allowed;\n opacity: 0.56;\n}\n\n.workspace-hint {\n margin-left: auto;\n color: var(--muted);\n font-size: 0.86rem;\n}\n\n.status-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));\n gap: 14px;\n}\n\n.status-label {\n display: block;\n margin-bottom: 6px;\n color: var(--muted);\n font-size: 0.78rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n padding: 0 24px 18px;\n}\n\n.detail-chip {\n padding: 10px 12px;\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n color: var(--muted);\n font-size: 0.88rem;\n}\n\n.detail-body {\n padding: 0 24px 24px;\n overflow: auto;\n}\n\n.review-panel {\n padding: 0 24px 24px;\n}\n\n.harness-panel {\n padding: 0 24px 24px;\n}\n\n.harness-grid {\n display: grid;\n grid-template-columns: minmax(260px, 320px) minmax(0, 1fr);\n gap: 18px;\n}\n\n.harness-toolbar,\n.harness-status-row {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n margin-bottom: 16px;\n}\n\n.harness-status-row h2 {\n margin: 0;\n font-size: 1.1rem;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n\n.harness-test-result {\n display: grid;\n gap: 14px;\n margin-top: 16px;\n}\n\n.review-body {\n display: grid;\n gap: 18px;\n}\n\n.review-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 18px;\n}\n\n.workspace-grid {\n display: grid;\n grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);\n gap: 18px;\n}\n\n.detail-section {\n margin-bottom: 20px;\n padding: 20px;\n background: rgba(255, 255, 255, 0.72);\n border: 1px solid var(--line);\n border-radius: 20px;\n}\n\n.detail-section h2 {\n margin: 0 0 14px;\n font-size: 1rem;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.detail-section h3 {\n margin: 18px 0 10px;\n font-size: 0.95rem;\n}\n\n.detail-pre {\n margin: 0;\n white-space: pre-wrap;\n word-break: break-word;\n font-family: var(--mono);\n line-height: 1.55;\n}\n\n.detail-list {\n margin: 0;\n padding-left: 18px;\n display: grid;\n gap: 10px;\n}\n\n.field-input--plain,\n.review-textarea {\n width: 100%;\n margin-top: 0;\n padding: 12px 14px;\n border: 1px solid var(--line);\n border-radius: 18px;\n background: rgba(255, 255, 255, 0.92);\n color: var(--ink);\n}\n\n.review-textarea {\n min-height: 220px;\n resize: vertical;\n}\n\n.review-textarea--summary {\n min-height: 96px;\n}\n\n.review-history {\n margin-bottom: 0;\n}\n\n.empty {\n margin: 24px;\n padding: 24px;\n border-radius: 20px;\n background: rgba(255, 255, 255, 0.72);\n color: var(--muted);\n}\n\n@media (max-width: 1024px) {\n .shell,\n .onboarding-panel__hero,\n .onboarding-providers,\n .harness-grid,\n .review-grid,\n .workspace-grid {\n grid-template-columns: 1fr;\n }\n}\n/*$vite$:1*/";
9732
+ 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 automationEvaluate: \"/automation/evaluate\",\n automationHarnesses: \"/automation/harnesses\",\n automationHarnessExplain: \"/automation/harnesses/explain\",\n automationMatches: \"/automation/matches\",\n automationArtefacts: \"/automation/artefacts\",\n automationRules: \"/automation/rules\",\n automationRuns: \"/automation/runs\",\n events: \"/events\",\n exportJobs: \"/exports/jobs\",\n exportNotes: \"/exports/notes\",\n exportTranscripts: \"/exports/transcripts\",\n folderResolve: \"/folders/resolve\",\n folders: \"/folders\",\n health: \"/health\",\n meetingResolve: \"/meetings/resolve\",\n meetings: \"/meetings\",\n processingIssues: \"/processing/issues\",\n root: \"/\",\n serverInfo: \"/server/info\",\n syncRun: \"/sync\",\n syncEvents: \"/sync/events\",\n state: \"/state\"\n};\nfunction appendSearchParams(path, params) {\n const url = new URL(path, \"http://localhost\");\n for (const [key, value] of Object.entries(params)) {\n if (value === void 0 || value === false || value === \"\") continue;\n url.searchParams.set(key, String(value));\n }\n return `${url.pathname}${url.search}`;\n}\nfunction granolaMeetingPath(id) {\n return `${granolaTransportPaths.meetings}/${encodeURIComponent(id)}`;\n}\nfunction granolaMeetingResolvePath(query, options = {}) {\n return appendSearchParams(granolaTransportPaths.meetingResolve, {\n includeTranscript: options.includeTranscript ? \"true\" : void 0,\n q: query\n });\n}\nfunction granolaMeetingsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.meetings, {\n folderId: options.folderId,\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search,\n sort: options.sort,\n updatedFrom: options.updatedFrom,\n updatedTo: options.updatedTo\n });\n}\nfunction granolaFolderPath(id) {\n return `${granolaTransportPaths.folders}/${encodeURIComponent(id)}`;\n}\nfunction granolaFolderResolvePath(query) {\n return appendSearchParams(granolaTransportPaths.folderResolve, { q: query });\n}\nfunction granolaFoldersPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.folders, {\n limit: options.limit,\n refresh: options.forceRefresh ? \"true\" : void 0,\n search: options.search\n });\n}\nfunction granolaExportJobsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.exportJobs, { limit: options.limit });\n}\nfunction granolaAutomationRunsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationRuns, {\n limit: options.limit,\n status: options.status\n });\n}\nfunction granolaAutomationHarnessExplainPath(meetingId) {\n return appendSearchParams(granolaTransportPaths.automationHarnessExplain, { meetingId });\n}\nfunction granolaAutomationArtefactsPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.automationArtefacts, {\n kind: options.kind,\n limit: options.limit,\n meetingId: options.meetingId,\n status: options.status\n });\n}\nfunction granolaAutomationRunDecisionPath(id, decision) {\n return `${granolaTransportPaths.automationRuns}/${encodeURIComponent(id)}/${decision}`;\n}\nfunction granolaAutomationArtefactRerunPath(id) {\n return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}/rerun`;\n}\nfunction granolaAutomationArtefactPath(id) {\n return `${granolaTransportPaths.automationArtefacts}/${encodeURIComponent(id)}`;\n}\nfunction granolaAutomationArtefactDecisionPath(id, decision) {\n return `${granolaAutomationArtefactPath(id)}/${decision}`;\n}\nfunction granolaAutomationArtefactUpdatePath(id) {\n return `${granolaAutomationArtefactPath(id)}/update`;\n}\nfunction granolaProcessingIssuesPath(options = {}) {\n return appendSearchParams(granolaTransportPaths.processingIssues, {\n limit: options.limit,\n meetingId: options.meetingId,\n severity: options.severity\n });\n}\nfunction granolaProcessingIssueRecoverPath(id) {\n return `${granolaTransportPaths.processingIssues}/${encodeURIComponent(id)}/recover`;\n}\nfunction granolaExportJobRerunPath(id) {\n return `${granolaTransportPaths.exportJobs}/${encodeURIComponent(id)}/rerun`;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/checkPrivateRedeclaration.js\nfunction _checkPrivateRedeclaration(e, t) {\n if (t.has(e)) throw new TypeError(\"Cannot initialize the same private elements twice on an object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldInitSpec.js\nfunction _classPrivateFieldInitSpec(e, t, a) {\n _checkPrivateRedeclaration(e, t), t.set(e, a);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/typeof.js\nfunction _typeof(o) {\n \"@babel/helpers - typeof\";\n return _typeof = \"function\" == typeof Symbol && \"symbol\" == typeof Symbol.iterator ? function(o) {\n return typeof o;\n } : function(o) {\n return o && \"function\" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? \"symbol\" : typeof o;\n }, _typeof(o);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js\nfunction toPrimitive(t, r) {\n if (\"object\" != _typeof(t) || !t) return t;\n var e = t[Symbol.toPrimitive];\n if (void 0 !== e) {\n var i = e.call(t, r || \"default\");\n if (\"object\" != _typeof(i)) return i;\n throw new TypeError(\"@@toPrimitive must return a primitive value.\");\n }\n return (\"string\" === r ? String : Number)(t);\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js\nfunction toPropertyKey(t) {\n var i = toPrimitive(t, \"string\");\n return \"symbol\" == _typeof(i) ? i : i + \"\";\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/defineProperty.js\nfunction _defineProperty(e, r, t) {\n return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {\n value: t,\n enumerable: !0,\n configurable: !0,\n writable: !0\n }) : e[r] = t, e;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/assertClassBrand.js\nfunction _assertClassBrand(e, t, n) {\n if (\"function\" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;\n throw new TypeError(\"Private element is not present on this object\");\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldSet2.js\nfunction _classPrivateFieldSet2(s, a, r) {\n return s.set(_assertClassBrand(s, a), r), r;\n}\n//#endregion\n//#region \\0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldGet2.js\nfunction _classPrivateFieldGet2(s, a) {\n return s.get(_assertClassBrand(s, a));\n}\n//#endregion\n//#region src/server/client.ts\nfunction resolveFetchImpl(fetchImpl) {\n if (fetchImpl) return ((input, init) => fetchImpl(input, init));\n return ((input, init) => globalThis.fetch(input, init));\n}\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, resolveFetchImpl(options.fetchImpl));\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 = resolveFetchImpl(options.fetchImpl);\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 listAgentHarnesses() {\n return await this.requestJson(granolaTransportPaths.automationHarnesses);\n }\n async saveAgentHarnesses(harnesses) {\n return await this.requestJson(granolaTransportPaths.automationHarnesses, {\n body: JSON.stringify({ harnesses }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async explainAgentHarnesses(meetingId) {\n return await this.requestJson(granolaAutomationHarnessExplainPath(meetingId));\n }\n async listAutomationArtefacts(options = {}) {\n return await this.requestJson(granolaAutomationArtefactsPath(options));\n }\n async evaluateAutomationCases(cases, options) {\n return await this.requestJson(granolaTransportPaths.automationEvaluate, {\n body: JSON.stringify({\n cases,\n options\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async listProcessingIssues(options = {}) {\n return await this.requestJson(granolaProcessingIssuesPath(options));\n }\n async getAutomationArtefact(id) {\n return await this.requestJson(granolaAutomationArtefactPath(id));\n }\n async listAutomationRules() {\n return await this.requestJson(granolaTransportPaths.automationRules);\n }\n async saveAutomationRules(rules) {\n return await this.requestJson(granolaTransportPaths.automationRules, {\n body: JSON.stringify({ rules }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async listAutomationMatches(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.automationMatches}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.automationMatches;\n return await this.requestJson(path);\n }\n async listAutomationRuns(options = {}) {\n return await this.requestJson(granolaAutomationRunsPath(options));\n }\n async resolveAutomationRun(id, decision, options = {}) {\n return await this.requestJson(granolaAutomationRunDecisionPath(id, decision), {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async resolveAutomationArtefact(id, decision, options = {}) {\n return await this.requestJson(granolaAutomationArtefactDecisionPath(id, decision), {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async recoverProcessingIssue(id) {\n return await this.requestJson(granolaProcessingIssueRecoverPath(id), { method: \"POST\" });\n }\n async updateAutomationArtefact(id, patch) {\n return await this.requestJson(granolaAutomationArtefactUpdatePath(id), {\n body: JSON.stringify(patch),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async rerunAutomationArtefact(id) {\n return await this.requestJson(granolaAutomationArtefactRerunPath(id), { method: \"POST\" });\n }\n async inspectSync() {\n return cloneValue(_classPrivateFieldGet2(_state, this).sync);\n }\n async listSyncEvents(options = {}) {\n const path = options.limit ? `${granolaTransportPaths.syncEvents}?limit=${encodeURIComponent(String(options.limit))}` : granolaTransportPaths.syncEvents;\n return await this.requestJson(path);\n }\n async loginAuth(options = {}) {\n return await this.requestJson(granolaTransportPaths.authLogin, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async logoutAuth() {\n return await this.requestJson(granolaTransportPaths.authLogout, { method: \"POST\" });\n }\n async refreshAuth() {\n return await this.requestJson(granolaTransportPaths.authRefresh, { method: \"POST\" });\n }\n async switchAuthMode(mode) {\n return await this.requestJson(granolaTransportPaths.authMode, {\n body: JSON.stringify({ mode }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async sync(options = {}) {\n return await this.requestJson(granolaTransportPaths.syncRun, {\n body: JSON.stringify(options),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async listFolders(options = {}) {\n return await this.requestJson(granolaFoldersPath(options));\n }\n async getFolder(id) {\n return await this.requestJson(granolaFolderPath(id));\n }\n async findFolder(query) {\n return await this.requestJson(granolaFolderResolvePath(query));\n }\n async listMeetings(options = {}) {\n return await this.requestJson(granolaMeetingsPath(options));\n }\n async getMeeting(id, options = {}) {\n return await this.requestJson(`${granolaMeetingPath(id)}${options.requireCache ? \"?includeTranscript=true\" : \"\"}`);\n }\n async findMeeting(query, options = {}) {\n return await this.requestJson(granolaMeetingResolvePath(query, { includeTranscript: options.requireCache }));\n }\n async listExportJobs(options = {}) {\n return await this.requestJson(granolaExportJobsPath(options));\n }\n async exportNotes(format = \"markdown\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportNotes, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async exportTranscripts(format = \"text\", options = {}) {\n return await this.requestJson(granolaTransportPaths.exportTranscripts, {\n body: JSON.stringify({\n folderId: options.folderId,\n format\n }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n }\n async rerunExportJob(id) {\n return await this.requestJson(granolaExportJobRerunPath(id), { method: \"POST\" });\n }\n async request(path, init = {}) {\n const response = await _classPrivateFieldGet2(_fetchImpl, this).call(this, new URL(path, this.url), {\n ...init,\n headers: mergeHeaders({\n ..._classPrivateFieldGet2(_password, this) ? { \"x-granola-password\": _classPrivateFieldGet2(_password, this) } : {},\n accept: \"application/json\"\n }, init.headers)\n });\n if (!response.ok) throw await responseError(response);\n return response;\n }\n async requestJson(path, init = {}) {\n return cloneValue(await (await this.request(path, init)).json());\n }\n emit(event) {\n _classPrivateFieldSet2(_state, this, cloneValue(event.state));\n const nextEvent = cloneValue(event);\n for (const listener of _classPrivateFieldGet2(_listeners, this)) listener(nextEvent);\n }\n startEvents() {\n if (_classPrivateFieldGet2(_eventLoop, this)) return;\n _classPrivateFieldSet2(_eventLoop, this, this.runEventsLoop());\n }\n async runEventsLoop() {\n while (!_classPrivateFieldGet2(_closed, this)) {\n const controller = new AbortController();\n _classPrivateFieldSet2(_streamAbortController, this, controller);\n try {\n const response = await this.request(granolaTransportPaths.events, {\n headers: { accept: \"text/event-stream\" },\n signal: controller.signal\n });\n await this.consumeEventStream(response);\n } catch {\n if (_classPrivateFieldGet2(_closed, this) || controller.signal.aborted) break;\n await new Promise((resolve) => {\n setTimeout(resolve, _classPrivateFieldGet2(_reconnectDelayMs, this));\n });\n }\n }\n }\n async consumeEventStream(response) {\n const reader = response.body?.getReader();\n if (!reader) throw new Error(\"server did not provide an event stream\");\n const decoder = new TextDecoder();\n let buffer = \"\";\n while (!_classPrivateFieldGet2(_closed, this)) {\n const { done, value } = await reader.read();\n if (done) return;\n buffer += decoder.decode(value, { stream: true });\n buffer = buffer.replaceAll(\"\\r\\n\", \"\\n\");\n while (true) {\n const boundary = buffer.indexOf(\"\\n\\n\");\n if (boundary < 0) break;\n const chunk = buffer.slice(0, boundary);\n buffer = buffer.slice(boundary + 2);\n const event = parseSseEvent(chunk);\n if (event) this.emit(event);\n }\n }\n }\n};\nasync function createGranolaServerClient(serverUrl, options = {}) {\n return await GranolaServerClient.connect(serverUrl, options);\n}\n//#endregion\n//#region src/web/client-state.ts\nvar granolaWebWorkspaceStorageKey = \"granola-toolkit.web-workspace\";\nvar maxRecentMeetings = 6;\nvar maxSavedFilters = 6;\nfunction normaliseFilterValue(value) {\n const trimmed = value?.trim();\n return trimmed ? trimmed : void 0;\n}\nfunction normaliseFilters(filters) {\n const selectedFolderId = normaliseFilterValue(filters.selectedFolderId);\n return {\n search: normaliseFilterValue(filters.search),\n selectedFolderId,\n sort: normaliseFilterValue(filters.sort) ?? \"updated-desc\",\n updatedFrom: normaliseFilterValue(filters.updatedFrom),\n updatedTo: normaliseFilterValue(filters.updatedTo)\n };\n}\nfunction filtersKey(filters) {\n return JSON.stringify(normaliseFilters(filters));\n}\nfunction defaultWorkspacePreferences() {\n return {\n recentMeetings: [],\n savedFilters: []\n };\n}\nfunction parseWorkspacePreferences(raw) {\n if (!raw) return defaultWorkspacePreferences();\n try {\n const parsed = JSON.parse(raw);\n return {\n recentMeetings: Array.isArray(parsed?.recentMeetings) ? parsed.recentMeetings.map((entry) => ({\n folderId: normaliseFilterValue(entry?.folderId),\n id: normaliseFilterValue(entry?.id) || \"\",\n title: normaliseFilterValue(entry?.title) || \"\",\n updatedAt: normaliseFilterValue(entry?.updatedAt) || \"\"\n })).filter((entry) => entry.id && entry.title).slice(0, maxRecentMeetings) : [],\n savedFilters: Array.isArray(parsed?.savedFilters) ? parsed.savedFilters.map((preset) => ({\n filters: normaliseFilters(preset?.filters ?? {}),\n id: normaliseFilterValue(preset?.id) || \"\",\n label: normaliseFilterValue(preset?.label) || \"\"\n })).filter((preset) => preset.id && preset.label).slice(0, maxSavedFilters) : []\n };\n } catch {\n return defaultWorkspacePreferences();\n }\n}\nfunction serialiseWorkspacePreferences(preferences) {\n return JSON.stringify({\n recentMeetings: preferences.recentMeetings.slice(0, maxRecentMeetings),\n savedFilters: preferences.savedFilters.slice(0, maxSavedFilters)\n });\n}\nfunction hasActiveFilters(filters) {\n const normalised = normaliseFilters(filters);\n return Boolean(normalised.search || normalised.selectedFolderId || normalised.updatedFrom || normalised.updatedTo || normalised.sort !== \"updated-desc\");\n}\nfunction filterLabel(filters) {\n const summary = currentFilterSummary(filters);\n if (!summary) return \"Current workspace\";\n return summary;\n}\nfunction rememberRecentMeeting(preferences, meeting) {\n const nextEntry = {\n folderId: meeting.folders?.[0]?.id,\n id: meeting.id,\n title: meeting.title?.trim() || meeting.id,\n updatedAt: meeting.updatedAt\n };\n return {\n ...preferences,\n recentMeetings: [nextEntry, ...preferences.recentMeetings.filter((entry) => entry.id !== nextEntry.id)].slice(0, maxRecentMeetings)\n };\n}\nfunction saveWorkspaceFilter(preferences, filters, options = {}) {\n const nextFilters = normaliseFilters(filters);\n if (!hasActiveFilters(nextFilters)) return preferences;\n const key = filtersKey(nextFilters);\n const nextPreset = {\n filters: nextFilters,\n id: preferences.savedFilters.find((preset) => filtersKey(preset.filters) === key)?.id ?? options.idFactory?.() ?? `filter-${preferences.savedFilters.length + 1}`,\n label: filterLabel(filters)\n };\n return {\n ...preferences,\n savedFilters: [nextPreset, ...preferences.savedFilters.filter((preset) => preset.id !== nextPreset.id)].slice(0, maxSavedFilters)\n };\n}\nfunction removeWorkspaceFilter(preferences, id) {\n return {\n ...preferences,\n savedFilters: preferences.savedFilters.filter((preset) => preset.id !== id)\n };\n}\nfunction applyWorkspaceFilter(preset) {\n return {\n search: preset.filters.search ?? \"\",\n selectedFolderId: preset.filters.selectedFolderId ?? null,\n sort: preset.filters.sort ?? \"updated-desc\",\n updatedFrom: preset.filters.updatedFrom ?? \"\",\n updatedTo: preset.filters.updatedTo ?? \"\"\n };\n}\nfunction parseWorkspaceTab(value) {\n switch (value) {\n case \"metadata\":\n case \"raw\":\n case \"transcript\": return value;\n default: return \"notes\";\n }\n}\nfunction startupSelectionFromSearch(search) {\n const params = new URLSearchParams(search);\n return {\n folderId: params.get(\"folder\")?.trim() || \"\",\n meetingId: params.get(\"meeting\")?.trim() || \"\",\n workspaceTab: parseWorkspaceTab(params.get(\"tab\"))\n };\n}\nfunction buildBrowserUrlPath(currentHref, selection) {\n const url = new URL(currentHref);\n if (selection.selectedFolderId) url.searchParams.set(\"folder\", selection.selectedFolderId);\n else url.searchParams.delete(\"folder\");\n if (selection.selectedMeetingId) url.searchParams.set(\"meeting\", selection.selectedMeetingId);\n else url.searchParams.delete(\"meeting\");\n if (parseWorkspaceTab(selection.workspaceTab) !== \"notes\") url.searchParams.set(\"tab\", parseWorkspaceTab(selection.workspaceTab));\n else url.searchParams.delete(\"tab\");\n return `${url.pathname}${url.search}${url.hash}`;\n}\nfunction exportScopeLabel(scope) {\n return scope && scope.mode === \"folder\" ? `Folder: ${scope.folderName || scope.folderId}` : \"Scope: All meetings\";\n}\nfunction currentFilterSummary(filters) {\n const parts = [];\n if (filters.selectedFolderId) {\n const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);\n parts.push(`folder \"${folder ? folder.name : filters.selectedFolderId}\"`);\n }\n if (filters.search) parts.push(`search \"${filters.search}\"`);\n if (filters.updatedFrom) parts.push(`from ${filters.updatedFrom}`);\n if (filters.updatedTo) parts.push(`to ${filters.updatedTo}`);\n if (filters.sort && filters.sort !== \"updated-desc\") parts.push(filters.sort === \"updated-asc\" ? \"oldest first\" : filters.sort === \"title-asc\" ? \"title A-Z\" : \"title Z-A\");\n return parts.join(\", \");\n}\nfunction selectMeetingId(meetings, selectedMeetingId) {\n if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;\n return meetings[0]?.id ?? null;\n}\nfunction nextWorkspaceTab(currentTab, key) {\n const current = parseWorkspaceTab(currentTab);\n switch (key) {\n case \"1\": return \"notes\";\n case \"2\": return \"transcript\";\n case \"3\": return \"metadata\";\n case \"4\": return \"raw\";\n case \"]\":\n switch (current) {\n case \"notes\": return \"transcript\";\n case \"transcript\": return \"metadata\";\n case \"metadata\": return \"raw\";\n case \"raw\": return \"notes\";\n }\n break;\n case \"[\":\n switch (current) {\n case \"notes\": return \"raw\";\n case \"transcript\": return \"notes\";\n case \"metadata\": return \"transcript\";\n case \"raw\": return \"metadata\";\n }\n break;\n default: return;\n }\n}\nfunction describeSyncStatus(sync) {\n if (sync.running) return \"Sync running\";\n if (sync.lastError) return \"Sync needs attention\";\n if (sync.lastCompletedAt) {\n const suffix = sync.summary?.changedCount ? ` · ${sync.summary.changedCount} changes` : \"\";\n return `Synced ${sync.lastCompletedAt.slice(11, 19)}${suffix}`;\n }\n return \"Sync idle\";\n}\nfunction describeAuthStatus(auth) {\n if (!auth) return \"Waiting for auth\";\n if (auth.lastError) return \"Auth needs attention\";\n switch (auth.mode) {\n case \"api-key\": return \"API key active\";\n case \"stored-session\": return \"Stored session active\";\n default: return \"supabase.json active\";\n }\n}\n//#endregion\n//#region src/web-app/components.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$$3 = /* @__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$3 = /* @__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$3 = /* @__PURE__ */ template(`<div class=\"folder-empty folder-empty--error\">`), _tmpl$4$2 = /* @__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$2 = /* @__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$2 = /* @__PURE__ */ template(`<div class=folder-empty>No folders found.`), _tmpl$7$2 = /* @__PURE__ */ template(`<button class=folder-row type=button><span class=folder-row__title></span><span class=folder-row__meta>`), _tmpl$8$2 = /* @__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$1 = /* @__PURE__ */ template(`<div class=folder-empty>No saved filters yet.`), _tmpl$0$1 = /* @__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$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$1 = /* @__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(`<section class=jobs-panel><div class=jobs-panel__head><h3>Processing Health</h3><p>Catch stale syncs, missing transcripts, and failed or outdated note pipelines.</p></div><div class=jobs-list>`), _tmpl$38 = /* @__PURE__ */ template(`<div class=job-empty>No processing issues detected.`), _tmpl$39 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Open Meeting`), _tmpl$40 = /* @__PURE__ */ template(`<button class=\"button button--secondary\"type=button>Recover`), _tmpl$41 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=jobs-panel__head><h3>Review Queue</h3><p>Generated note and enrichment candidates waiting for review or follow-up.</p></div><div class=jobs-list>`), _tmpl$42 = /* @__PURE__ */ template(`<div class=job-empty>No automation artefacts yet.`), _tmpl$43 = /* @__PURE__ */ template(`<button class=\"job-card job-card--button\"type=button><div class=job-card__head><div><div class=job-card__title></div><div class=job-card__meta></div></div><div class=job-card__status></div></div><div class=job-card__meta></div><div class=job-card__meta>`), _tmpl$44 = /* @__PURE__ */ template(`<section class=review-panel><div class=jobs-panel__head><h3>Artefact Review</h3><p>Review generated candidate notes, compare them to the current meeting, then approve, reject, edit, or rerun.`), _tmpl$45 = /* @__PURE__ */ template(`<div class=job-empty>`), _tmpl$46 = /* @__PURE__ */ template(`<div class=detail-section><h3>Action Items</h3><ul class=detail-list>`), _tmpl$47 = /* @__PURE__ */ template(`<div class=detail-section><h3>Participant Summaries</h3><ul class=detail-list>`), _tmpl$48 = /* @__PURE__ */ template(`<div class=review-grid><section class=detail-section><h2>Current Meeting Notes</h2><pre class=detail-pre></pre></section><section class=detail-section><h2>Candidate</h2><label class=field-row><span class=field-label>Title</span><input class=\"field-input field-input--plain\"></label><label class=field-row><span class=field-label>Summary</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><label class=field-row><span class=field-label>Markdown</span><textarea class=review-textarea></textarea></label><label class=field-row><span class=field-label>Review Note</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><div class=job-card__actions><button class=\"button button--secondary\"type=button>Save edits</button><button class=\"button button--secondary\"type=button>Approve</button><button class=\"button button--secondary\"type=button>Reject</button><button class=\"button button--secondary\"type=button>Rerun`), _tmpl$49 = /* @__PURE__ */ template(`<div class=review-body><div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip></div></div><section class=\"detail-section review-history\"><h2>History</h2><div class=jobs-list>`), _tmpl$50 = /* @__PURE__ */ template(`<div class=empty>`), _tmpl$51 = /* @__PURE__ */ template(`<span>`), _tmpl$52 = /* @__PURE__ */ template(`<li><strong>`), _tmpl$53 = /* @__PURE__ */ template(`<li><strong></strong><div>`), _tmpl$54 = /* @__PURE__ */ template(`<div class=job-card><div class=job-card__head><div class=job-card__title></div><div class=job-card__meta>`), _tmpl$55 = /* @__PURE__ */ template(`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle`), _tmpl$56 = /* @__PURE__ */ template(`<button class=workspace-tab type=button>`), _tmpl$57 = /* @__PURE__ */ template(`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>`), _tmpl$58 = /* @__PURE__ */ template(`<div class=detail-body><div class=workspace-grid><aside class=\"detail-section workspace-sidebar\"><h2>Meeting Metadata</h2><pre class=detail-pre></pre></aside><section class=\"detail-section workspace-main\"><h2></h2><pre class=detail-pre>`);\nfunction authModeLabel(mode) {\n switch (mode) {\n case \"api-key\": return \"API key\";\n case \"stored-session\": return \"Stored session\";\n default: return \"supabase.json\";\n }\n}\nfunction metadataLines(record) {\n return [\n `Title: ${record.meeting.title || record.meeting.id}`,\n `Created: ${record.meeting.createdAt}`,\n `Updated: ${record.meeting.updatedAt}`,\n `Folders: ${record.meeting.folders.length ? record.meeting.folders.map((folder) => folder.name).join(\", \") : \"none\"}`,\n `Tags: ${record.meeting.tags.length ? record.meeting.tags.join(\", \") : \"none\"}`,\n `Transcript loaded: ${record.meeting.transcriptLoaded ? \"yes\" : \"no\"}`,\n `Owner candidates: ${record.roleHelpers.ownerCandidates.length ? record.roleHelpers.ownerCandidates.map((candidate) => candidate.label).join(\", \") : \"none\"}`,\n `Speakers: ${record.roleHelpers.speakers.length ? record.roleHelpers.speakers.map((speaker) => `${speaker.label} (${speaker.segmentCount})`).join(\", \") : \"none\"}`\n ].join(\"\\n\");\n}\nfunction workspaceBody(bundle, record, tab) {\n switch (tab) {\n case \"transcript\": return {\n body: record.transcriptText || \"(Transcript unavailable)\",\n title: \"Transcript\"\n };\n case \"metadata\": return {\n body: metadataLines(record),\n title: \"Metadata\"\n };\n case \"raw\": return {\n body: JSON.stringify(bundle || record, null, 2),\n title: \"Raw Bundle\"\n };\n default: return {\n body: record.noteMarkdown || \"(No notes available)\",\n title: \"Notes\"\n };\n }\n}\nfunction scopeLabel(scope) {\n return exportScopeLabel(scope);\n}\nfunction ToolbarFilters(props) {\n return [(() => {\n var _el$ = _tmpl$$3(), _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$3(), _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$2(), _el$20 = _el$18.firstChild.nextSibling;\n insert(_el$20, createComponent(Show, {\n get fallback() {\n return [\n (() => {\n var _el$22 = _tmpl$5$2();\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$2(), _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$2();\n }\n })\n ];\n },\n get when() {\n return !props.error;\n },\n get children() {\n var _el$21 = _tmpl$3$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$2(), _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$1();\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$1(), _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$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$1();\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$2(), _el$43 = _el$42.firstChild, _el$44 = _el$43.nextSibling;\n _el$42.$$click = () => {\n props.onOpen(meeting);\n };\n insert(_el$43, () => meeting.title);\n insert(_el$44, () => meeting.updatedAt.slice(0, 10));\n return _el$42;\n })()\n });\n }\n }));\n return _el$38;\n })();\n}\nfunction MeetingList(props) {\n const summary = () => currentFilterSummary({\n folders: props.folders,\n search: props.search,\n selectedFolderId: props.selectedFolderId,\n updatedFrom: props.updatedFrom,\n updatedTo: props.updatedTo\n });\n return (() => {\n var _el$45 = _tmpl$12();\n insert(_el$45, createComponent(Show, {\n get fallback() {\n return createComponent(Show, {\n get fallback() {\n return (() => {\n var _el$47 = _tmpl$13();\n insert(_el$47, (() => {\n var _c$ = memo(() => !!summary());\n return () => _c$() ? `No meetings match ${summary()}.` : props.emptyHint || \"No meetings yet. Try Sync now.\";\n })());\n return _el$47;\n })();\n },\n get when() {\n return props.meetings.length > 0;\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.meetings;\n },\n children: (meeting) => (() => {\n var _el$48 = _tmpl$14(), _el$49 = _el$48.firstChild, _el$50 = _el$49.nextSibling, _el$51 = _el$50.nextSibling;\n _el$48.$$click = () => {\n props.onSelect(meeting.id);\n };\n insert(_el$49, () => meeting.title || meeting.id);\n insert(_el$50, (() => {\n var _c$2 = memo(() => !!meeting.tags.length);\n return () => _c$2() ? meeting.tags.map((tag) => `#${tag}`).join(\" \") : \"untagged\";\n })());\n insert(_el$51, (() => {\n var _c$3 = memo(() => !!meeting.updatedAt);\n return () => _c$3() ? meeting.updatedAt.slice(0, 10) : \"unknown\";\n })());\n createRenderEffect(() => setAttribute(_el$48, \"data-selected\", meeting.id === props.selectedMeetingId ? \"true\" : void 0));\n return _el$48;\n })()\n });\n }\n });\n },\n get when() {\n return props.error;\n },\n get children() {\n var _el$46 = _tmpl$11();\n insert(_el$46, () => props.error);\n return _el$46;\n }\n }));\n return _el$45;\n })();\n}\nfunction AppStatePanel(props) {\n const syncStatus = () => describeSyncStatus(props.appState?.sync ?? {});\n const authStatus = () => describeAuthStatus(props.appState?.auth);\n return (() => {\n var _el$52 = _tmpl$16(), _el$53 = _el$52.firstChild;\n _el$53.firstChild;\n var _el$56 = _el$53.nextSibling;\n insert(_el$53, createComponent(Show, {\n get fallback() {\n return _tmpl$17();\n },\n get when() {\n return props.appState;\n },\n children: (appState) => (() => {\n var _el$58 = _tmpl$18(), _el$59 = _el$58.firstChild, _el$61 = _el$59.firstChild.nextSibling, _el$62 = _el$59.nextSibling, _el$64 = _el$62.firstChild.nextSibling, _el$65 = _el$62.nextSibling, _el$67 = _el$65.firstChild.nextSibling, _el$68 = _el$65.nextSibling, _el$70 = _el$68.firstChild.nextSibling, _el$71 = _el$68.nextSibling, _el$73 = _el$71.firstChild.nextSibling, _el$74 = _el$71.nextSibling, _el$76 = _el$74.firstChild.nextSibling, _el$77 = _el$74.nextSibling, _el$79 = _el$77.firstChild.nextSibling, _el$80 = _el$77.nextSibling, _el$82 = _el$80.firstChild.nextSibling, _el$85 = _el$80.nextSibling.firstChild.nextSibling;\n insert(_el$61, () => appState().ui.surface);\n insert(_el$64, () => appState().ui.view);\n insert(_el$67, authStatus);\n insert(_el$70, syncStatus);\n insert(_el$73, (() => {\n var _c$4 = memo(() => !!appState().documents.loaded);\n return () => _c$4() ? String(appState().documents.count) : \"not loaded\";\n })());\n insert(_el$76, (() => {\n var _c$5 = memo(() => !!appState().folders.loaded);\n return () => _c$5() ? String(appState().folders.count) : \"not loaded\";\n })());\n insert(_el$79, (() => {\n var _c$6 = memo(() => !!appState().cache.loaded);\n return () => _c$6() ? `${appState().cache.transcriptCount} transcript sets` : appState().cache.configured ? \"configured\" : \"not configured\";\n })());\n insert(_el$82, (() => {\n var _c$7 = memo(() => !!appState().index.loaded);\n return () => _c$7() ? `${appState().index.meetingCount} meetings` : appState().index.available ? \"available\" : \"not built\";\n })());\n insert(_el$85, () => `${appState().automation.runCount} runs / ${appState().automation.pendingRunCount} pending runs / ${appState().automation.pendingArtefactCount} pending artefacts`);\n return _el$58;\n })()\n }), null);\n insert(_el$53, createComponent(Show, {\n get when() {\n return props.appState?.auth.lastError;\n },\n get children() {\n var _el$55 = _tmpl$15();\n insert(_el$55, () => props.appState?.auth.lastError);\n return _el$55;\n }\n }), null);\n insert(_el$56, () => props.statusLabel);\n createRenderEffect(() => setAttribute(_el$56, \"data-tone\", props.statusTone));\n return _el$52;\n })();\n}\nfunction SecurityPanel(props) {\n return createComponent(Show, {\n get when() {\n return props.visible;\n },\n get children() {\n var _el$86 = _tmpl$19(), _el$89 = _el$86.firstChild.nextSibling.firstChild, _el$91 = _el$89.nextSibling.firstChild, _el$92 = _el$91.nextSibling;\n _el$89.$$keydown = (event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n props.onUnlock();\n }\n };\n _el$89.$$input = (event) => {\n props.onPasswordChange(event.currentTarget.value);\n };\n addEventListener(_el$91, \"click\", props.onUnlock, true);\n addEventListener(_el$92, \"click\", props.onLock, true);\n createRenderEffect(() => _el$89.value = props.password);\n return _el$86;\n }\n });\n}\nfunction AuthPanel(props) {\n return (() => {\n var _el$93 = _tmpl$20(), _el$95 = _el$93.firstChild.nextSibling;\n insert(_el$95, createComponent(Show, {\n get fallback() {\n return _tmpl$21();\n },\n get when() {\n return props.auth;\n },\n children: (auth) => (() => {\n var _el$97 = _tmpl$26(), _el$98 = _el$97.firstChild, _el$99 = _el$98.firstChild, _el$101 = _el$99.firstChild.nextSibling, _el$102 = _el$99.nextSibling, _el$104 = _el$102.firstChild.nextSibling, _el$105 = _el$102.nextSibling, _el$107 = _el$105.firstChild.nextSibling, _el$108 = _el$105.nextSibling, _el$110 = _el$108.firstChild.nextSibling, _el$113 = _el$108.nextSibling.firstChild.nextSibling, _el$121 = _el$98.nextSibling, _el$123 = _el$121.nextSibling.firstChild, _el$124 = _el$123.nextSibling, _el$125 = _el$124.nextSibling, _el$126 = _el$125.nextSibling, _el$127 = _el$126.nextSibling, _el$128 = _el$127.nextSibling, _el$129 = _el$128.nextSibling, _el$130 = _el$129.nextSibling;\n insert(_el$101, () => authModeLabel(auth().mode));\n insert(_el$104, () => auth().apiKeyAvailable ? \"available\" : \"missing\");\n insert(_el$107, () => auth().storedSessionAvailable ? \"available\" : \"missing\");\n insert(_el$110, () => auth().supabaseAvailable ? \"available\" : \"missing\");\n insert(_el$113, () => auth().refreshAvailable ? \"available\" : \"missing\");\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().clientId;\n },\n get children() {\n var _el$114 = _tmpl$22();\n _el$114.firstChild;\n insert(_el$114, () => auth().clientId, null);\n return _el$114;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().signInMethod;\n },\n get children() {\n var _el$116 = _tmpl$23();\n _el$116.firstChild;\n insert(_el$116, () => auth().signInMethod, null);\n return _el$116;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().supabasePath;\n },\n get children() {\n var _el$118 = _tmpl$24();\n _el$118.firstChild;\n insert(_el$118, () => auth().supabasePath, null);\n return _el$118;\n }\n }), _el$121);\n insert(_el$97, createComponent(Show, {\n get when() {\n return auth().lastError;\n },\n get children() {\n var _el$120 = _tmpl$25();\n insert(_el$120, () => auth().lastError);\n return _el$120;\n }\n }), _el$121);\n _el$123.$$input = (event) => {\n props.onApiKeyDraftChange(event.currentTarget.value);\n };\n addEventListener(_el$124, \"click\", props.onSaveApiKey, true);\n addEventListener(_el$125, \"click\", props.onImportDesktopSession, true);\n addEventListener(_el$126, \"click\", props.onRefresh, true);\n _el$127.$$click = () => {\n props.onSwitchMode(\"api-key\");\n };\n _el$128.$$click = () => {\n props.onSwitchMode(\"stored-session\");\n };\n _el$129.$$click = () => {\n props.onSwitchMode(\"supabase-file\");\n };\n addEventListener(_el$130, \"click\", props.onLogout, true);\n createRenderEffect((_p$) => {\n var _v$ = !auth().supabaseAvailable, _v$2 = !auth().storedSessionAvailable || !auth().refreshAvailable, _v$3 = !auth().apiKeyAvailable || auth().mode === \"api-key\", _v$4 = !auth().storedSessionAvailable || auth().mode === \"stored-session\", _v$5 = !auth().supabaseAvailable || auth().mode === \"supabase-file\", _v$6 = !auth().apiKeyAvailable && !auth().storedSessionAvailable;\n _v$ !== _p$.e && (_el$125.disabled = _p$.e = _v$);\n _v$2 !== _p$.t && (_el$126.disabled = _p$.t = _v$2);\n _v$3 !== _p$.a && (_el$127.disabled = _p$.a = _v$3);\n _v$4 !== _p$.o && (_el$128.disabled = _p$.o = _v$4);\n _v$5 !== _p$.i && (_el$129.disabled = _p$.i = _v$5);\n _v$6 !== _p$.n && (_el$130.disabled = _p$.n = _v$6);\n return _p$;\n }, {\n e: void 0,\n t: void 0,\n a: void 0,\n o: void 0,\n i: void 0,\n n: void 0\n });\n createRenderEffect(() => _el$123.value = props.apiKeyDraft);\n return _el$97;\n })()\n }));\n return _el$93;\n })();\n}\nfunction ExportJobsPanel(props) {\n return (() => {\n var _el$131 = _tmpl$27(), _el$133 = _el$131.firstChild.nextSibling;\n insert(_el$133, createComponent(Show, {\n get when() {\n return props.jobs.length > 0;\n },\n get fallback() {\n return _tmpl$28();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.jobs.slice(0, 6);\n },\n children: (job) => (() => {\n var _el$135 = _tmpl$31(), _el$136 = _el$135.firstChild, _el$137 = _el$136.firstChild, _el$138 = _el$137.firstChild, _el$139 = _el$138.firstChild, _el$140 = _el$138.nextSibling, _el$141 = _el$137.nextSibling, _el$142 = _el$136.nextSibling, _el$143 = _el$142.nextSibling;\n _el$143.firstChild;\n var _el$145 = _el$143.nextSibling;\n _el$145.firstChild;\n var _el$148 = _el$145.nextSibling;\n insert(_el$138, () => job.kind, _el$139);\n insert(_el$140, () => job.id);\n insert(_el$141, () => job.status);\n insert(_el$142, () => `Format: ${job.format} • ${scopeLabel(job.scope)} • ${job.itemCount > 0 ? `${job.completedCount}/${job.itemCount} items` : \"0 items\"} • Written: ${job.written}`);\n insert(_el$143, () => job.startedAt.slice(0, 19), null);\n insert(_el$145, () => job.outputDir, null);\n insert(_el$135, createComponent(Show, {\n get when() {\n return job.error;\n },\n get children() {\n var _el$147 = _tmpl$29();\n insert(_el$147, () => job.error);\n return _el$147;\n }\n }), _el$148);\n insert(_el$148, createComponent(Show, {\n get when() {\n return job.status !== \"running\";\n },\n get children() {\n var _el$149 = _tmpl$30();\n _el$149.$$click = () => {\n props.onRerun(job.id);\n };\n return _el$149;\n }\n }));\n createRenderEffect(() => setAttribute(_el$141, \"data-status\", job.status));\n return _el$135;\n })()\n });\n }\n }));\n return _el$131;\n })();\n}\nfunction AutomationRunsPanel(props) {\n return (() => {\n var _el$150 = _tmpl$32(), _el$152 = _el$150.firstChild.nextSibling;\n insert(_el$152, createComponent(Show, {\n get when() {\n return props.runs.length > 0;\n },\n get fallback() {\n return _tmpl$33();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.runs.slice(0, 6);\n },\n children: (run) => (() => {\n var _el$154 = _tmpl$36(), _el$155 = _el$154.firstChild, _el$156 = _el$155.firstChild, _el$157 = _el$156.firstChild, _el$158 = _el$157.nextSibling, _el$159 = _el$156.nextSibling, _el$160 = _el$155.nextSibling, _el$161 = _el$160.nextSibling, _el$165 = _el$161.nextSibling;\n insert(_el$157, () => run.actionName);\n insert(_el$158, () => `${run.ruleName} • ${run.id}`);\n insert(_el$159, () => run.status);\n insert(_el$160, () => `${run.title} • ${run.eventKind}`);\n insert(_el$161, () => `Started: ${run.startedAt.slice(0, 19)}`);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.prompt;\n },\n get children() {\n var _el$162 = _tmpl$29();\n insert(_el$162, () => run.prompt);\n return _el$162;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.result;\n },\n get children() {\n var _el$163 = _tmpl$29();\n insert(_el$163, () => run.result);\n return _el$163;\n }\n }), _el$165);\n insert(_el$154, createComponent(Show, {\n get when() {\n return run.error;\n },\n get children() {\n var _el$164 = _tmpl$29();\n insert(_el$164, () => run.error);\n return _el$164;\n }\n }), _el$165);\n insert(_el$165, createComponent(Show, {\n get when() {\n return run.status === \"pending\";\n },\n get children() {\n return [(() => {\n var _el$166 = _tmpl$34();\n _el$166.$$click = () => {\n props.onApprove(run.id);\n };\n return _el$166;\n })(), (() => {\n var _el$167 = _tmpl$35();\n _el$167.$$click = () => {\n props.onReject(run.id);\n };\n return _el$167;\n })()];\n }\n }));\n createRenderEffect(() => setAttribute(_el$159, \"data-status\", run.status));\n return _el$154;\n })()\n });\n }\n }));\n return _el$150;\n })();\n}\nfunction ProcessingIssuesPanel(props) {\n return (() => {\n var _el$168 = _tmpl$37(), _el$170 = _el$168.firstChild.nextSibling;\n insert(_el$170, createComponent(Show, {\n get when() {\n return props.issues.length > 0;\n },\n get fallback() {\n return _tmpl$38();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.issues.slice(0, 8);\n },\n children: (issue) => (() => {\n var _el$172 = _tmpl$36(), _el$173 = _el$172.firstChild, _el$174 = _el$173.firstChild, _el$175 = _el$174.firstChild, _el$176 = _el$175.nextSibling, _el$177 = _el$174.nextSibling, _el$178 = _el$173.nextSibling, _el$179 = _el$178.nextSibling, _el$180 = _el$179.nextSibling;\n insert(_el$175, () => issue.title);\n insert(_el$176, () => issue.id);\n insert(_el$177, () => issue.severity);\n insert(_el$178, () => issue.kind);\n insert(_el$179, () => issue.detail);\n insert(_el$180, createComponent(Show, {\n get when() {\n return issue.meetingId;\n },\n get children() {\n var _el$181 = _tmpl$39();\n _el$181.$$click = () => {\n props.onOpenMeeting(issue.meetingId);\n };\n return _el$181;\n }\n }), null);\n insert(_el$180, createComponent(Show, {\n get when() {\n return issue.recoverable;\n },\n get children() {\n var _el$182 = _tmpl$40();\n _el$182.$$click = () => {\n props.onRecover(issue.id);\n };\n return _el$182;\n }\n }), null);\n createRenderEffect(() => setAttribute(_el$177, \"data-status\", issue.severity));\n return _el$172;\n })()\n });\n }\n }));\n return _el$168;\n })();\n}\nfunction AutomationArtefactsPanel(props) {\n return (() => {\n var _el$183 = _tmpl$41(), _el$185 = _el$183.firstChild.nextSibling;\n insert(_el$185, createComponent(Show, {\n get when() {\n return props.artefacts.length > 0;\n },\n get fallback() {\n return _tmpl$42();\n },\n get children() {\n return createComponent(For, {\n get each() {\n return props.artefacts.slice(0, 10);\n },\n children: (artefact) => (() => {\n var _el$187 = _tmpl$43(), _el$188 = _el$187.firstChild, _el$189 = _el$188.firstChild, _el$190 = _el$189.firstChild, _el$191 = _el$190.nextSibling, _el$192 = _el$189.nextSibling, _el$193 = _el$188.nextSibling, _el$195 = _el$193.nextSibling;\n _el$187.$$click = () => {\n props.onSelect(artefact.id);\n };\n insert(_el$190, () => artefact.structured.title);\n insert(_el$191, () => `${artefact.kind} • ${artefact.ruleName}`);\n insert(_el$192, () => artefact.status);\n insert(_el$193, () => artefact.meetingId);\n insert(_el$187, createComponent(Show, {\n get when() {\n return artefact.structured.summary;\n },\n get children() {\n var _el$194 = _tmpl$29();\n insert(_el$194, () => artefact.structured.summary);\n return _el$194;\n }\n }), _el$195);\n insert(_el$195, () => `Updated: ${artefact.updatedAt.slice(0, 19)}`);\n createRenderEffect((_p$) => {\n var _v$7 = artefact.id === props.selectedArtefactId ? \"true\" : void 0, _v$8 = artefact.status;\n _v$7 !== _p$.e && setAttribute(_el$187, \"data-selected\", _p$.e = _v$7);\n _v$8 !== _p$.t && setAttribute(_el$192, \"data-status\", _p$.t = _v$8);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n return _el$187;\n })()\n });\n }\n }));\n return _el$183;\n })();\n}\nfunction ArtefactReviewPanel(props) {\n return (() => {\n var _el$196 = _tmpl$44();\n _el$196.firstChild;\n insert(_el$196, createComponent(Show, {\n get when() {\n return props.artefact;\n },\n get fallback() {\n return (() => {\n var _el$198 = _tmpl$45();\n insert(_el$198, () => props.error || \"Select an automation artefact to review it.\");\n return _el$198;\n })();\n },\n children: (artefact) => (() => {\n var _el$199 = _tmpl$49(), _el$200 = _el$199.firstChild, _el$201 = _el$200.firstChild, _el$202 = _el$201.nextSibling, _el$203 = _el$202.nextSibling, _el$204 = _el$203.nextSibling, _el$234 = _el$200.nextSibling, _el$236 = _el$234.firstChild.nextSibling;\n insert(_el$201, () => `Status: ${artefact().status}`);\n insert(_el$202, () => `Kind: ${artefact().kind}`);\n insert(_el$203, () => `Meeting: ${artefact().meetingId}`);\n insert(_el$204, () => `Provider: ${artefact().provider}/${artefact().model}`);\n insert(_el$199, createComponent(Show, {\n get when() {\n return !props.error;\n },\n get fallback() {\n return (() => {\n var _el$237 = _tmpl$50();\n insert(_el$237, () => props.error);\n return _el$237;\n })();\n },\n get children() {\n var _el$205 = _tmpl$48(), _el$206 = _el$205.firstChild, _el$208 = _el$206.firstChild.nextSibling, _el$209 = _el$206.nextSibling, _el$211 = _el$209.firstChild.nextSibling, _el$213 = _el$211.firstChild.nextSibling, _el$214 = _el$211.nextSibling, _el$216 = _el$214.firstChild.nextSibling, _el$217 = _el$214.nextSibling, _el$219 = _el$217.firstChild.nextSibling, _el$220 = _el$217.nextSibling, _el$222 = _el$220.firstChild.nextSibling, _el$224 = _el$220.nextSibling.firstChild, _el$225 = _el$224.nextSibling, _el$226 = _el$225.nextSibling, _el$227 = _el$226.nextSibling;\n insert(_el$208, () => props.bundle?.meeting.noteMarkdown || \"(No existing meeting notes)\");\n _el$213.$$input = (event) => {\n props.onDraftTitleChange(event.currentTarget.value);\n };\n _el$216.$$input = (event) => {\n props.onDraftSummaryChange(event.currentTarget.value);\n };\n insert(_el$216, () => props.draftSummary);\n _el$219.$$input = (event) => {\n props.onDraftMarkdownChange(event.currentTarget.value);\n };\n insert(_el$219, () => props.draftMarkdown);\n _el$222.$$input = (event) => {\n props.onReviewNoteChange(event.currentTarget.value);\n };\n insert(_el$222, () => props.reviewNote);\n addEventListener(_el$224, \"click\", props.onSave, true);\n addEventListener(_el$225, \"click\", props.onApprove, true);\n addEventListener(_el$226, \"click\", props.onReject, true);\n addEventListener(_el$227, \"click\", props.onRerun, true);\n insert(_el$209, createComponent(Show, {\n get when() {\n return artefact().structured.actionItems.length > 0;\n },\n get children() {\n var _el$228 = _tmpl$46(), _el$230 = _el$228.firstChild.nextSibling;\n insert(_el$230, createComponent(For, {\n get each() {\n return artefact().structured.actionItems;\n },\n children: (item) => (() => {\n var _el$238 = _tmpl$52(), _el$239 = _el$238.firstChild;\n insert(_el$239, () => item.title);\n insert(_el$238, createComponent(Show, {\n get when() {\n return item.owner;\n },\n get children() {\n var _el$240 = _tmpl$51();\n insert(_el$240, () => ` • ${item.owner}`);\n return _el$240;\n }\n }), null);\n insert(_el$238, createComponent(Show, {\n get when() {\n return item.dueDate;\n },\n get children() {\n var _el$241 = _tmpl$51();\n insert(_el$241, () => ` • due ${item.dueDate}`);\n return _el$241;\n }\n }), null);\n return _el$238;\n })()\n }));\n return _el$228;\n }\n }), null);\n insert(_el$209, createComponent(Show, {\n get when() {\n return (artefact().structured.participantSummaries?.length ?? 0) > 0;\n },\n get children() {\n var _el$231 = _tmpl$47(), _el$233 = _el$231.firstChild.nextSibling;\n insert(_el$233, createComponent(For, {\n get each() {\n return artefact().structured.participantSummaries;\n },\n children: (summary) => (() => {\n var _el$242 = _tmpl$53(), _el$243 = _el$242.firstChild, _el$245 = _el$243.nextSibling;\n insert(_el$243, () => summary.speaker);\n insert(_el$242, createComponent(Show, {\n get when() {\n return summary.role;\n },\n get children() {\n var _el$244 = _tmpl$51();\n insert(_el$244, () => ` • ${summary.role}`);\n return _el$244;\n }\n }), _el$245);\n insert(_el$245, () => summary.summary);\n return _el$242;\n })()\n }));\n return _el$231;\n }\n }), null);\n createRenderEffect((_p$) => {\n var _v$9 = artefact().status === \"superseded\", _v$0 = artefact().status === \"superseded\";\n _v$9 !== _p$.e && (_el$225.disabled = _p$.e = _v$9);\n _v$0 !== _p$.t && (_el$226.disabled = _p$.t = _v$0);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n createRenderEffect(() => _el$213.value = props.draftTitle);\n return _el$205;\n }\n }), _el$234);\n insert(_el$236, createComponent(For, {\n get each() {\n return artefact().history.slice().reverse();\n },\n children: (entry) => (() => {\n var _el$246 = _tmpl$54(), _el$248 = _el$246.firstChild.firstChild, _el$249 = _el$248.nextSibling;\n insert(_el$248, () => entry.action);\n insert(_el$249, () => entry.at.slice(0, 19));\n insert(_el$246, createComponent(Show, {\n get when() {\n return entry.note;\n },\n get children() {\n var _el$250 = _tmpl$29();\n insert(_el$250, () => entry.note);\n return _el$250;\n }\n }), null);\n return _el$246;\n })()\n }));\n return _el$199;\n })()\n }), null);\n return _el$196;\n })();\n}\nfunction Workspace(props) {\n const parsedTab = () => parseWorkspaceTab(props.tab);\n const details = () => {\n if (!props.selectedMeeting) return null;\n return workspaceBody(props.bundle, props.selectedMeeting, parsedTab());\n };\n return [(() => {\n var _el$251 = _tmpl$55(), _el$252 = _el$251.firstChild;\n insert(_el$251, createComponent(For, {\n each: [\n \"notes\",\n \"transcript\",\n \"metadata\",\n \"raw\"\n ],\n children: (tab) => (() => {\n var _el$253 = _tmpl$56();\n _el$253.$$click = () => {\n props.onSelectTab(tab);\n };\n insert(_el$253, tab === \"notes\" ? \"Notes\" : tab === \"transcript\" ? \"Transcript\" : tab === \"metadata\" ? \"Metadata\" : \"Raw\");\n createRenderEffect(() => setAttribute(_el$253, \"data-selected\", parsedTab() === tab ? \"true\" : void 0));\n return _el$253;\n })()\n }), _el$252);\n return _el$251;\n })(), createComponent(Show, {\n get when() {\n return props.selectedMeeting;\n },\n get fallback() {\n return (() => {\n var _el$254 = _tmpl$50();\n insert(_el$254, () => props.detailError || \"Select a meeting to inspect its notes and transcript.\");\n return _el$254;\n })();\n },\n children: (meeting) => [(() => {\n var _el$255 = _tmpl$57(), _el$256 = _el$255.firstChild, _el$257 = _el$256.nextSibling, _el$258 = _el$257.nextSibling;\n insert(_el$256, () => `ID: ${meeting().meeting.id}`);\n insert(_el$257, () => `Source: ${meeting().meeting.noteContentSource}`);\n insert(_el$258, () => `Transcript: ${meeting().meeting.transcriptSegmentCount} segments`);\n return _el$255;\n })(), createComponent(Show, {\n get when() {\n return !props.detailError;\n },\n get fallback() {\n return (() => {\n var _el$267 = _tmpl$50();\n insert(_el$267, () => props.detailError);\n return _el$267;\n })();\n },\n get children() {\n var _el$259 = _tmpl$58(), _el$261 = _el$259.firstChild.firstChild, _el$263 = _el$261.firstChild.nextSibling, _el$265 = _el$261.nextSibling.firstChild, _el$266 = _el$265.nextSibling;\n insert(_el$263, () => metadataLines(meeting()));\n insert(_el$265, () => details()?.title);\n insert(_el$266, () => details()?.body);\n return _el$259;\n }\n })]\n })];\n}\ndelegateEvents([\n \"input\",\n \"keydown\",\n \"click\"\n]);\n//#endregion\n//#region src/web-app/harness-editor.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$$2 = /* @__PURE__ */ template(`<div class=empty>No harnesses configured yet. Create one here and save it to the shared harness file.`), _tmpl$2$2 = /* @__PURE__ */ template(`<section class=harness-panel><div class=jobs-panel__head><h3>Harness Editor</h3><p>Manage meeting playbooks, inspect why they match the selected meeting, and run the selected harness through the current pipeline before changing live automation.</p></div><div class=harness-grid><div class=detail-section><div class=harness-toolbar><button class=\"button button--primary\"type=button>New Harness</button><button class=\"button button--secondary\"type=button>Duplicate</button><button class=\"button button--secondary\"type=button>Remove</button><button class=\"button button--secondary\"type=button>Reload</button><button class=\"button button--primary\"type=button>Save Harnesses</button></div><div class=jobs-list>`), _tmpl$3$2 = /* @__PURE__ */ template(`<button class=\"job-card job-card--button\"type=button><div class=job-card__head><span class=job-card__title></span><span class=job-card__status></span></div><div class=job-card__meta>`), _tmpl$4$1 = /* @__PURE__ */ template(`<div class=detail-section><div class=empty>Select a harness to edit its prompts, rules, and test output.`), _tmpl$5$1 = /* @__PURE__ */ template(`<div class=auth-card__error>`), _tmpl$6$1 = /* @__PURE__ */ template(`<div class=detail-section><div class=harness-status-row><h2></h2><span class=state-badge></span></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Id</span><input class=\"field-input field-input--plain\"></label><label><span class=field-label>Name</span><input class=\"field-input field-input--plain\"></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Provider</span><select class=select><option value>Use default</option><option value=codex>Codex</option><option value=openai>OpenAI</option><option value=openrouter>OpenRouter</option></select></label><label><span class=field-label>Model</span><input class=\"field-input field-input--plain\"placeholder=\"gpt-5-codex or openai/gpt-5-mini\"></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Priority</span><input class=\"field-input field-input--plain\"placeholder=0></label><label><span class=field-label>Working Directory</span><input class=\"field-input field-input--plain\"placeholder=/path/to/project></label></div><div class=field-row><label><span class=field-label>Prompt</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><label><span class=field-label>Prompt File</span><input class=\"field-input field-input--plain\"placeholder=./agents/customer-call/AGENT.md></label></div><div class=field-row><label><span class=field-label>System Prompt</span><textarea class=\"review-textarea review-textarea--summary\"></textarea></label><label><span class=field-label>System Prompt File</span><input class=\"field-input field-input--plain\"placeholder=./agents/customer-call/SYSTEM.md></label></div><div class=field-row><label><span class=field-label>Fallback Harnesses</span><input class=\"field-input field-input--plain\"placeholder=\"fallback-a, fallback-b\"></label></div><div class=field-row><label><span class=field-label>Event Kinds</span><input class=\"field-input field-input--plain\"placeholder=\"transcript.ready, meeting.created\"></label><label><span class=field-label>Meeting Ids</span><input class=\"field-input field-input--plain\"placeholder=doc-alpha-1111></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Folder Ids</span><input class=\"field-input field-input--plain\"placeholder=folder-team-1111></label><label><span class=field-label>Folder Names</span><input class=\"field-input field-input--plain\"placeholder=\"Team, Customers\"></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Tags</span><input class=\"field-input field-input--plain\"placeholder=\"customer, weekly\"></label><label><span class=field-label>Title Includes</span><input class=\"field-input field-input--plain\"placeholder=\"sync, review\"></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Title Regex</span><input class=\"field-input field-input--plain\"placeholder=customer.*sync></label><label><span class=field-label>Transcript Loaded</span><select class=select><option value>Either</option><option value=true>Must be loaded</option><option value=false>Must be missing</option></select></label></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Calendar Event Ids</span><input class=\"field-input field-input--plain\"placeholder=event-123></label><label><span class=field-label>Recurring Event Ids</span><input class=\"field-input field-input--plain\"placeholder=recurring-456></label></div><div class=detail-section><h3>Selected Meeting Test</h3><div class=job-card__meta></div><div class=\"field-row field-row--inline\"><label><span class=field-label>Pipeline Kind</span><select class=select><option value=notes>Notes</option><option value=enrichment>Enrichment</option></select></label><label><span class=field-label>Rule Explainer Event</span><input class=\"field-input field-input--plain\"disabled></label></div><div class=harness-toolbar><button class=\"button button--primary\"type=button>Test Harness`), _tmpl$7$1 = /* @__PURE__ */ template(`<div class=job-card__status>`), _tmpl$8$1 = /* @__PURE__ */ template(`<ul class=detail-list>`), _tmpl$9 = /* @__PURE__ */ template(`<li>`), _tmpl$0 = /* @__PURE__ */ template(`<div class=field-row><label><span class=field-label>Resolved Prompt Preview</span><textarea class=review-textarea readonly></textarea></label><label><span class=field-label>Raw Output</span><textarea class=\"review-textarea review-textarea--summary\"readonly>`), _tmpl$1 = /* @__PURE__ */ template(`<div class=harness-test-result><h3>Latest Test Run</h3><div class=job-card__meta>`), _tmpl$10 = /* @__PURE__ */ template(`<div class=field-row><label><span class=field-label>Structured Title</span><input class=\"field-input field-input--plain\"disabled></label><label><span class=field-label>Structured Summary</span><textarea class=\"review-textarea review-textarea--summary\"readonly>`);\nfunction csvValues(values) {\n return values?.join(\", \") ?? \"\";\n}\nfunction parseCsvValues(value) {\n const items = value.split(\",\").map((item) => item.trim()).filter(Boolean);\n return items.length > 0 ? [...new Set(items)] : void 0;\n}\nfunction compactMatch(match) {\n if (!match) return;\n const next = {\n calendarEventIds: match.calendarEventIds,\n eventKinds: match.eventKinds,\n folderIds: match.folderIds,\n folderNames: match.folderNames,\n meetingIds: match.meetingIds,\n recurringEventIds: match.recurringEventIds,\n tags: match.tags,\n titleIncludes: match.titleIncludes,\n titleMatches: match.titleMatches?.trim() || void 0,\n transcriptLoaded: match.transcriptLoaded\n };\n return Object.values(next).some((value) => Array.isArray(value) ? value.length > 0 : value !== void 0 && value !== \"\") ? next : void 0;\n}\nfunction sanitiseHarnessSeed(value) {\n return value.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\") || \"new-harness\";\n}\nfunction nextUniqueHarnessId(existing, seed) {\n const base = sanitiseHarnessSeed(seed);\n const ids = new Set(existing.map((harness) => harness.id));\n if (!ids.has(base)) return base;\n let index = 2;\n while (ids.has(`${base}-${index}`)) index += 1;\n return `${base}-${index}`;\n}\nfunction createHarnessTemplate(existing) {\n return {\n id: nextUniqueHarnessId(existing, \"new harness\"),\n name: \"New Harness\",\n prompt: \"Describe the structure and output you want for this meeting type.\",\n provider: \"codex\"\n };\n}\nfunction duplicateHarnessTemplate(existing, harness) {\n return {\n ...structuredClone(harness),\n id: nextUniqueHarnessId(existing, `${harness.id} copy`),\n name: `${harness.name} Copy`\n };\n}\nfunction patchHarness(harness, patch) {\n return {\n ...harness,\n ...patch,\n match: compactMatch(patch.match ?? harness.match)\n };\n}\nfunction patchHarnessMatch(harness, patch) {\n return patchHarness(harness, { match: {\n ...harness.match,\n ...patch\n } });\n}\nfunction HarnessEditorPanel(props) {\n const selectedExplanation = () => props.explanations.find((explanation) => explanation.harness.id === props.selectedHarnessId) ?? null;\n const selectedResult = () => props.testResult;\n const selectedHarness = () => props.selectedHarness;\n const updateHarness = (patch) => {\n const harness = selectedHarness();\n if (!harness) return;\n props.onChange(patchHarness(harness, patch));\n };\n const updateMatch = (patch) => {\n const harness = selectedHarness();\n if (!harness) return;\n props.onChange(patchHarnessMatch(harness, patch));\n };\n return (() => {\n var _el$ = _tmpl$2$2(), _el$3 = _el$.firstChild.nextSibling, _el$5 = _el$3.firstChild.firstChild, _el$6 = _el$5.firstChild, _el$7 = _el$6.nextSibling, _el$8 = _el$7.nextSibling, _el$9 = _el$8.nextSibling, _el$0 = _el$9.nextSibling, _el$1 = _el$5.nextSibling;\n addEventListener(_el$6, \"click\", props.onNew, true);\n addEventListener(_el$7, \"click\", props.onDuplicate, true);\n addEventListener(_el$8, \"click\", props.onRemove, true);\n addEventListener(_el$9, \"click\", props.onReload, true);\n addEventListener(_el$0, \"click\", props.onSave, true);\n insert(_el$1, createComponent(For, {\n get each() {\n return props.harnesses;\n },\n children: (harness) => {\n const explanation = () => props.explanations.find((candidate) => candidate.harness.id === harness.id) ?? null;\n return (() => {\n var _el$11 = _tmpl$3$2(), _el$12 = _el$11.firstChild, _el$13 = _el$12.firstChild, _el$14 = _el$13.nextSibling, _el$15 = _el$12.nextSibling;\n _el$11.$$click = () => {\n props.onSelect(harness.id);\n };\n insert(_el$13, () => harness.name);\n insert(_el$14, () => explanation()?.matched ? \"Matched\" : \"Check\");\n insert(_el$15, () => harness.id, null);\n insert(_el$15, (() => {\n var _c$ = memo(() => !!harness.provider);\n return () => _c$() ? ` • ${harness.provider}` : \"\";\n })(), null);\n insert(_el$15, (() => {\n var _c$2 = memo(() => !!harness.model);\n return () => _c$2() ? `/${harness.model}` : \"\";\n })(), null);\n createRenderEffect((_p$) => {\n var _v$4 = props.selectedHarnessId === harness.id, _v$5 = explanation()?.matched ? \"completed\" : \"warning\";\n _v$4 !== _p$.e && setAttribute(_el$11, \"data-selected\", _p$.e = _v$4);\n _v$5 !== _p$.t && setAttribute(_el$14, \"data-status\", _p$.t = _v$5);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n return _el$11;\n })();\n }\n }), null);\n insert(_el$1, createComponent(Show, {\n get when() {\n return props.harnesses.length === 0;\n },\n get children() {\n return _tmpl$$2();\n }\n }), null);\n insert(_el$3, createComponent(Show, {\n get when() {\n return selectedHarness();\n },\n get fallback() {\n return _tmpl$4$1();\n },\n children: (harness) => (() => {\n var _el$17 = _tmpl$6$1(), _el$18 = _el$17.firstChild, _el$19 = _el$18.firstChild, _el$20 = _el$19.nextSibling, _el$21 = _el$18.nextSibling, _el$22 = _el$21.firstChild, _el$24 = _el$22.firstChild.nextSibling, _el$27 = _el$22.nextSibling.firstChild.nextSibling, _el$28 = _el$21.nextSibling, _el$29 = _el$28.firstChild, _el$31 = _el$29.firstChild.nextSibling, _el$34 = _el$29.nextSibling.firstChild.nextSibling, _el$35 = _el$28.nextSibling, _el$36 = _el$35.firstChild, _el$38 = _el$36.firstChild.nextSibling, _el$41 = _el$36.nextSibling.firstChild.nextSibling, _el$42 = _el$35.nextSibling, _el$43 = _el$42.firstChild, _el$45 = _el$43.firstChild.nextSibling, _el$48 = _el$43.nextSibling.firstChild.nextSibling, _el$49 = _el$42.nextSibling, _el$50 = _el$49.firstChild, _el$52 = _el$50.firstChild.nextSibling, _el$55 = _el$50.nextSibling.firstChild.nextSibling, _el$56 = _el$49.nextSibling, _el$59 = _el$56.firstChild.firstChild.nextSibling, _el$60 = _el$56.nextSibling, _el$61 = _el$60.firstChild, _el$63 = _el$61.firstChild.nextSibling, _el$66 = _el$61.nextSibling.firstChild.nextSibling, _el$67 = _el$60.nextSibling, _el$68 = _el$67.firstChild, _el$70 = _el$68.firstChild.nextSibling, _el$73 = _el$68.nextSibling.firstChild.nextSibling, _el$74 = _el$67.nextSibling, _el$75 = _el$74.firstChild, _el$77 = _el$75.firstChild.nextSibling, _el$80 = _el$75.nextSibling.firstChild.nextSibling, _el$81 = _el$74.nextSibling, _el$82 = _el$81.firstChild, _el$84 = _el$82.firstChild.nextSibling, _el$87 = _el$82.nextSibling.firstChild.nextSibling, _el$88 = _el$81.nextSibling, _el$89 = _el$88.firstChild, _el$91 = _el$89.firstChild.nextSibling, _el$94 = _el$89.nextSibling.firstChild.nextSibling, _el$95 = _el$88.nextSibling, _el$97 = _el$95.firstChild.nextSibling, _el$98 = _el$97.nextSibling, _el$99 = _el$98.firstChild, _el$101 = _el$99.firstChild.nextSibling, _el$104 = _el$99.nextSibling.firstChild.nextSibling, _el$106 = _el$98.nextSibling.firstChild;\n insert(_el$19, () => harness().name);\n insert(_el$20, () => props.dirty ? \"Unsaved changes\" : \"Saved\");\n _el$24.$$input = (event) => {\n updateHarness({ id: event.currentTarget.value.trim() });\n };\n _el$27.$$input = (event) => {\n updateHarness({ name: event.currentTarget.value });\n };\n _el$31.addEventListener(\"change\", (event) => {\n updateHarness({ provider: event.currentTarget.value === \"\" ? void 0 : event.currentTarget.value });\n });\n _el$34.$$input = (event) => {\n updateHarness({ model: event.currentTarget.value.trim() || void 0 });\n };\n _el$38.$$input = (event) => {\n const value = event.currentTarget.value.trim();\n updateHarness({ priority: value ? Number(value) : void 0 });\n };\n _el$41.$$input = (event) => {\n updateHarness({ cwd: event.currentTarget.value.trim() || void 0 });\n };\n _el$45.$$input = (event) => {\n updateHarness({ prompt: event.currentTarget.value.trim() || void 0 });\n };\n _el$48.$$input = (event) => {\n updateHarness({ promptFile: event.currentTarget.value.trim() || void 0 });\n };\n _el$52.$$input = (event) => {\n updateHarness({ systemPrompt: event.currentTarget.value.trim() || void 0 });\n };\n _el$55.$$input = (event) => {\n updateHarness({ systemPromptFile: event.currentTarget.value.trim() || void 0 });\n };\n _el$59.$$input = (event) => {\n updateHarness({ fallbackHarnessIds: parseCsvValues(event.currentTarget.value) });\n };\n _el$63.$$input = (event) => {\n updateMatch({ eventKinds: parseCsvValues(event.currentTarget.value) });\n };\n _el$66.$$input = (event) => {\n updateMatch({ meetingIds: parseCsvValues(event.currentTarget.value) });\n };\n _el$70.$$input = (event) => {\n updateMatch({ folderIds: parseCsvValues(event.currentTarget.value) });\n };\n _el$73.$$input = (event) => {\n updateMatch({ folderNames: parseCsvValues(event.currentTarget.value) });\n };\n _el$77.$$input = (event) => {\n updateMatch({ tags: parseCsvValues(event.currentTarget.value) });\n };\n _el$80.$$input = (event) => {\n updateMatch({ titleIncludes: parseCsvValues(event.currentTarget.value) });\n };\n _el$84.$$input = (event) => {\n updateMatch({ titleMatches: event.currentTarget.value.trim() || void 0 });\n };\n _el$87.addEventListener(\"change\", (event) => {\n updateMatch({ transcriptLoaded: event.currentTarget.value === \"\" ? void 0 : event.currentTarget.value === \"true\" });\n });\n _el$91.$$input = (event) => {\n updateMatch({ calendarEventIds: parseCsvValues(event.currentTarget.value) });\n };\n _el$94.$$input = (event) => {\n updateMatch({ recurringEventIds: parseCsvValues(event.currentTarget.value) });\n };\n insert(_el$97, (() => {\n var _c$3 = memo(() => !!props.selectedMeeting);\n return () => _c$3() ? `Run ${harness().name} against ${props.selectedMeeting.meeting.title || props.selectedMeeting.meeting.id}.` : \"Select a meeting to explain and test this harness.\";\n })());\n _el$101.addEventListener(\"change\", (event) => {\n props.onTestKindChange(event.currentTarget.value);\n });\n addEventListener(_el$106, \"click\", props.onTest, true);\n insert(_el$95, createComponent(Show, {\n get when() {\n return selectedExplanation();\n },\n children: (explanation) => [(() => {\n var _el$108 = _tmpl$7$1();\n insert(_el$108, () => explanation().matched ? \"Matched selected meeting\" : \"Did not match selected meeting\");\n createRenderEffect(() => setAttribute(_el$108, \"data-status\", explanation().matched ? \"completed\" : \"warning\"));\n return _el$108;\n })(), (() => {\n var _el$109 = _tmpl$8$1();\n insert(_el$109, createComponent(For, {\n get each() {\n return explanation().reasons;\n },\n children: (reason) => (() => {\n var _el$110 = _tmpl$9();\n insert(_el$110, reason);\n return _el$110;\n })()\n }));\n return _el$109;\n })()]\n }), null);\n insert(_el$95, createComponent(Show, {\n get when() {\n return selectedResult();\n },\n children: (result) => (() => {\n var _el$111 = _tmpl$1(), _el$113 = _el$111.firstChild.nextSibling;\n insert(_el$113, () => result().status, null);\n insert(_el$113, (() => {\n var _c$4 = memo(() => !!result().provider);\n return () => _c$4() ? ` • ${result().provider}` : \"\";\n })(), null);\n insert(_el$113, (() => {\n var _c$5 = memo(() => !!result().model);\n return () => _c$5() ? `/${result().model}` : \"\";\n })(), null);\n insert(_el$113, (() => {\n var _c$6 = memo(() => !!result().parseMode);\n return () => _c$6() ? ` • ${result().parseMode}` : \"\";\n })(), null);\n insert(_el$111, createComponent(Show, {\n get when() {\n return result().status === \"failed\";\n },\n get children() {\n var _el$114 = _tmpl$5$1();\n insert(_el$114, () => result().error);\n return _el$114;\n }\n }), null);\n insert(_el$111, createComponent(Show, {\n get when() {\n return result().status === \"completed\";\n },\n get children() {\n return [(() => {\n var _el$115 = _tmpl$0(), _el$116 = _el$115.firstChild, _el$118 = _el$116.firstChild.nextSibling, _el$121 = _el$116.nextSibling.firstChild.nextSibling;\n createRenderEffect(() => _el$118.value = result().prompt);\n createRenderEffect(() => _el$121.value = result().rawOutput ?? \"\");\n return _el$115;\n })(), createComponent(Show, {\n get when() {\n return result().structured;\n },\n children: (structured) => (() => {\n var _el$122 = _tmpl$10(), _el$123 = _el$122.firstChild, _el$125 = _el$123.firstChild.nextSibling, _el$128 = _el$123.nextSibling.firstChild.nextSibling;\n createRenderEffect(() => _el$125.value = structured().title);\n createRenderEffect(() => _el$128.value = structured().summary ?? \"\");\n return _el$122;\n })()\n })];\n }\n }), null);\n return _el$111;\n })()\n }), null);\n insert(_el$17, createComponent(Show, {\n get when() {\n return props.error;\n },\n get children() {\n var _el$107 = _tmpl$5$1();\n insert(_el$107, () => props.error);\n return _el$107;\n }\n }), null);\n createRenderEffect((_p$) => {\n var _v$6 = props.dirty ? \"busy\" : \"ok\", _v$7 = !props.selectedMeeting;\n _v$6 !== _p$.e && setAttribute(_el$20, \"data-tone\", _p$.e = _v$6);\n _v$7 !== _p$.t && (_el$106.disabled = _p$.t = _v$7);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n createRenderEffect(() => _el$24.value = harness().id);\n createRenderEffect(() => _el$27.value = harness().name);\n createRenderEffect(() => _el$31.value = harness().provider ?? \"\");\n createRenderEffect(() => _el$34.value = harness().model ?? \"\");\n createRenderEffect(() => _el$38.value = harness().priority?.toString() ?? \"\");\n createRenderEffect(() => _el$41.value = harness().cwd ?? \"\");\n createRenderEffect(() => _el$45.value = harness().prompt ?? \"\");\n createRenderEffect(() => _el$48.value = harness().promptFile ?? \"\");\n createRenderEffect(() => _el$52.value = harness().systemPrompt ?? \"\");\n createRenderEffect(() => _el$55.value = harness().systemPromptFile ?? \"\");\n createRenderEffect(() => _el$59.value = csvValues(harness().fallbackHarnessIds));\n createRenderEffect(() => _el$63.value = csvValues(harness().match?.eventKinds));\n createRenderEffect(() => _el$66.value = csvValues(harness().match?.meetingIds));\n createRenderEffect(() => _el$70.value = csvValues(harness().match?.folderIds));\n createRenderEffect(() => _el$73.value = csvValues(harness().match?.folderNames));\n createRenderEffect(() => _el$77.value = csvValues(harness().match?.tags));\n createRenderEffect(() => _el$80.value = csvValues(harness().match?.titleIncludes));\n createRenderEffect(() => _el$84.value = harness().match?.titleMatches ?? \"\");\n createRenderEffect(() => _el$87.value = harness().match?.transcriptLoaded === void 0 ? \"\" : String(harness().match?.transcriptLoaded));\n createRenderEffect(() => _el$91.value = csvValues(harness().match?.calendarEventIds));\n createRenderEffect(() => _el$94.value = csvValues(harness().match?.recurringEventIds));\n createRenderEffect(() => _el$101.value = props.testKind);\n createRenderEffect(() => _el$104.value = props.explanationEventKind ?? \"Select a meeting\");\n return _el$17;\n })()\n }), null);\n createRenderEffect((_p$) => {\n var _v$ = !selectedHarness(), _v$2 = !selectedHarness(), _v$3 = !props.dirty;\n _v$ !== _p$.e && (_el$7.disabled = _p$.e = _v$);\n _v$2 !== _p$.t && (_el$8.disabled = _p$.t = _v$2);\n _v$3 !== _p$.a && (_el$0.disabled = _p$.a = _v$3);\n return _p$;\n }, {\n e: void 0,\n t: void 0,\n a: void 0\n });\n return _el$;\n })();\n}\ndelegateEvents([\"click\", \"input\"]);\n//#endregion\n//#region src/agent-defaults.ts\nvar defaultGranolaAgentModels = {\n codex: \"gpt-5-codex\",\n openai: \"gpt-5-mini\",\n openrouter: \"openai/gpt-5-mini\"\n};\nfunction defaultGranolaAgentModel(provider, explicitModel) {\n return explicitModel?.trim() || defaultGranolaAgentModels[provider];\n}\nfunction granolaAgentProviderLabel(provider) {\n switch (provider) {\n case \"codex\": return \"Codex\";\n case \"openai\": return \"OpenAI\";\n case \"openrouter\": return \"OpenRouter\";\n }\n}\n//#endregion\n//#region src/web-app/onboarding.tsx\n/** @jsxImportSource solid-js */\nvar _tmpl$$1 = /* @__PURE__ */ template(`<section class=onboarding-panel><div class=onboarding-panel__hero><div><p class=onboarding-panel__eyebrow>First-Run Setup</p><h2>Connect Granola, import your meetings, and pick an agent.</h2><p>The toolkit already has the core automation engine. This flow just gets you to the first useful setup without dumping every advanced panel on screen at once.</p></div><div class=onboarding-panel__summary><div class=job-card__status></div><div class=auth-card__meta></div></div></div><div class=onboarding-steps>`), _tmpl$2$1 = /* @__PURE__ */ template(`<div class=auth-card__meta>Single-folder workspace detected: `), _tmpl$3$1 = /* @__PURE__ */ template(`<div class=onboarding-step__body><input class=input placeholder=grn_... type=password><div class=toolbar-actions><button class=\"button button--primary\"type=button>Save API key</button><button class=\"button button--secondary\"type=button>Import desktop session`), _tmpl$4 = /* @__PURE__ */ template(`<div class=onboarding-step__body><button class=\"button button--primary\"type=button>Import meetings now`), _tmpl$5 = /* @__PURE__ */ template(`<div class=onboarding-step__body><div class=onboarding-providers></div><div class=auth-card__meta>Starter pipeline: transcript-ready meetings will generate reviewable notes with <!>.</div><button class=\"button button--primary\"type=button>Create starter pipeline`), _tmpl$6 = /* @__PURE__ */ template(`<article class=onboarding-step><div class=onboarding-step__head><div><span class=onboarding-step__number>Step </span><h3></h3></div><div class=job-card__status></div></div><p>`), _tmpl$7 = /* @__PURE__ */ template(`<div class=auth-card__meta>`), _tmpl$8 = /* @__PURE__ */ template(`<button class=onboarding-provider type=button><span class=onboarding-provider__title></span><span class=onboarding-provider__body>`);\nvar starterHarnessId = \"starter-meeting-notes\";\nvar starterRuleId = \"starter-meeting-notes-review\";\nvar starterRuleActionId = \"starter-meeting-notes-pipeline\";\nvar providerOptions = [\n {\n description: \"Use the local Codex CLI and your ChatGPT subscription.\",\n provider: \"codex\"\n },\n {\n description: \"Use an OPENROUTER_API_KEY and route to your preferred model.\",\n provider: \"openrouter\"\n },\n {\n description: \"Use an OPENAI_API_KEY with direct OpenAI access.\",\n provider: \"openai\"\n }\n];\nfunction suggestedFolderLabel(folders) {\n if (folders.length === 1) return folders[0]?.name;\n}\nfunction deriveOnboardingState(input) {\n const auth = input.appState?.auth;\n const connected = Boolean(auth?.apiKeyAvailable || auth?.storedSessionAvailable);\n const synced = Boolean(input.appState?.sync.lastCompletedAt);\n const pipelineReady = input.harnesses.length > 0 && input.automationRuleCount > 0;\n const syncedMeetingCount = input.appState?.documents.count ?? input.meetingsLoadedCount;\n const stepCards = [\n {\n body: \"Store a Granola API key for the toolkit. Desktop session import stays available as a fallback.\",\n complete: connected,\n cta: connected ? void 0 : \"Save API key\",\n detail: connected ? `Connected via ${auth?.mode === \"api-key\" ? \"API key\" : auth?.mode === \"stored-session\" ? \"stored session\" : \"fallback auth\"}.` : \"Recommended: use a Granola Personal API key from Settings → API.\",\n title: \"Connect Granola\"\n },\n {\n body: \"Run the first sync so the local index has meetings, folders, and transcript-ready events.\",\n complete: synced,\n cta: synced ? void 0 : \"Import meetings\",\n detail: synced ? `${syncedMeetingCount} meetings indexed locally.` : \"This builds the local meeting index the workspace and automation layer rely on.\",\n title: \"Import Meetings\"\n },\n {\n body: \"Choose the AI agent you want to use by default, then seed a starter reviewable notes pipeline.\",\n complete: pipelineReady,\n cta: pipelineReady ? void 0 : \"Create starter pipeline\",\n detail: pipelineReady ? \"Starter harnesses and automation rules are ready.\" : \"The starter pipeline generates reviewable meeting notes on transcript-ready events.\",\n title: \"Choose An Agent\"\n }\n ];\n return {\n complete: connected && synced && pipelineReady,\n connected,\n pipelineReady,\n stepCards,\n synced,\n syncedMeetingCount\n };\n}\nfunction upsertById(items, nextItem) {\n return [...items.filter((item) => item.id !== nextItem.id), nextItem];\n}\nfunction createStarterHarness(provider) {\n return {\n id: starterHarnessId,\n match: { transcriptLoaded: true },\n model: defaultGranolaAgentModel(provider),\n name: \"Starter Meeting Notes\",\n priority: 10,\n prompt: [\n \"Turn this meeting into concise, high-signal notes for internal follow-up.\",\n \"\",\n \"Requirements:\",\n \"- lead with the most important outcome, not a play-by-play recap\",\n \"- capture decisions, action items, open questions, and risks\",\n \"- assign owners when the transcript makes them clear\",\n \"- call out uncertainty explicitly instead of inventing missing detail\",\n \"- optimise for a team reading this five minutes after the meeting ends\"\n ].join(\"\\n\"),\n provider\n };\n}\nfunction createStarterRule() {\n return {\n actions: [{\n approvalMode: \"manual\",\n harnessId: starterHarnessId,\n id: starterRuleActionId,\n kind: \"agent\",\n name: \"Generate starter meeting notes\",\n pipeline: { kind: \"notes\" }\n }],\n id: starterRuleId,\n name: \"Review starter notes when a transcript is ready\",\n when: {\n eventKinds: [\"transcript.ready\"],\n transcriptLoaded: true\n }\n };\n}\nfunction buildStarterPipeline(existing) {\n const harness = createStarterHarness(existing.provider);\n const rule = createStarterRule();\n return {\n harnesses: upsertById(existing.harnesses, harness),\n rule,\n rules: upsertById(existing.rules, rule)\n };\n}\nfunction OnboardingPanel(props) {\n const dominantFolder = () => suggestedFolderLabel(props.folders);\n return (() => {\n var _el$ = _tmpl$$1(), _el$2 = _el$.firstChild, _el$4 = _el$2.firstChild.nextSibling, _el$5 = _el$4.firstChild, _el$6 = _el$5.nextSibling, _el$7 = _el$2.nextSibling;\n insert(_el$5, () => props.state.complete ? \"Ready\" : \"Setup in progress\");\n insert(_el$6, (() => {\n var _c$ = memo(() => !!props.state.synced);\n return () => _c$() ? `${props.state.syncedMeetingCount} meetings indexed` : \"No local sync yet\";\n })());\n insert(_el$4, createComponent(Show, {\n get when() {\n return dominantFolder();\n },\n children: (folderName) => (() => {\n var _el$8 = _tmpl$2$1();\n _el$8.firstChild;\n insert(_el$8, folderName, null);\n return _el$8;\n })()\n }), null);\n insert(_el$7, createComponent(For, {\n get each() {\n return props.state.stepCards;\n },\n children: (step, index) => (() => {\n var _el$0 = _tmpl$6(), _el$1 = _el$0.firstChild, _el$10 = _el$1.firstChild, _el$11 = _el$10.firstChild;\n _el$11.firstChild;\n var _el$13 = _el$11.nextSibling, _el$14 = _el$10.nextSibling, _el$15 = _el$1.nextSibling;\n insert(_el$11, () => index() + 1, null);\n insert(_el$13, () => step.title);\n insert(_el$14, () => step.complete ? \"Done\" : \"Next\");\n insert(_el$15, () => step.body);\n insert(_el$0, createComponent(Show, {\n get when() {\n return step.detail;\n },\n children: (detail) => (() => {\n var _el$31 = _tmpl$7();\n insert(_el$31, detail);\n return _el$31;\n })()\n }), null);\n insert(_el$0, createComponent(Show, {\n get when() {\n return memo(() => index() === 0)() && !step.complete;\n },\n get children() {\n var _el$16 = _tmpl$3$1(), _el$17 = _el$16.firstChild, _el$19 = _el$17.nextSibling.firstChild, _el$20 = _el$19.nextSibling;\n _el$17.$$input = (event) => {\n props.onApiKeyDraftChange(event.currentTarget.value);\n };\n addEventListener(_el$19, \"click\", props.onSaveApiKey, true);\n addEventListener(_el$20, \"click\", props.onImportDesktopSession, true);\n createRenderEffect(() => _el$20.disabled = !props.auth?.supabaseAvailable);\n createRenderEffect(() => _el$17.value = props.apiKeyDraft);\n return _el$16;\n }\n }), null);\n insert(_el$0, createComponent(Show, {\n get when() {\n return memo(() => index() === 1)() && !step.complete;\n },\n get children() {\n var _el$21 = _tmpl$4(), _el$22 = _el$21.firstChild;\n addEventListener(_el$22, \"click\", props.onRunSync, true);\n createRenderEffect(() => _el$22.disabled = !props.state.connected);\n return _el$21;\n }\n }), null);\n insert(_el$0, createComponent(Show, {\n get when() {\n return memo(() => index() === 2)() && !step.complete;\n },\n get children() {\n var _el$23 = _tmpl$5(), _el$24 = _el$23.firstChild, _el$25 = _el$24.nextSibling, _el$29 = _el$25.firstChild.nextSibling;\n _el$29.nextSibling;\n var _el$30 = _el$25.nextSibling;\n insert(_el$24, createComponent(For, {\n each: providerOptions,\n children: (option) => (() => {\n var _el$32 = _tmpl$8(), _el$33 = _el$32.firstChild, _el$34 = _el$33.nextSibling;\n _el$32.$$click = () => {\n props.onSelectProvider(option.provider);\n };\n insert(_el$33, () => granolaAgentProviderLabel(option.provider));\n insert(_el$34, () => option.description);\n createRenderEffect(() => setAttribute(_el$32, \"data-selected\", props.preferredProvider === option.provider));\n return _el$32;\n })()\n }));\n insert(_el$25, () => granolaAgentProviderLabel(props.preferredProvider), _el$29);\n addEventListener(_el$30, \"click\", props.onCreateStarterPipeline, true);\n createRenderEffect(() => _el$30.disabled = !props.state.synced);\n return _el$23;\n }\n }), null);\n createRenderEffect((_p$) => {\n var _v$ = step.complete, _v$2 = step.complete ? \"completed\" : \"warning\";\n _v$ !== _p$.e && setAttribute(_el$0, \"data-complete\", _p$.e = _v$);\n _v$2 !== _p$.t && setAttribute(_el$14, \"data-status\", _p$.t = _v$2);\n return _p$;\n }, {\n e: void 0,\n t: void 0\n });\n return _el$0;\n })()\n }));\n createRenderEffect(() => setAttribute(_el$5, \"data-status\", props.state.complete ? \"completed\" : \"warning\"));\n return _el$;\n })();\n}\ndelegateEvents([\"input\", \"click\"]);\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.`), _tmpl$2 = /* @__PURE__ */ template(`<div class=\"shell shell--onboarding\"><main class=\"pane detail detail--onboarding\">`), _tmpl$3 = /* @__PURE__ */ template(`<section class=jobs-panel><div class=auth-card><div class=\"auth-card__meta auth-card__error\">`);\nfunction browserConfig() {\n return { passwordRequired: Boolean(window.__GRANOLA_SERVER__?.passwordRequired) };\n}\nasync function requestJson(path, init) {\n const response = await fetch(path, init);\n const payload = await response.json().catch(() => ({}));\n if (!response.ok) {\n const error = typeof payload.error === \"string\" && payload.error.trim() ? payload.error : response.statusText || \"Request failed\";\n throw new Error(error);\n }\n return payload;\n}\nfunction App() {\n const startup = startupSelectionFromSearch(window.location.search);\n const initialPreferences = parseWorkspacePreferences(window.localStorage.getItem(granolaWebWorkspaceStorageKey));\n const [state, setState] = createStore({\n apiKeyDraft: \"\",\n automationArtefactDraftMarkdown: \"\",\n automationArtefactDraftSummary: \"\",\n automationArtefactDraftTitle: \"\",\n automationArtefactError: \"\",\n automationArtefacts: [],\n automationRules: [],\n appState: null,\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n harnessDirty: false,\n harnessError: \"\",\n harnessExplainEventKind: null,\n harnessExplanations: [],\n harnessTestKind: \"notes\",\n harnessTestResult: null,\n harnesses: [],\n listError: \"\",\n meetingSource: \"live\",\n meetings: [],\n processingIssueError: \"\",\n processingIssues: [],\n preferredProvider: \"openrouter\",\n quickOpen: \"\",\n recentMeetings: initialPreferences.recentMeetings,\n savedFilters: initialPreferences.savedFilters,\n search: \"\",\n selectedAutomationArtefactId: null,\n selectedFolderId: startup.folderId || null,\n selectedHarnessId: null,\n selectedMeetingBundle: null,\n selectedMeetingId: startup.meetingId || null,\n selectedMeeting: null,\n reviewNote: \"\",\n serverLocked: browserConfig().passwordRequired,\n serverPassword: \"\",\n sort: \"updated-desc\",\n statusLabel: browserConfig().passwordRequired ? \"Server locked\" : \"Connecting…\",\n statusTone: browserConfig().passwordRequired ? \"error\" : \"idle\",\n updatedFrom: \"\",\n updatedTo: \"\",\n workspaceTab: parseWorkspaceTab(startup.workspaceTab)\n });\n let client = null;\n let automationPanelsHydrated = false;\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 loadAutomationRules = async () => {\n if (!client) return;\n try {\n setState(\"automationRules\", (await client.listAutomationRules()).rules);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setState(\"automationRules\", []);\n }\n };\n const syncSelectedArtefact = (artefacts, options = {}) => {\n const preferred = (options.preferredId ? artefacts.find((candidate) => candidate.id === options.preferredId) : void 0) ?? (options.preferredMeetingId ? artefacts.find((candidate) => candidate.meetingId === options.preferredMeetingId && candidate.status === \"generated\") : void 0) ?? artefacts.find((candidate) => candidate.status === \"generated\") ?? artefacts[0];\n setState(\"selectedAutomationArtefactId\", preferred?.id ?? null);\n setState(\"automationArtefactDraftTitle\", preferred?.structured.title ?? \"\");\n setState(\"automationArtefactDraftSummary\", preferred?.structured.summary ?? \"\");\n setState(\"automationArtefactDraftMarkdown\", preferred?.structured.markdown ?? \"\");\n setState(\"reviewNote\", \"\");\n };\n const loadAutomationArtefacts = async (options = {}) => {\n if (!client) return;\n try {\n setState(\"automationArtefactError\", \"\");\n const result = await client.listAutomationArtefacts({ limit: 30 });\n setState(\"automationArtefacts\", result.artefacts);\n syncSelectedArtefact(result.artefacts, {\n preferredId: options.preferredId ?? state.selectedAutomationArtefactId,\n preferredMeetingId: options.preferredMeetingId ?? state.selectedMeetingId\n });\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setState(\"automationArtefacts\", []);\n syncSelectedArtefact([]);\n }\n };\n const loadProcessingIssues = async () => {\n if (!client) return;\n try {\n setState(\"processingIssueError\", \"\");\n setState(\"processingIssues\", (await client.listProcessingIssues({ limit: 20 })).issues);\n } catch (error) {\n setState(\"processingIssueError\", error instanceof Error ? error.message : String(error));\n setState(\"processingIssues\", []);\n }\n };\n const selectHarnessId = (harnesses, preferredId) => {\n if (preferredId && harnesses.some((harness) => harness.id === preferredId)) return preferredId;\n return harnesses[0]?.id ?? null;\n };\n const selectedHarness = () => state.harnesses.find((harness) => harness.id === state.selectedHarnessId) ?? null;\n const loadHarnessExplanations = async (meetingId) => {\n if (!client || !meetingId) {\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\n return;\n }\n try {\n const result = await client.explainAgentHarnesses(meetingId);\n setState(\"harnessExplainEventKind\", result.eventKind);\n setState(\"harnessExplanations\", result.harnesses);\n } catch (error) {\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\n setState(\"harnessError\", error instanceof Error ? error.message : String(error));\n }\n };\n const loadHarnesses = async (preferredId) => {\n if (!client) return;\n try {\n setState(\"harnessError\", \"\");\n const result = await client.listAgentHarnesses();\n const nextSelectedHarnessId = selectHarnessId(result.harnesses, preferredId ?? state.selectedHarnessId);\n setState(\"harnesses\", result.harnesses);\n setState(\"selectedHarnessId\", nextSelectedHarnessId);\n const nextPreferredProvider = result.harnesses.find((harness) => harness.provider)?.provider;\n if (nextPreferredProvider) setState(\"preferredProvider\", nextPreferredProvider);\n setState(\"harnessDirty\", false);\n setState(\"harnessTestResult\", null);\n await loadHarnessExplanations(state.selectedMeetingId);\n } catch (error) {\n setState(\"harnessError\", error instanceof Error ? error.message : String(error));\n setState(\"harnesses\", []);\n setState(\"selectedHarnessId\", null);\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\n setState(\"harnessTestResult\", null);\n }\n };\n const updateHarness = (nextHarness) => {\n setState(\"harnesses\", state.harnesses.map((harness) => harness.id === nextHarness.id ? nextHarness : harness));\n setState(\"selectedHarnessId\", nextHarness.id);\n setState(\"harnessDirty\", true);\n setState(\"harnessTestResult\", null);\n };\n const createHarness = () => {\n const nextHarness = createHarnessTemplate(state.harnesses);\n setState(\"harnesses\", [...state.harnesses, nextHarness]);\n setState(\"selectedHarnessId\", nextHarness.id);\n setState(\"harnessDirty\", true);\n setState(\"harnessTestResult\", null);\n };\n const duplicateHarness = () => {\n const harness = selectedHarness();\n if (!harness) return;\n const nextHarness = duplicateHarnessTemplate(state.harnesses, harness);\n setState(\"harnesses\", [...state.harnesses, nextHarness]);\n setState(\"selectedHarnessId\", nextHarness.id);\n setState(\"harnessDirty\", true);\n setState(\"harnessTestResult\", null);\n };\n const removeHarness = () => {\n if (!state.selectedHarnessId) return;\n const nextHarnesses = state.harnesses.filter((harness) => harness.id !== state.selectedHarnessId);\n setState(\"harnesses\", nextHarnesses);\n setState(\"selectedHarnessId\", selectHarnessId(nextHarnesses, null));\n setState(\"harnessDirty\", true);\n setState(\"harnessTestResult\", null);\n };\n const saveHarnesses = async () => {\n if (!client) return;\n setStatus(\"Saving harnesses…\", \"busy\");\n try {\n const result = await client.saveAgentHarnesses(state.harnesses);\n const nextSelectedHarnessId = selectHarnessId(result.harnesses, state.selectedHarnessId);\n setState(\"harnesses\", result.harnesses);\n setState(\"selectedHarnessId\", nextSelectedHarnessId);\n const nextPreferredProvider = result.harnesses.find((harness) => harness.provider)?.provider;\n if (nextPreferredProvider) setState(\"preferredProvider\", nextPreferredProvider);\n setState(\"harnessDirty\", false);\n setState(\"harnessError\", \"\");\n await loadHarnessExplanations(state.selectedMeetingId);\n setStatus(\"Harnesses saved\", \"ok\");\n } catch (error) {\n setState(\"harnessError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Harness save failed\", \"error\");\n }\n };\n const reloadHarnesses = async () => {\n setStatus(\"Reloading harnesses…\", \"busy\");\n await loadHarnesses(state.selectedHarnessId);\n setStatus(\"Harnesses reloaded\", \"ok\");\n };\n const createStarterPipeline = async () => {\n if (!client) return;\n setStatus(\"Creating starter pipeline…\", \"busy\");\n try {\n const [currentHarnesses, currentRules] = await Promise.all([client.listAgentHarnesses(), client.listAutomationRules()]);\n const starter = buildStarterPipeline({\n harnesses: currentHarnesses.harnesses,\n provider: state.preferredProvider,\n rules: currentRules.rules\n });\n await client.saveAgentHarnesses(starter.harnesses);\n await client.saveAutomationRules(starter.rules);\n await refreshAll();\n setStatus(\"Starter pipeline ready\", \"ok\");\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Starter pipeline setup failed\", \"error\");\n }\n };\n const testHarness = async () => {\n if (!client) return;\n const harness = selectedHarness();\n if (!harness) {\n setStatus(\"Select a harness first\", \"error\");\n return;\n }\n const meetingId = state.selectedMeetingId;\n if (!meetingId) {\n setStatus(\"Select a meeting first\", \"error\");\n return;\n }\n setStatus(\"Testing harness…\", \"busy\");\n try {\n const bundle = state.selectedMeetingBundle?.document.id === meetingId ? state.selectedMeetingBundle : await client.getMeeting(meetingId, { requireCache: true });\n setState(\"harnessTestResult\", (await client.evaluateAutomationCases([{\n bundle,\n id: `web:${meetingId}`,\n title: bundle.meeting.meeting.title || bundle.document.title || bundle.document.id\n }], {\n harnessIds: [harness.id],\n kind: state.harnessTestKind,\n model: harness.model,\n provider: harness.provider\n })).results[0] ?? null);\n setStatus(\"Harness test complete\", \"ok\");\n } catch (error) {\n setState(\"harnessTestResult\", {\n caseId: `web:${meetingId}`,\n caseTitle: state.selectedMeeting?.meeting.title || meetingId,\n error: error instanceof Error ? error.message : String(error),\n harnessId: harness.id,\n harnessName: harness.name,\n prompt: \"\",\n status: \"failed\"\n });\n setStatus(\"Harness test failed\", \"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 await loadHarnessExplanations(bundle.document.id);\n await loadAutomationArtefacts({\n preferredId: state.selectedAutomationArtefactId,\n preferredMeetingId: bundle.document.id\n });\n } catch (error) {\n setState(\"selectedMeetingBundle\", null);\n setState(\"selectedMeeting\", null);\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\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 setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\n await loadAutomationArtefacts({\n preferredId: state.selectedAutomationArtefactId,\n preferredMeetingId: null\n });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n setState(\"listError\", message);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n setState(\"detailError\", message);\n setState(\"harnessExplainEventKind\", null);\n setState(\"harnessExplanations\", []);\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 loadHarnesses(),\n loadAutomationRules(),\n loadAutomationRuns(),\n loadAutomationArtefacts(),\n loadProcessingIssues(),\n mergeAuthState()\n ]);\n await loadMeetings({ refresh: forceRefresh });\n setState(\"serverLocked\", false);\n setStatus(forceRefresh ? \"Sync complete\" : state.meetingSource === \"index\" ? \"Loaded from index\" : \"Connected\", \"ok\");\n };\n const connectAndRefresh = async (forceRefresh = false) => {\n try {\n await refreshAll(forceRefresh);\n } catch (error) {\n setStatus(\"Connection failed\", \"error\");\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n }\n };\n const quickOpenMeeting = async () => {\n if (!client) return;\n const query = state.quickOpen.trim();\n if (!query) {\n setStatus(\"Enter a title or id\", \"error\");\n return;\n }\n setStatus(\"Opening meeting…\", \"busy\");\n try {\n const bundle = await client.findMeeting(query);\n setState(\"selectedFolderId\", bundle.meeting.meeting.folders[0]?.id || null);\n setState(\"search\", \"\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n await loadMeetings({ preferredMeetingId: bundle.document.id });\n setStatus(\"Connected\", \"ok\");\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Quick open failed\", \"error\");\n }\n };\n const saveApiKey = async () => {\n if (!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 requestJson(granolaTransportPaths.authLogin, {\n body: JSON.stringify({ apiKey: state.apiKeyDraft.trim() }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n setState(\"apiKeyDraft\", \"\");\n setState(\"detailError\", \"\");\n if (state.appState) setState(\"appState\", \"auth\", auth);\n setStatus(\"API key saved\", \"ok\");\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 setStatus(\"Importing desktop session…\", \"busy\");\n try {\n const auth = await requestJson(granolaTransportPaths.authLogin, {\n body: JSON.stringify({}),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n setState(\"detailError\", \"\");\n if (state.appState) setState(\"appState\", \"auth\", auth);\n setStatus(\"Desktop session imported\", \"ok\");\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 setStatus(\"Refreshing session…\", \"busy\");\n try {\n await mergeAuthState(await requestJson(granolaTransportPaths.authRefresh, { method: \"POST\" }));\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 setStatus(\"Switching auth source…\", \"busy\");\n try {\n await mergeAuthState(await requestJson(granolaTransportPaths.authMode, {\n body: JSON.stringify({ mode }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n }));\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 setStatus(\"Signing out…\", \"busy\");\n try {\n await mergeAuthState(await requestJson(granolaTransportPaths.authLogout, { method: \"POST\" }));\n await refreshAll();\n } catch (error) {\n await mergeAuthState();\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Sign out failed\", \"error\");\n }\n };\n const exportNotes = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder notes…\" : \"Exporting notes…\", \"busy\");\n try {\n await client.exportNotes(\"markdown\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const clearFilters = async () => {\n setState(\"search\", \"\");\n setState(\"sort\", \"updated-desc\");\n setState(\"updatedFrom\", \"\");\n setState(\"updatedTo\", \"\");\n setState(\"selectedFolderId\", null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(\"Filters cleared\", \"ok\");\n };\n const saveCurrentFilter = () => {\n updatePreferences((preferences) => saveWorkspaceFilter(preferences, {\n folders: state.folders,\n search: state.search,\n selectedFolderId: state.selectedFolderId,\n sort: state.sort,\n updatedFrom: state.updatedFrom,\n updatedTo: state.updatedTo\n }, { idFactory: () => `filter-${Date.now()}` }));\n setStatus(\"Saved filter\", \"ok\");\n };\n const applySavedFilterPreset = async (id) => {\n const preset = state.savedFilters.find((candidate) => candidate.id === id);\n if (!preset) return;\n const nextFilters = applyWorkspaceFilter(preset);\n setState(\"search\", nextFilters.search);\n setState(\"selectedFolderId\", nextFilters.selectedFolderId);\n setState(\"sort\", nextFilters.sort);\n setState(\"updatedFrom\", nextFilters.updatedFrom);\n setState(\"updatedTo\", nextFilters.updatedTo);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings();\n setStatus(`Applied ${preset.label}`, \"ok\");\n };\n const removeSavedFilterPreset = (id) => {\n updatePreferences((preferences) => removeWorkspaceFilter(preferences, id));\n setStatus(\"Removed saved filter\", \"ok\");\n };\n const openRecentMeeting = async (meetingId, folderId) => {\n if (folderId !== void 0) {\n setState(\"selectedFolderId\", folderId || null);\n setState(\"selectedMeetingId\", null);\n setState(\"selectedMeeting\", null);\n setState(\"selectedMeetingBundle\", null);\n await loadMeetings({ preferredMeetingId: meetingId });\n return;\n }\n await loadMeeting(meetingId);\n };\n const meetingEmptyHint = () => {\n if (!state.appState) return \"Connect to the local server to load meetings.\";\n if (state.appState.auth.lastError) return \"Resolve auth first, then sync again.\";\n if (!state.appState.documents.loaded && !state.appState.sync.lastCompletedAt) return \"Run Sync now to populate your local meeting index.\";\n return \"Try a different folder or filter, or sync again.\";\n };\n const exportTranscripts = async () => {\n if (!client) return;\n setStatus(state.selectedFolderId ? \"Exporting folder transcripts…\" : \"Exporting transcripts…\", \"busy\");\n try {\n await client.exportTranscripts(\"text\", { folderId: state.selectedFolderId || void 0 });\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Export failed\", \"error\");\n }\n };\n const rerunJob = async (jobId) => {\n if (!client) return;\n setStatus(\"Rerunning export…\", \"busy\");\n try {\n await client.rerunExportJob(jobId);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Rerun failed\", \"error\");\n }\n };\n const selectedAutomationArtefact = () => state.automationArtefacts.find((artefact) => artefact.id === state.selectedAutomationArtefactId) || null;\n const resolveAutomationRun = async (id, decision) => {\n if (!client) return;\n setStatus(decision === \"approve\" ? \"Approving automation…\" : \"Rejecting automation…\", \"busy\");\n try {\n await client.resolveAutomationRun(id, decision);\n await refreshAll();\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Automation decision failed\", \"error\");\n }\n };\n const recoverProcessingIssue = async (id) => {\n if (!client) return;\n setStatus(\"Recovering processing issue…\", \"busy\");\n try {\n const result = await client.recoverProcessingIssue(id);\n await refreshAll();\n setStatus(result.runCount > 0 ? `Recovered ${result.issue.kind} and re-ran ${result.runCount} pipeline${result.runCount === 1 ? \"\" : \"s\"}` : `Recovered ${result.issue.kind}`, \"ok\");\n } catch (error) {\n setState(\"processingIssueError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Recovery failed\", \"error\");\n }\n };\n const selectAutomationArtefact = async (id) => {\n if (!client) return;\n try {\n const artefact = state.automationArtefacts.find((candidate) => candidate.id === id) ?? await client.getAutomationArtefact(id);\n setState(\"selectedAutomationArtefactId\", artefact.id);\n setState(\"automationArtefactDraftTitle\", artefact.structured.title);\n setState(\"automationArtefactDraftSummary\", artefact.structured.summary ?? \"\");\n setState(\"automationArtefactDraftMarkdown\", artefact.structured.markdown);\n setState(\"reviewNote\", \"\");\n if (artefact.meetingId !== state.selectedMeetingId) await loadMeeting(artefact.meetingId);\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unable to open artefact\", \"error\");\n }\n };\n const saveAutomationArtefact = async () => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(\"Saving artefact edits…\", \"busy\");\n try {\n const artefact = await client.updateAutomationArtefact(state.selectedAutomationArtefactId, {\n markdown: state.automationArtefactDraftMarkdown,\n note: state.reviewNote || void 0,\n summary: state.automationArtefactDraftSummary,\n title: state.automationArtefactDraftTitle\n });\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(\"Artefact updated\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact save failed\", \"error\");\n }\n };\n const resolveAutomationArtefact = async (decision) => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(decision === \"approve\" ? \"Approving artefact…\" : \"Rejecting artefact…\", \"busy\");\n try {\n const artefact = await client.resolveAutomationArtefact(state.selectedAutomationArtefactId, decision, { note: state.reviewNote || void 0 });\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(decision === \"approve\" ? \"Artefact approved\" : \"Artefact rejected\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact decision failed\", \"error\");\n }\n };\n const rerunAutomationArtefact = async () => {\n if (!client || !state.selectedAutomationArtefactId) return;\n setStatus(\"Rerunning artefact pipeline…\", \"busy\");\n try {\n const artefact = await client.rerunAutomationArtefact(state.selectedAutomationArtefactId);\n await loadAutomationArtefacts({\n preferredId: artefact.id,\n preferredMeetingId: artefact.meetingId\n });\n await loadAutomationRuns();\n setStatus(\"Artefact rerun complete\", \"ok\");\n } catch (error) {\n setState(\"automationArtefactError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Artefact rerun failed\", \"error\");\n }\n };\n const unlockServer = async () => {\n if (!state.serverPassword.trim()) {\n setStatus(\"Enter the server password\", \"error\");\n return;\n }\n setStatus(\"Unlocking server…\", \"busy\");\n try {\n await requestJson(\"/auth/unlock\", {\n body: JSON.stringify({ password: state.serverPassword }),\n headers: { \"content-type\": \"application/json\" },\n method: \"POST\"\n });\n setState(\"serverPassword\", \"\");\n setState(\"serverLocked\", false);\n await connectAndRefresh(true);\n } catch (error) {\n setState(\"detailError\", error instanceof Error ? error.message : String(error));\n setStatus(\"Unlock failed\", \"error\");\n }\n };\n const lockServer = async () => {\n try {\n await requestJson(\"/auth/lock\", { method: \"POST\" });\n } catch {}\n await detachClient();\n setState({\n appState: null,\n automationArtefactDraftMarkdown: \"\",\n automationArtefactDraftSummary: \"\",\n automationArtefactDraftTitle: \"\",\n automationArtefactError: \"\",\n automationArtefacts: [],\n automationRules: [],\n automationRuns: [],\n detailError: \"\",\n folderError: \"\",\n folders: [],\n harnessDirty: false,\n harnessError: \"\",\n harnessExplainEventKind: null,\n harnessExplanations: [],\n harnessTestResult: null,\n harnesses: [],\n listError: \"\",\n meetings: [],\n processingIssueError: \"\",\n processingIssues: [],\n reviewNote: \"\",\n selectedAutomationArtefactId: null,\n selectedFolderId: null,\n selectedHarnessId: 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) {\n automationPanelsHydrated = false;\n return;\n }\n if (automationPanelsHydrated) return;\n automationPanelsHydrated = true;\n loadAutomationRuns();\n loadAutomationArtefacts();\n loadProcessingIssues();\n });\n onMount(() => {\n const onKeyDown = (event) => {\n const target = event.target;\n if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return;\n const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);\n if (nextTab) setState(\"workspaceTab\", nextTab);\n };\n document.addEventListener(\"keydown\", onKeyDown);\n onCleanup(() => {\n document.removeEventListener(\"keydown\", onKeyDown);\n });\n if (!state.serverLocked) connectAndRefresh();\n });\n onCleanup(() => {\n detachClient();\n });\n const onboardingState = () => deriveOnboardingState({\n appState: state.appState,\n automationRuleCount: state.automationRules.length,\n harnesses: state.harnesses,\n meetingsLoadedCount: state.meetings.length\n });\n const showOnboarding = () => !state.serverLocked && !onboardingState().complete;\n return createComponent(Show, {\n get when() {\n return memo(() => !!!state.serverLocked)() && !showOnboarding();\n },\n get fallback() {\n return (() => {\n var _el$0 = _tmpl$2(), _el$1 = _el$0.firstChild;\n insert(_el$1, 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 }), null);\n insert(_el$1, 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$1, (() => {\n var _c$ = memo(() => !!!state.serverLocked);\n return () => _c$() ? createComponent(OnboardingPanel, {\n get apiKeyDraft() {\n return state.apiKeyDraft;\n },\n get auth() {\n return state.appState?.auth;\n },\n get folders() {\n return state.folders;\n },\n get meetingsLoadedCount() {\n return state.meetings.length;\n },\n onApiKeyDraftChange: (value) => {\n setState(\"apiKeyDraft\", value);\n },\n onCreateStarterPipeline: () => {\n createStarterPipeline();\n },\n onImportDesktopSession: () => {\n importDesktopSession();\n },\n onRunSync: () => {\n connectAndRefresh(true);\n },\n onSaveApiKey: () => {\n saveApiKey();\n },\n onSelectProvider: (provider) => {\n setState(\"preferredProvider\", provider);\n },\n get preferredProvider() {\n return state.preferredProvider;\n },\n get state() {\n return onboardingState();\n }\n }) : null;\n })(), null);\n insert(_el$1, (() => {\n var _c$2 = memo(() => !!(!state.serverLocked && state.detailError));\n return () => _c$2() ? (() => {\n var _el$10 = _tmpl$3(), _el$12 = _el$10.firstChild.firstChild;\n insert(_el$12, () => state.detailError);\n return _el$10;\n })() : null;\n })(), null);\n return _el$0;\n })();\n },\n get children() {\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(HarnessEditorPanel, {\n get dirty() {\n return state.harnessDirty;\n },\n get error() {\n return state.harnessError;\n },\n get explanations() {\n return state.harnessExplanations;\n },\n get explanationEventKind() {\n return state.harnessExplainEventKind;\n },\n get harnesses() {\n return state.harnesses;\n },\n onChange: updateHarness,\n onDuplicate: duplicateHarness,\n onNew: createHarness,\n onReload: () => {\n reloadHarnesses();\n },\n onRemove: removeHarness,\n onSave: () => {\n saveHarnesses();\n },\n onSelect: (id) => {\n setState(\"selectedHarnessId\", id);\n setState(\"harnessTestResult\", null);\n },\n onTest: () => {\n testHarness();\n },\n onTestKindChange: (kind) => {\n setState(\"harnessTestKind\", kind);\n },\n get selectedHarness() {\n return selectedHarness();\n },\n get selectedHarnessId() {\n return state.selectedHarnessId;\n },\n get selectedMeeting() {\n return state.selectedMeeting;\n },\n get testKind() {\n return state.harnessTestKind;\n },\n get testResult() {\n return state.harnessTestResult;\n }\n }), null);\n insert(_el$3, createComponent(ExportJobsPanel, {\n get jobs() {\n return state.appState?.exports.jobs || [];\n },\n onRerun: (jobId) => {\n rerunJob(jobId);\n }\n }), null);\n insert(_el$3, createComponent(AutomationRunsPanel, {\n onApprove: (runId) => {\n resolveAutomationRun(runId, \"approve\");\n },\n onReject: (runId) => {\n resolveAutomationRun(runId, \"reject\");\n },\n get runs() {\n return state.automationRuns;\n }\n }), null);\n insert(_el$3, createComponent(ProcessingIssuesPanel, {\n get issues() {\n return state.processingIssues;\n },\n onOpenMeeting: (meetingId) => {\n loadMeeting(meetingId);\n },\n onRecover: (issueId) => {\n recoverProcessingIssue(issueId);\n }\n }), null);\n insert(_el$3, createComponent(AutomationArtefactsPanel, {\n get artefacts() {\n return state.automationArtefacts;\n },\n onSelect: (artefactId) => {\n selectAutomationArtefact(artefactId);\n },\n get selectedArtefactId() {\n return state.selectedAutomationArtefactId;\n }\n }), null);\n insert(_el$3, createComponent(ArtefactReviewPanel, {\n get artefact() {\n return selectedAutomationArtefact();\n },\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get draftMarkdown() {\n return state.automationArtefactDraftMarkdown;\n },\n get draftSummary() {\n return state.automationArtefactDraftSummary;\n },\n get draftTitle() {\n return state.automationArtefactDraftTitle;\n },\n get error() {\n return state.automationArtefactError;\n },\n onApprove: () => {\n resolveAutomationArtefact(\"approve\");\n },\n onDraftMarkdownChange: (value) => {\n setState(\"automationArtefactDraftMarkdown\", value);\n },\n onDraftSummaryChange: (value) => {\n setState(\"automationArtefactDraftSummary\", value);\n },\n onDraftTitleChange: (value) => {\n setState(\"automationArtefactDraftTitle\", value);\n },\n onReject: () => {\n resolveAutomationArtefact(\"reject\");\n },\n onRerun: () => {\n rerunAutomationArtefact();\n },\n onReviewNoteChange: (value) => {\n setState(\"reviewNote\", value);\n },\n onSave: () => {\n saveAutomationArtefact();\n },\n get reviewNote() {\n return state.reviewNote;\n }\n }), null);\n insert(_el$3, createComponent(Workspace, {\n get bundle() {\n return state.selectedMeetingBundle;\n },\n get detailError() {\n return state.detailError;\n },\n onSelectTab: (tab) => {\n setState(\"workspaceTab\", tab);\n },\n get selectedMeeting() {\n return state.selectedMeeting;\n },\n get tab() {\n return state.workspaceTab;\n }\n }), null);\n return _el$;\n }\n });\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";
9547
9733
  //#endregion
9548
9734
  //#region src/web/assets.ts
9549
9735
  const granolaWebAssetPaths = {
@@ -9667,6 +9853,10 @@ function harnessesFromBody(value) {
9667
9853
  if (!Array.isArray(value)) throw new Error("harnesses must be an array");
9668
9854
  return value;
9669
9855
  }
9856
+ function rulesFromBody(value) {
9857
+ if (!Array.isArray(value)) throw new Error("rules must be an array");
9858
+ return value;
9859
+ }
9670
9860
  function evaluationCasesFromBody(value) {
9671
9861
  if (!Array.isArray(value)) throw new Error("evaluation cases must be an array");
9672
9862
  return value;
@@ -9943,6 +10133,11 @@ async function startGranolaServer(app, options = {}) {
9943
10133
  sendJson(response, await app.listAutomationRules(), { headers: originHeaders });
9944
10134
  return;
9945
10135
  }
10136
+ if (method === "POST" && path === granolaTransportPaths.automationRules) {
10137
+ const body = await readJsonBody(request);
10138
+ sendJson(response, await app.saveAutomationRules(rulesFromBody(body.rules)), { headers: originHeaders });
10139
+ return;
10140
+ }
9946
10141
  if (method === "GET" && path === granolaTransportPaths.automationMatches) {
9947
10142
  sendJson(response, await app.listAutomationMatches({ limit: parseInteger(url.searchParams.get("limit")) }), { headers: originHeaders });
9948
10143
  return;
@@ -10753,6 +10948,229 @@ const searchCommand = {
10753
10948
  }
10754
10949
  };
10755
10950
  //#endregion
10951
+ //#region src/commands/service.ts
10952
+ function serviceHelp() {
10953
+ return `Granola service
10954
+
10955
+ Usage:
10956
+ granola service start [options]
10957
+ granola service status
10958
+ granola service stop
10959
+
10960
+ Options:
10961
+ --network <mode> Network mode: local or lan (default: local)
10962
+ --hostname <value> Hostname to bind (overrides network default)
10963
+ --port <value> Port to bind (default: 0 for any available port)
10964
+ --password <value> Optional server password for API and browser access
10965
+ --sync-interval <value> Background sync interval, e.g. 60s or 5m (default: 60s)
10966
+ --no-sync Disable the background sync loop
10967
+ --trusted-origins <v> Comma-separated extra browser origins to trust
10968
+ --cache <path> Path to Granola cache JSON
10969
+ --timeout <value> Request timeout, e.g. 2m, 30s, 120000 (default: 2m)
10970
+ --supabase <path> Path to supabase.json
10971
+ --debug Enable debug logging
10972
+ --config <path> Path to .granola.toml
10973
+ -h, --help Show help
10974
+ `;
10975
+ }
10976
+ function appendFlag(args, name, value) {
10977
+ if (value === void 0 || value === false) return;
10978
+ args.push(`--${name}`);
10979
+ if (typeof value === "string") args.push(value);
10980
+ }
10981
+ function serialiseServiceFlags(commandFlags, globalFlags) {
10982
+ const args = [];
10983
+ appendFlag(args, "network", commandFlags.network);
10984
+ appendFlag(args, "hostname", commandFlags.hostname);
10985
+ appendFlag(args, "port", commandFlags.port);
10986
+ appendFlag(args, "password", commandFlags.password);
10987
+ appendFlag(args, "sync-interval", commandFlags["sync-interval"]);
10988
+ appendFlag(args, "no-sync", commandFlags["no-sync"]);
10989
+ appendFlag(args, "trusted-origins", commandFlags["trusted-origins"]);
10990
+ appendFlag(args, "cache", commandFlags.cache);
10991
+ appendFlag(args, "timeout", commandFlags.timeout);
10992
+ appendFlag(args, "config", globalFlags.config);
10993
+ appendFlag(args, "rules", globalFlags.rules);
10994
+ appendFlag(args, "supabase", globalFlags.supabase);
10995
+ appendFlag(args, "debug", globalFlags.debug);
10996
+ const env = { ...process.env };
10997
+ if (typeof globalFlags["api-key"] === "string" && globalFlags["api-key"].trim()) env.GRANOLA_API_KEY = globalFlags["api-key"].trim();
10998
+ return {
10999
+ args,
11000
+ env
11001
+ };
11002
+ }
11003
+ function printServiceStatus(status) {
11004
+ switch (status.kind) {
11005
+ case "running":
11006
+ console.log(`Granola Toolkit service is running on ${status.record?.url}`);
11007
+ console.log(`PID: ${status.record?.pid}`);
11008
+ console.log(`Log: ${status.record?.logFile}`);
11009
+ console.log(status.record?.syncEnabled ? `Background sync: enabled (${status.record.syncIntervalMs}ms)` : "Background sync: disabled");
11010
+ if (status.record?.passwordProtected) console.log("Password protection: enabled");
11011
+ return;
11012
+ case "stale":
11013
+ console.log("Granola Toolkit service metadata exists, but the process is not running.");
11014
+ return;
11015
+ case "unreachable":
11016
+ console.log("Granola Toolkit service metadata exists, but the server did not respond.");
11017
+ if (status.record?.url) console.log(`Last known URL: ${status.record.url}`);
11018
+ if (status.error) console.log(`Health check: ${status.error.message}`);
11019
+ return;
11020
+ case "invalid":
11021
+ console.log("Granola Toolkit service metadata is invalid.");
11022
+ return;
11023
+ default: console.log("Granola Toolkit service is not running.");
11024
+ }
11025
+ }
11026
+ function printServiceRunBanner(record) {
11027
+ console.log(`Granola Toolkit background service listening on ${record.url}`);
11028
+ console.log(record.syncEnabled ? `Background sync: enabled (${record.syncIntervalMs}ms)` : "Background sync: disabled");
11029
+ if (record.passwordProtected) console.log("Password protection: enabled");
11030
+ }
11031
+ async function runServiceProcess(config, commandFlags) {
11032
+ const hostname = resolveServerHostname(parseNetworkMode(commandFlags.network), commandFlags.hostname);
11033
+ const port = parsePort(commandFlags.port);
11034
+ const password = typeof commandFlags.password === "string" && commandFlags.password.trim() ? commandFlags.password.trim() : void 0;
11035
+ const syncEnabledForService = syncEnabled(commandFlags);
11036
+ const syncIntervalMs = parseSyncInterval(commandFlags["sync-interval"]);
11037
+ const trustedOrigins = parseTrustedOrigins(commandFlags["trusted-origins"]);
11038
+ const { logFile, serviceStateFile } = defaultGranolaServiceRecord();
11039
+ const app = await createGranolaApp(config, { surface: "server" });
11040
+ const server = await startGranolaServer(app, {
11041
+ enableWebClient: true,
11042
+ hostname,
11043
+ port,
11044
+ security: {
11045
+ password,
11046
+ trustedOrigins
11047
+ }
11048
+ });
11049
+ const syncLoop = syncEnabledForService ? createGranolaSyncLoop({
11050
+ app,
11051
+ intervalMs: syncIntervalMs,
11052
+ logger: console
11053
+ }) : void 0;
11054
+ const record = {
11055
+ hostname: server.hostname,
11056
+ logFile,
11057
+ passwordProtected: Boolean(password),
11058
+ pid: process.pid,
11059
+ port: server.port,
11060
+ protocolVersion: 2,
11061
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
11062
+ syncEnabled: syncEnabledForService,
11063
+ syncIntervalMs,
11064
+ url: server.url.href
11065
+ };
11066
+ await writeGranolaServiceRecord(record, serviceStateFile);
11067
+ syncLoop?.start();
11068
+ printServiceRunBanner(record);
11069
+ await waitForShutdown(async () => {
11070
+ await syncLoop?.stop();
11071
+ await server.close();
11072
+ await removeGranolaServiceRecord(serviceStateFile);
11073
+ });
11074
+ return 0;
11075
+ }
11076
+ const serviceCommand = {
11077
+ description: "Run and manage the Granola Toolkit background service",
11078
+ flags: {
11079
+ cache: { type: "string" },
11080
+ help: { type: "boolean" },
11081
+ hostname: { type: "string" },
11082
+ network: { type: "string" },
11083
+ "no-sync": { type: "boolean" },
11084
+ password: { type: "string" },
11085
+ port: { type: "string" },
11086
+ "sync-interval": { type: "string" },
11087
+ timeout: { type: "string" },
11088
+ "trusted-origins": { type: "string" }
11089
+ },
11090
+ help: serviceHelp,
11091
+ name: "service",
11092
+ async run({ commandArgs, commandFlags, globalFlags }) {
11093
+ const action = commandArgs[0];
11094
+ if (!action) {
11095
+ console.log(serviceHelp());
11096
+ return 1;
11097
+ }
11098
+ if (action === "status") {
11099
+ const status = await inspectGranolaService();
11100
+ printServiceStatus(status);
11101
+ return status.kind === "running" ? 0 : 1;
11102
+ }
11103
+ if (action === "stop") {
11104
+ const status = await inspectGranolaService({ cleanupStale: false });
11105
+ if (status.kind === "missing" || status.kind === "stale") {
11106
+ printServiceStatus(status);
11107
+ await removeGranolaServiceRecord(defaultGranolaServiceRecord().serviceStateFile);
11108
+ return 0;
11109
+ }
11110
+ if (status.kind !== "running" || !status.record) {
11111
+ printServiceStatus(status);
11112
+ return 1;
11113
+ }
11114
+ process.kill(status.record.pid, "SIGTERM");
11115
+ const startedAt = Date.now();
11116
+ while (Date.now() - startedAt <= 1e4) {
11117
+ const nextStatus = await inspectGranolaService();
11118
+ if (nextStatus.kind === "missing" || nextStatus.kind === "stale") {
11119
+ console.log("Granola Toolkit service stopped.");
11120
+ return 0;
11121
+ }
11122
+ await new Promise((resolve) => {
11123
+ setTimeout(resolve, 200);
11124
+ });
11125
+ }
11126
+ throw new Error("service did not stop within 10s");
11127
+ }
11128
+ if (action === "run") {
11129
+ const config = await loadConfig({
11130
+ globalFlags,
11131
+ subcommandFlags: commandFlags
11132
+ });
11133
+ debug(config.debug, "using config", config.configFileUsed ?? "(none)");
11134
+ debug(config.debug, "supabase", config.supabase);
11135
+ debug(config.debug, "cacheFile", config.transcripts.cacheFile || "(none)");
11136
+ debug(config.debug, "timeoutMs", config.notes.timeoutMs);
11137
+ return await runServiceProcess(config, commandFlags);
11138
+ }
11139
+ if (action === "start") {
11140
+ const existing = await discoverGranolaService();
11141
+ if (existing) {
11142
+ console.log(`Granola Toolkit service is already running on ${existing.url}`);
11143
+ console.log(`PID: ${existing.pid}`);
11144
+ return 0;
11145
+ }
11146
+ await loadConfig({
11147
+ env: typeof globalFlags["api-key"] === "string" && globalFlags["api-key"].trim() ? {
11148
+ ...process.env,
11149
+ GRANOLA_API_KEY: globalFlags["api-key"].trim()
11150
+ } : process.env,
11151
+ globalFlags,
11152
+ subcommandFlags: commandFlags
11153
+ });
11154
+ const { args, env } = serialiseServiceFlags(commandFlags, globalFlags);
11155
+ await spawnGranolaServiceProcess({
11156
+ commandArgs: args,
11157
+ env,
11158
+ logFile: defaultGranolaServiceRecord().logFile
11159
+ });
11160
+ const status = await waitForGranolaService();
11161
+ if (status.kind !== "running" || !status.record) {
11162
+ const logTail = await readGranolaServiceLogTail();
11163
+ throw new Error(logTail ? `service failed to start cleanly:\n${logTail}` : "service failed to start cleanly");
11164
+ }
11165
+ console.log(`Granola Toolkit service started on ${status.record.url}`);
11166
+ console.log(`PID: ${status.record.pid}`);
11167
+ console.log(`Log: ${status.record.logFile}`);
11168
+ return 0;
11169
+ }
11170
+ throw new Error(`unknown service command: ${action}`);
11171
+ }
11172
+ };
11173
+ //#endregion
10756
11174
  //#region src/commands/serve.ts
10757
11175
  function serveHelp() {
10758
11176
  return `Granola serve
@@ -11097,6 +11515,11 @@ Options:
11097
11515
  -h, --help Show help
11098
11516
  `;
11099
11517
  }
11518
+ function canReuseRunningService(commandFlags, globalFlags) {
11519
+ const hasRuntimeOverride = commandFlags.cache !== void 0 || commandFlags.hostname !== void 0 || commandFlags.network !== void 0 || commandFlags["no-sync"] !== void 0 || commandFlags.password !== void 0 || commandFlags.port !== void 0 || commandFlags["sync-interval"] !== void 0 || commandFlags.timeout !== void 0 || commandFlags["trusted-origins"] !== void 0;
11520
+ const hasGlobalOverride = globalFlags["api-key"] !== void 0 || globalFlags.config !== void 0 || globalFlags.rules !== void 0 || globalFlags.supabase !== void 0;
11521
+ return !hasRuntimeOverride && !hasGlobalOverride;
11522
+ }
11100
11523
  //#endregion
11101
11524
  //#region src/commands/index.ts
11102
11525
  const commands = [
@@ -11109,6 +11532,7 @@ const commands = [
11109
11532
  meetingCommand,
11110
11533
  notesCommand,
11111
11534
  searchCommand,
11535
+ serviceCommand,
11112
11536
  serveCommand,
11113
11537
  syncCommand,
11114
11538
  tuiCommand,
@@ -11132,6 +11556,24 @@ const commands = [
11132
11556
  help: webHelp,
11133
11557
  name: "web",
11134
11558
  async run({ commandFlags, globalFlags }) {
11559
+ const options = resolveGranolaWebWorkspaceOptions(commandFlags);
11560
+ const targetMeetingId = typeof commandFlags.meeting === "string" && commandFlags.meeting.trim() ? commandFlags.meeting.trim() : void 0;
11561
+ if (canReuseRunningService(commandFlags, globalFlags)) {
11562
+ const runningService = await discoverGranolaService();
11563
+ if (runningService) {
11564
+ const targetUrl = targetMeetingId ? buildGranolaMeetingUrl(new URL(runningService.url), targetMeetingId) : new URL(runningService.url);
11565
+ console.log(`Granola Toolkit web workspace already running on ${runningService.url}`);
11566
+ if (targetUrl.href !== runningService.url) console.log(`Focused meeting URL: ${targetUrl.href}`);
11567
+ if (options.openBrowser) try {
11568
+ await openExternalUrl(targetUrl);
11569
+ } catch (error) {
11570
+ const message = error instanceof Error ? error.message : String(error);
11571
+ console.error(`failed to open browser automatically: ${message}`);
11572
+ console.error(`open ${targetUrl.href} manually`);
11573
+ }
11574
+ return 0;
11575
+ }
11576
+ }
11135
11577
  const config = await loadConfig({
11136
11578
  globalFlags,
11137
11579
  subcommandFlags: commandFlags
@@ -11140,10 +11582,7 @@ const commands = [
11140
11582
  debug(config.debug, "supabase", config.supabase);
11141
11583
  debug(config.debug, "cacheFile", config.transcripts.cacheFile || "(none)");
11142
11584
  debug(config.debug, "timeoutMs", config.notes.timeoutMs);
11143
- const app = await createGranolaApp(config, { surface: "web" });
11144
- const options = resolveGranolaWebWorkspaceOptions(commandFlags);
11145
- const targetMeetingId = typeof commandFlags.meeting === "string" && commandFlags.meeting.trim() ? commandFlags.meeting.trim() : void 0;
11146
- return await runGranolaWebWorkspace(app, {
11585
+ return await runGranolaWebWorkspace(await createGranolaApp(config, { surface: "web" }), {
11147
11586
  ...options,
11148
11587
  targetMeetingId
11149
11588
  });
@@ -11242,6 +11681,7 @@ Examples:
11242
11681
  granola attach http://127.0.0.1:4123
11243
11682
  granola folder list
11244
11683
  granola init --provider openrouter
11684
+ granola service start
11245
11685
  granola sync
11246
11686
  granola notes --supabase "${granolaSupabaseCandidates()[0] ?? "/path/to/supabase.json"}"
11247
11687
  granola transcripts --cache "${granolaCacheCandidates()[0] ?? "/path/to/cache-v3.json"}"