granola-toolkit 0.57.0 → 0.59.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 -7
  2. package/dist/cli.js +337 -20
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { Input, ProcessTerminal, TUI, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
3
3
  import { existsSync } from "node:fs";
4
- import { appendFile, mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
4
+ import { access, appendFile, mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
5
5
  import { homedir, platform, tmpdir } from "node:os";
6
- import { basename, dirname, extname, join, resolve } from "node:path";
6
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
7
7
  import { NodeHtmlMarkdown } from "node-html-markdown";
8
8
  import { createHash, randomUUID } from "node:crypto";
9
9
  import { execFile, spawn } from "node:child_process";
@@ -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);
@@ -577,7 +584,7 @@ function latestDocumentTimestamp(document) {
577
584
  });
578
585
  return candidates[0] ?? document.updatedAt;
579
586
  }
580
- async function writeTextFile(filePath, content) {
587
+ async function writeTextFile$1(filePath, content) {
581
588
  await mkdir(dirname(filePath), { recursive: true });
582
589
  await writeFile(filePath, content, "utf8");
583
590
  }
@@ -866,7 +873,7 @@ async function syncManagedExports({ items, kind, onProgress, outputDir }) {
866
873
  const filePath = join(outputDir, plan.fileName);
867
874
  const shouldWrite = !plan.existing || plan.existing.contentHash !== plan.contentHash || plan.existing.fileName !== plan.fileName || !await fileExists(filePath);
868
875
  if (shouldWrite) {
869
- await writeTextFile(filePath, plan.content);
876
+ await writeTextFile$1(filePath, plan.content);
870
877
  written += 1;
871
878
  }
872
879
  const nextEntry = {
@@ -904,7 +911,7 @@ async function syncManagedExports({ items, kind, onProgress, outputDir }) {
904
911
  }, null, 2)}\n`;
905
912
  const statePath = exportStatePath(outputDir, kind);
906
913
  const existingState = await fileExists(statePath) ? await readUtf8(statePath) : void 0;
907
- if (stateChanged || existingState !== serialisedState) await writeTextFile(statePath, serialisedState);
914
+ if (stateChanged || existingState !== serialisedState) await writeTextFile$1(statePath, serialisedState);
908
915
  return written;
909
916
  }
910
917
  //#endregion
@@ -3071,6 +3078,18 @@ function cloneHarness(harness) {
3071
3078
  } : void 0
3072
3079
  };
3073
3080
  }
3081
+ function resolveRelativeHarnessPath(baseDirectory, value) {
3082
+ if (!value?.trim()) return value;
3083
+ return isAbsolute(value) ? value : resolve(baseDirectory, value);
3084
+ }
3085
+ function normaliseFileHarness(baseDirectory, harness) {
3086
+ const cloned = cloneHarness(harness);
3087
+ const cwd = resolveRelativeHarnessPath(baseDirectory, cloned.cwd) ?? baseDirectory;
3088
+ return {
3089
+ ...cloned,
3090
+ cwd
3091
+ };
3092
+ }
3074
3093
  function includesIgnoreCase$1(candidate, values) {
3075
3094
  const lowerCandidate = candidate.toLowerCase();
3076
3095
  return values.some((value) => lowerCandidate.includes(value.toLowerCase()));
@@ -3210,7 +3229,7 @@ var FileAgentHarnessStore = class {
3210
3229
  async readHarnesses() {
3211
3230
  try {
3212
3231
  const parsed = parseJsonString(await readFile(this.filePath, "utf8"));
3213
- return (Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.harnesses) ? parsed.harnesses : []).map((harness) => parseHarness(harness)).filter((harness) => Boolean(harness)).map(cloneHarness);
3232
+ return (Array.isArray(parsed) ? parsed : parsed && typeof parsed === "object" && Array.isArray(parsed.harnesses) ? parsed.harnesses : []).map((harness) => parseHarness(harness)).filter((harness) => Boolean(harness)).map((harness) => normaliseFileHarness(dirname(this.filePath), harness));
3214
3233
  } catch {
3215
3234
  return [];
3216
3235
  }
@@ -4752,6 +4771,10 @@ var FileAutomationRuleStore = class {
4752
4771
  return [];
4753
4772
  }
4754
4773
  }
4774
+ async writeRules(rules) {
4775
+ await mkdir(dirname(this.filePath), { recursive: true });
4776
+ await writeFile(this.filePath, `${JSON.stringify({ rules }, null, 2)}\n`, "utf8");
4777
+ }
4755
4778
  };
4756
4779
  function defaultAutomationRulesFilePath() {
4757
4780
  return defaultGranolaToolkitPersistenceLayout().automationRulesFile;
@@ -7301,6 +7324,15 @@ var GranolaApp = class {
7301
7324
  this.setUiState({ view: "idle" });
7302
7325
  return { rules: rules.map((rule) => this.cloneAutomationRule(rule)) };
7303
7326
  }
7327
+ async saveAutomationRules(rules) {
7328
+ if (!this.deps.automationRuleStore) throw new Error("automation rule store is not configured");
7329
+ await this.deps.automationRuleStore.writeRules(rules.map((rule) => this.cloneAutomationRule(rule)));
7330
+ this.#automationRules = rules.map((rule) => this.cloneAutomationRule(rule));
7331
+ this.refreshAutomationState();
7332
+ this.setUiState({ view: "idle" });
7333
+ this.emitStateUpdate();
7334
+ return { rules: this.#automationRules.map((rule) => this.cloneAutomationRule(rule)) };
7335
+ }
7304
7336
  async listAutomationMatches(options = {}) {
7305
7337
  const limit = options.limit ?? 20;
7306
7338
  const matches = this.deps.automationMatchStore ? await this.deps.automationMatchStore.readMatches(limit) : this.#automationMatches.slice(-limit).reverse();
@@ -7761,7 +7793,7 @@ var GranolaApp = class {
7761
7793
  const filePath = resolveWriteFilePath(action, payload);
7762
7794
  if (existsSync(filePath) && action.overwrite === false) throw new Error(`automation write-file target already exists: ${filePath}`);
7763
7795
  const content = renderWriteFileContent(action, payload);
7764
- await writeTextFile(filePath, content);
7796
+ await writeTextFile$1(filePath, content);
7765
7797
  return {
7766
7798
  bytes: Buffer.byteLength(content, "utf8"),
7767
7799
  filePath,
@@ -7802,7 +7834,7 @@ var GranolaApp = class {
7802
7834
  const meetingTitle = match.title || context.artefact.structured.title;
7803
7835
  const folderName = match.folders[0]?.name;
7804
7836
  const filePath = join(target.folderSubdirectories && folderName ? join(target.outputDir, sanitiseFilename(folderName, "folder")) : target.outputDir, sanitiseFilename(target.filenameTemplate?.trim() ? target.filenameTemplate.replaceAll("{{meeting.title}}", meetingTitle).replaceAll("{{artefact.kind}}", context.artefact.kind).replaceAll("{{artefact.title}}", context.artefact.structured.title) : `${sanitiseFilename(`${meetingTitle}-${context.artefact.kind}`)}.md`, "meeting.md"));
7805
- await writeTextFile(filePath, `${this.buildPkmFrontmatter(target, context.artefact, match)}${context.artefact.structured.markdown.trim()}\n`);
7837
+ await writeTextFile$1(filePath, `${this.buildPkmFrontmatter(target, context.artefact, match)}${context.artefact.structured.markdown.trim()}\n`);
7806
7838
  return {
7807
7839
  filePath,
7808
7840
  targetId: target.id
@@ -8465,9 +8497,15 @@ function envFlag(value) {
8465
8497
  if (/^(1|true|yes|on)$/i.test(value)) return true;
8466
8498
  if (/^(0|false|no|off)$/i.test(value)) return false;
8467
8499
  }
8500
+ function resolveConfigPath(configPath, value) {
8501
+ if (!value?.trim()) return value;
8502
+ if (!configPath || isAbsolute(value)) return value;
8503
+ return resolve(dirname(configPath), value);
8504
+ }
8468
8505
  async function loadConfig(options) {
8469
8506
  const env = options.env ?? process.env;
8470
8507
  const config = await loadTomlConfig(pickString(options.globalFlags.config));
8508
+ const configPath = config.path;
8471
8509
  const configValues = config.values;
8472
8510
  const defaultSupabase = firstExistingPath(granolaSupabaseCandidates());
8473
8511
  const defaultCache = firstExistingPath(granolaCacheCandidates());
@@ -8475,9 +8513,9 @@ async function loadConfig(options) {
8475
8513
  const timeoutValue = pickString(options.subcommandFlags.timeout) ?? pickString(env.TIMEOUT) ?? pickString(configValues.timeout) ?? "2m";
8476
8514
  return {
8477
8515
  automation: {
8478
- artefactsFile: pickString(env.GRANOLA_AUTOMATION_ARTEFACTS_FILE) ?? pickString(configValues["automation-artefacts-file"]) ?? pickString(configValues.automationArtefactsFile) ?? defaultGranolaToolkitPersistenceLayout().automationArtefactsFile,
8479
- pkmTargetsFile: pickString(env.GRANOLA_PKM_TARGETS_FILE) ?? pickString(configValues["pkm-targets-file"]) ?? pickString(configValues.pkmTargetsFile) ?? defaultGranolaToolkitPersistenceLayout().pkmTargetsFile,
8480
- rulesFile: pickString(options.globalFlags.rules) ?? pickString(env.GRANOLA_AUTOMATION_RULES_FILE) ?? pickString(configValues["automation-rules-file"]) ?? pickString(configValues.automationRulesFile) ?? defaultGranolaToolkitPersistenceLayout().automationRulesFile
8516
+ artefactsFile: pickString(env.GRANOLA_AUTOMATION_ARTEFACTS_FILE) ?? resolveConfigPath(configPath, pickString(configValues["automation-artefacts-file"]) ?? pickString(configValues.automationArtefactsFile)) ?? defaultGranolaToolkitPersistenceLayout().automationArtefactsFile,
8517
+ pkmTargetsFile: pickString(env.GRANOLA_PKM_TARGETS_FILE) ?? resolveConfigPath(configPath, pickString(configValues["pkm-targets-file"]) ?? pickString(configValues.pkmTargetsFile)) ?? defaultGranolaToolkitPersistenceLayout().pkmTargetsFile,
8518
+ rulesFile: pickString(options.globalFlags.rules) ?? pickString(env.GRANOLA_AUTOMATION_RULES_FILE) ?? resolveConfigPath(configPath, pickString(configValues["automation-rules-file"]) ?? pickString(configValues.automationRulesFile)) ?? defaultGranolaToolkitPersistenceLayout().automationRulesFile
8481
8519
  },
8482
8520
  agents: {
8483
8521
  codexCommand: pickString(env.GRANOLA_CODEX_COMMAND) ?? pickString(configValues["codex-command"]) ?? pickString(configValues.codexCommand) ?? "codex",
@@ -8487,7 +8525,7 @@ async function loadConfig(options) {
8487
8525
  return value === "codex" || value === "openai" || value === "openrouter" ? value : void 0;
8488
8526
  })(),
8489
8527
  dryRun: envFlag(env.GRANOLA_AGENT_DRY_RUN) ?? pickBoolean(configValues["agent-dry-run"]) ?? pickBoolean(configValues.agentDryRun) ?? false,
8490
- harnessesFile: pickString(env.GRANOLA_AGENT_HARNESSES_FILE) ?? pickString(configValues["agent-harnesses-file"]) ?? pickString(configValues.agentHarnessesFile) ?? defaultGranolaToolkitPersistenceLayout().agentHarnessesFile,
8528
+ harnessesFile: pickString(env.GRANOLA_AGENT_HARNESSES_FILE) ?? resolveConfigPath(configPath, pickString(configValues["agent-harnesses-file"]) ?? pickString(configValues.agentHarnessesFile)) ?? defaultGranolaToolkitPersistenceLayout().agentHarnessesFile,
8491
8529
  maxRetries: pickNumber(env.GRANOLA_AGENT_MAX_RETRIES) ?? pickNumber(configValues["agent-max-retries"]) ?? pickNumber(configValues.agentMaxRetries) ?? 2,
8492
8530
  openaiBaseUrl: pickString(env.GRANOLA_OPENAI_BASE_URL) ?? pickString(env.OPENAI_BASE_URL) ?? pickString(configValues["openai-base-url"]) ?? pickString(configValues.openaiBaseUrl) ?? "https://api.openai.com/v1",
8493
8531
  openrouterBaseUrl: pickString(env.GRANOLA_OPENROUTER_BASE_URL) ?? pickString(env.OPENROUTER_BASE_URL) ?? pickString(configValues["openrouter-base-url"]) ?? pickString(configValues.openrouterBaseUrl) ?? "https://openrouter.ai/api/v1",
@@ -8497,13 +8535,13 @@ async function loadConfig(options) {
8497
8535
  configFileUsed: config.path,
8498
8536
  debug: pickBoolean(options.globalFlags.debug) ?? envFlag(env.DEBUG_MODE) ?? pickBoolean(configValues.debug) ?? false,
8499
8537
  notes: {
8500
- output: pickString(options.subcommandFlags.output) ?? pickString(env.OUTPUT) ?? pickString(configValues.output) ?? "./notes",
8538
+ output: pickString(options.subcommandFlags.output) ?? pickString(env.OUTPUT) ?? resolveConfigPath(configPath, pickString(configValues.output)) ?? "./notes",
8501
8539
  timeoutMs: parseDuration(timeoutValue)
8502
8540
  },
8503
- supabase: pickString(options.globalFlags.supabase) ?? pickString(env.SUPABASE_FILE) ?? pickString(configValues.supabase) ?? defaultSupabase,
8541
+ supabase: pickString(options.globalFlags.supabase) ?? pickString(env.SUPABASE_FILE) ?? resolveConfigPath(configPath, pickString(configValues.supabase)) ?? defaultSupabase,
8504
8542
  transcripts: {
8505
- cacheFile: pickString(options.subcommandFlags.cache) ?? pickString(env.CACHE_FILE) ?? pickString(configValues["cache-file"]) ?? pickString(configValues.cacheFile) ?? defaultCache ?? "",
8506
- output: pickString(options.subcommandFlags.output) ?? pickString(env.TRANSCRIPT_OUTPUT) ?? pickString(configValues["transcript-output"]) ?? pickString(configValues.transcriptOutput) ?? "./transcripts"
8543
+ cacheFile: pickString(options.subcommandFlags.cache) ?? pickString(env.CACHE_FILE) ?? resolveConfigPath(configPath, pickString(configValues["cache-file"]) ?? pickString(configValues.cacheFile)) ?? defaultCache ?? "",
8544
+ output: pickString(options.subcommandFlags.output) ?? pickString(env.TRANSCRIPT_OUTPUT) ?? resolveConfigPath(configPath, pickString(configValues["transcript-output"]) ?? pickString(configValues.transcriptOutput)) ?? "./transcripts"
8507
8545
  }
8508
8546
  };
8509
8547
  }
@@ -8724,7 +8762,7 @@ function parseHarnessIds(value) {
8724
8762
  const ids = value.split(",").map((item) => item.trim()).filter(Boolean);
8725
8763
  return ids.length > 0 ? ids : void 0;
8726
8764
  }
8727
- function parseProvider(value) {
8765
+ function parseProvider$1(value) {
8728
8766
  switch (value) {
8729
8767
  case void 0: return;
8730
8768
  case "codex":
@@ -8873,7 +8911,7 @@ const automationCommand = {
8873
8911
  harnessIds: parseHarnessIds(commandFlags.harness),
8874
8912
  kind: parseArtefactKind(commandFlags.kind) ?? "notes",
8875
8913
  model: typeof commandFlags.model === "string" ? commandFlags.model.trim() : void 0,
8876
- provider: parseProvider(commandFlags.provider)
8914
+ provider: parseProvider$1(commandFlags.provider)
8877
8915
  });
8878
8916
  console.log(renderEvaluations(result, format).trimEnd());
8879
8917
  return 0;
@@ -9229,6 +9267,274 @@ async function view$1(query, commandFlags, globalFlags) {
9229
9267
  return 0;
9230
9268
  }
9231
9269
  //#endregion
9270
+ //#region src/agent-defaults.ts
9271
+ const defaultGranolaAgentModels = {
9272
+ codex: "gpt-5-codex",
9273
+ openai: "gpt-5-mini",
9274
+ openrouter: "openai/gpt-5-mini"
9275
+ };
9276
+ function defaultGranolaAgentModel(provider, explicitModel) {
9277
+ return explicitModel?.trim() || defaultGranolaAgentModels[provider];
9278
+ }
9279
+ //#endregion
9280
+ //#region src/init.ts
9281
+ const TEAM_PROMPT = `# Team Notes Harness
9282
+
9283
+ Turn this meeting into concise internal notes for the team.
9284
+
9285
+ Requirements:
9286
+
9287
+ - lead with the most important outcome, not a meeting recap
9288
+ - capture decisions, blockers, risks, and follow-ups
9289
+ - keep action items concrete and assign them to named people when possible
9290
+ - prefer the canonical participant names from the meeting context over vague owners like "you"
9291
+ - call out anything that needs manual follow-up if the transcript is ambiguous
9292
+ - keep the tone direct and useful for people who did not attend
9293
+ `;
9294
+ const CUSTOMER_PROMPT = `# Customer Follow-Up Harness
9295
+
9296
+ Turn this meeting into customer-facing follow-up notes for the internal team.
9297
+
9298
+ Requirements:
9299
+
9300
+ - summarise the customer's goals, requests, and concerns
9301
+ - capture commitments we made and anything we still owe them
9302
+ - highlight product feedback, risks, blockers, and dates
9303
+ - prefer named owners for follow-up actions
9304
+ - separate confirmed facts from assumptions when the transcript is unclear
9305
+ - optimise for a quick post-call handoff to sales, success, or product
9306
+ `;
9307
+ function quoteTomlString(value) {
9308
+ return JSON.stringify(value);
9309
+ }
9310
+ function defaultModel(provider, explicitModel) {
9311
+ return defaultGranolaAgentModel(provider, explicitModel);
9312
+ }
9313
+ function configTemplate(options) {
9314
+ return [
9315
+ "# Generated by `granola init`.",
9316
+ "# Relative paths in this file resolve from the directory that contains this config file.",
9317
+ "# Store Granola auth once with: granola auth login --api-key grn_...",
9318
+ "",
9319
+ `agent-provider = ${quoteTomlString(options.provider)}`,
9320
+ `agent-model = ${quoteTomlString(options.model)}`,
9321
+ "agent-timeout = \"5m\"",
9322
+ "agent-max-retries = 2",
9323
+ "",
9324
+ `agent-harnesses-file = ${quoteTomlString(options.harnessesFile)}`,
9325
+ `automation-rules-file = ${quoteTomlString(options.rulesFile)}`,
9326
+ `pkm-targets-file = ${quoteTomlString(options.pkmTargetsFile)}`,
9327
+ "output = \"./exports/notes\"",
9328
+ "transcript-output = \"./exports/transcripts\"",
9329
+ "",
9330
+ "# For provider credentials:",
9331
+ "# - OpenRouter: export OPENROUTER_API_KEY=...",
9332
+ "# - OpenAI: export OPENAI_API_KEY=...",
9333
+ "# - Codex: make sure `codex exec` works locally",
9334
+ ""
9335
+ ].join("\n");
9336
+ }
9337
+ function harnessesTemplate(options) {
9338
+ return { harnesses: [{
9339
+ id: "team-notes",
9340
+ match: {
9341
+ folderNames: ["Team"],
9342
+ transcriptLoaded: true
9343
+ },
9344
+ model: options.model,
9345
+ name: "Team Notes",
9346
+ priority: 50,
9347
+ promptFile: options.teamPromptFile,
9348
+ provider: options.provider
9349
+ }, {
9350
+ id: "customer-follow-up",
9351
+ match: {
9352
+ folderNames: ["Customers"],
9353
+ transcriptLoaded: true
9354
+ },
9355
+ model: options.model,
9356
+ name: "Customer Follow-Up",
9357
+ priority: 60,
9358
+ promptFile: options.customerPromptFile,
9359
+ provider: options.provider
9360
+ }] };
9361
+ }
9362
+ function rulesTemplate() {
9363
+ return { rules: [{
9364
+ actions: [{
9365
+ approvalMode: "manual",
9366
+ harnessId: "team-notes",
9367
+ id: "team-notes-pipeline",
9368
+ kind: "agent",
9369
+ name: "Generate team notes",
9370
+ pipeline: { kind: "notes" }
9371
+ }],
9372
+ id: "team-notes-on-transcript",
9373
+ name: "Review team notes when a transcript is ready",
9374
+ when: {
9375
+ eventKinds: ["transcript.ready"],
9376
+ folderNames: ["Team"],
9377
+ transcriptLoaded: true
9378
+ }
9379
+ }, {
9380
+ actions: [{
9381
+ approvalMode: "manual",
9382
+ harnessId: "customer-follow-up",
9383
+ id: "customer-follow-up-pipeline",
9384
+ kind: "agent",
9385
+ name: "Generate customer follow-up",
9386
+ pipeline: { kind: "notes" }
9387
+ }],
9388
+ id: "customer-follow-up-on-transcript",
9389
+ name: "Review customer follow-up notes when a transcript is ready",
9390
+ when: {
9391
+ eventKinds: ["transcript.ready"],
9392
+ folderNames: ["Customers"],
9393
+ transcriptLoaded: true
9394
+ }
9395
+ }] };
9396
+ }
9397
+ async function pathExists(filePath) {
9398
+ try {
9399
+ await access(filePath);
9400
+ return true;
9401
+ } catch {
9402
+ return false;
9403
+ }
9404
+ }
9405
+ async function ensureWritable(filePaths, force) {
9406
+ if (force) return;
9407
+ const existing = [];
9408
+ for (const filePath of filePaths) if (await pathExists(filePath)) existing.push(filePath);
9409
+ if (existing.length > 0) throw new Error(`init would overwrite existing files:\n${existing.map((filePath) => `- ${filePath}`).join("\n")}\nRe-run with --force to replace them.`);
9410
+ }
9411
+ async function writeTextFile(filePath, contents) {
9412
+ await mkdir(dirname(filePath), { recursive: true });
9413
+ await writeFile(filePath, contents, "utf8");
9414
+ }
9415
+ async function writeJsonFile(filePath, value) {
9416
+ await writeTextFile(filePath, `${JSON.stringify(value, null, 2)}\n`);
9417
+ }
9418
+ async function initialiseGranolaToolkitProject(options) {
9419
+ const directory = resolve(options.directory);
9420
+ const configPath = join(directory, ".granola.toml");
9421
+ const projectDirectory = join(directory, ".granola");
9422
+ const promptsDirectory = join(projectDirectory, "prompts");
9423
+ const teamPromptPath = join(promptsDirectory, "team-notes.md");
9424
+ const customerPromptPath = join(promptsDirectory, "customer-follow-up.md");
9425
+ const harnessesPath = join(projectDirectory, "agent-harnesses.json");
9426
+ const rulesPath = join(projectDirectory, "automation-rules.json");
9427
+ const pkmTargetsPath = join(projectDirectory, "pkm-targets.json");
9428
+ const files = [
9429
+ configPath,
9430
+ harnessesPath,
9431
+ rulesPath,
9432
+ pkmTargetsPath,
9433
+ teamPromptPath,
9434
+ customerPromptPath
9435
+ ];
9436
+ const provider = options.provider;
9437
+ const model = defaultModel(provider, options.model);
9438
+ await mkdir(directory, { recursive: true });
9439
+ await ensureWritable(files, options.force === true);
9440
+ await writeTextFile(configPath, configTemplate({
9441
+ harnessesFile: `./${relative(directory, harnessesPath)}`,
9442
+ model,
9443
+ pkmTargetsFile: `./${relative(directory, pkmTargetsPath)}`,
9444
+ provider,
9445
+ rulesFile: `./${relative(directory, rulesPath)}`
9446
+ }));
9447
+ await writeJsonFile(harnessesPath, harnessesTemplate({
9448
+ customerPromptFile: `./${relative(directory, customerPromptPath)}`,
9449
+ model,
9450
+ provider,
9451
+ teamPromptFile: `./${relative(directory, teamPromptPath)}`
9452
+ }));
9453
+ await writeJsonFile(rulesPath, rulesTemplate());
9454
+ await writeJsonFile(pkmTargetsPath, { targets: [] });
9455
+ await writeTextFile(teamPromptPath, TEAM_PROMPT);
9456
+ await writeTextFile(customerPromptPath, CUSTOMER_PROMPT);
9457
+ return {
9458
+ configPath,
9459
+ createdFiles: files,
9460
+ directory
9461
+ };
9462
+ }
9463
+ //#endregion
9464
+ //#region src/commands/init.ts
9465
+ function initHelp() {
9466
+ return `Granola init
9467
+
9468
+ Usage:
9469
+ granola init [options]
9470
+
9471
+ Create a local project bootstrap with:
9472
+ - .granola.toml
9473
+ - starter automation rules
9474
+ - starter harness definitions
9475
+ - prompt files for common meeting types
9476
+
9477
+ Options:
9478
+ --dir <path> Target directory (default: current directory)
9479
+ --force Overwrite existing generated files
9480
+ --model <value> Override the starter model for generated harnesses
9481
+ --provider <value> codex, openai, openrouter (default: codex)
9482
+ -h, --help Show help
9483
+ `;
9484
+ }
9485
+ function parseProvider(value) {
9486
+ switch (value) {
9487
+ case void 0: return "codex";
9488
+ case "codex":
9489
+ case "openai":
9490
+ case "openrouter": return value;
9491
+ default: throw new Error("invalid init provider: expected codex, openai, or openrouter");
9492
+ }
9493
+ }
9494
+ function providerNextStep(provider) {
9495
+ switch (provider) {
9496
+ case "openai": return "2. Export OPENAI_API_KEY in the shell or service that runs your sync loop.";
9497
+ case "openrouter": return "2. Export OPENROUTER_API_KEY in the shell or service that runs your sync loop.";
9498
+ default: return "2. Make sure `codex exec` works locally before you enable agent-driven automation.";
9499
+ }
9500
+ }
9501
+ const initCommand = {
9502
+ description: "Create a local Granola Toolkit project bootstrap",
9503
+ flags: {
9504
+ dir: { type: "string" },
9505
+ force: { type: "boolean" },
9506
+ help: { type: "boolean" },
9507
+ model: { type: "string" },
9508
+ provider: { type: "string" }
9509
+ },
9510
+ help: initHelp,
9511
+ name: "init",
9512
+ async run({ commandArgs, commandFlags }) {
9513
+ if (commandArgs.length > 0) throw new Error("granola init does not accept positional arguments");
9514
+ const directory = typeof commandFlags.dir === "string" && commandFlags.dir.trim() ? commandFlags.dir.trim() : process.cwd();
9515
+ const provider = parseProvider(commandFlags.provider);
9516
+ const result = await initialiseGranolaToolkitProject({
9517
+ directory,
9518
+ force: commandFlags.force === true,
9519
+ model: typeof commandFlags.model === "string" ? commandFlags.model.trim() : void 0,
9520
+ provider
9521
+ });
9522
+ const root = resolve(result.directory);
9523
+ console.log(`Initialised Granola Toolkit in ${root}`);
9524
+ console.log("");
9525
+ console.log("Created:");
9526
+ for (const filePath of result.createdFiles) console.log(` - ./${relative(root, filePath)}`);
9527
+ console.log("");
9528
+ console.log("Next:");
9529
+ console.log("1. Store Granola auth once with `granola auth login --api-key grn_...`.");
9530
+ console.log(providerNextStep(provider));
9531
+ console.log("3. Edit the prompt files under ./.granola/prompts/ to match your real meeting types.");
9532
+ console.log("4. Run `granola sync --config ./.granola.toml` and `granola web --config ./.granola.toml`.");
9533
+ console.log("5. If your folders are not named Team or Customers, adjust ./.granola/automation-rules.json and ./.granola/agent-harnesses.json before enabling a watch loop.");
9534
+ return 0;
9535
+ }
9536
+ };
9537
+ //#endregion
9232
9538
  //#region src/browser.ts
9233
9539
  const execFileAsync = promisify(execFile);
9234
9540
  function getBrowserOpenCommand(url, platform = process.platform) {
@@ -9261,8 +9567,8 @@ async function openExternalUrl(url, options = {}) {
9261
9567
  }
9262
9568
  //#endregion
9263
9569
  //#region src/web/generated.ts
9264
- 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*/";
9265
- 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";
9570
+ 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*/";
9571
+ 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";
9266
9572
  //#endregion
9267
9573
  //#region src/web/assets.ts
9268
9574
  const granolaWebAssetPaths = {
@@ -9386,6 +9692,10 @@ function harnessesFromBody(value) {
9386
9692
  if (!Array.isArray(value)) throw new Error("harnesses must be an array");
9387
9693
  return value;
9388
9694
  }
9695
+ function rulesFromBody(value) {
9696
+ if (!Array.isArray(value)) throw new Error("rules must be an array");
9697
+ return value;
9698
+ }
9389
9699
  function evaluationCasesFromBody(value) {
9390
9700
  if (!Array.isArray(value)) throw new Error("evaluation cases must be an array");
9391
9701
  return value;
@@ -9662,6 +9972,11 @@ async function startGranolaServer(app, options = {}) {
9662
9972
  sendJson(response, await app.listAutomationRules(), { headers: originHeaders });
9663
9973
  return;
9664
9974
  }
9975
+ if (method === "POST" && path === granolaTransportPaths.automationRules) {
9976
+ const body = await readJsonBody(request);
9977
+ sendJson(response, await app.saveAutomationRules(rulesFromBody(body.rules)), { headers: originHeaders });
9978
+ return;
9979
+ }
9665
9980
  if (method === "GET" && path === granolaTransportPaths.automationMatches) {
9666
9981
  sendJson(response, await app.listAutomationMatches({ limit: parseInteger(url.searchParams.get("limit")) }), { headers: originHeaders });
9667
9982
  return;
@@ -10824,6 +11139,7 @@ const commands = [
10824
11139
  authCommand,
10825
11140
  exportsCommand,
10826
11141
  folderCommand,
11142
+ initCommand,
10827
11143
  meetingCommand,
10828
11144
  notesCommand,
10829
11145
  searchCommand,
@@ -10959,6 +11275,7 @@ Global options:
10959
11275
  Examples:
10960
11276
  granola attach http://127.0.0.1:4123
10961
11277
  granola folder list
11278
+ granola init --provider openrouter
10962
11279
  granola sync
10963
11280
  granola notes --supabase "${granolaSupabaseCandidates()[0] ?? "/path/to/supabase.json"}"
10964
11281
  granola transcripts --cache "${granolaCacheCandidates()[0] ?? "/path/to/cache-v3.json"}"