granola-toolkit 0.38.0 → 0.40.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 +1 -0
  2. package/dist/cli.js +2957 -1599
  3. package/package.json +8 -4
package/dist/cli.js CHANGED
@@ -5115,1757 +5115,3105 @@ async function openExternalUrl(url, options = {}) {
5115
5115
  }))(command.file, command.args);
5116
5116
  }
5117
5117
  //#endregion
5118
- //#region src/web/client-state.ts
5119
- function parseWorkspaceTab(value) {
5120
- switch (value) {
5121
- case "metadata":
5122
- case "raw":
5123
- case "transcript": return value;
5124
- default: return "notes";
5125
- }
5126
- }
5127
- function startupSelectionFromSearch(search) {
5128
- const params = new URLSearchParams(search);
5129
- return {
5130
- folderId: params.get("folder")?.trim() || "",
5131
- meetingId: params.get("meeting")?.trim() || "",
5132
- workspaceTab: parseWorkspaceTab(params.get("tab"))
5133
- };
5134
- }
5135
- function buildBrowserUrlPath(currentHref, selection) {
5136
- const url = new URL(currentHref);
5137
- if (selection.selectedFolderId) url.searchParams.set("folder", selection.selectedFolderId);
5138
- else url.searchParams.delete("folder");
5139
- if (selection.selectedMeetingId) url.searchParams.set("meeting", selection.selectedMeetingId);
5140
- else url.searchParams.delete("meeting");
5141
- if (parseWorkspaceTab(selection.workspaceTab) !== "notes") url.searchParams.set("tab", parseWorkspaceTab(selection.workspaceTab));
5142
- else url.searchParams.delete("tab");
5143
- return `${url.pathname}${url.search}${url.hash}`;
5144
- }
5145
- function exportScopeLabel(scope) {
5146
- return scope && scope.mode === "folder" ? `Folder: ${scope.folderName || scope.folderId}` : "Scope: All meetings";
5147
- }
5148
- function currentFilterSummary(filters) {
5149
- const parts = [];
5150
- if (filters.selectedFolderId) {
5151
- const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);
5152
- parts.push(`folder "${folder ? folder.name : filters.selectedFolderId}"`);
5153
- }
5154
- if (filters.search) parts.push(`search "${filters.search}"`);
5155
- if (filters.updatedFrom) parts.push(`from ${filters.updatedFrom}`);
5156
- if (filters.updatedTo) parts.push(`to ${filters.updatedTo}`);
5157
- return parts.join(", ");
5158
- }
5159
- function selectMeetingId(meetings, selectedMeetingId) {
5160
- if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;
5161
- return meetings[0]?.id ?? null;
5162
- }
5163
- function buildMeetingsQuery(filters, options = {}) {
5164
- const params = new URLSearchParams();
5165
- params.set("limit", String(options.limit ?? 100));
5166
- params.set("sort", filters.sort || "updated-desc");
5167
- if (filters.search) params.set("search", filters.search);
5168
- if (filters.updatedFrom) params.set("updatedFrom", filters.updatedFrom);
5169
- if (filters.updatedTo) params.set("updatedTo", filters.updatedTo);
5170
- if (filters.selectedFolderId) params.set("folderId", filters.selectedFolderId);
5171
- if (options.refresh) params.set("refresh", "true");
5172
- return `?${params.toString()}`;
5173
- }
5174
- function buildNotesExportRequest(selectedFolderId) {
5175
- return {
5176
- folderId: selectedFolderId || void 0,
5177
- format: "markdown"
5178
- };
5179
- }
5180
- function buildTranscriptsExportRequest(selectedFolderId) {
5181
- return {
5182
- folderId: selectedFolderId || void 0,
5183
- format: "text"
5184
- };
5185
- }
5186
- function nextWorkspaceTab(currentTab, key) {
5187
- const current = parseWorkspaceTab(currentTab);
5188
- switch (key) {
5189
- case "1": return "notes";
5190
- case "2": return "transcript";
5191
- case "3": return "metadata";
5192
- case "4": return "raw";
5193
- case "]":
5194
- switch (current) {
5195
- case "notes": return "transcript";
5196
- case "transcript": return "metadata";
5197
- case "metadata": return "raw";
5198
- case "raw": return "notes";
5199
- }
5200
- break;
5201
- case "[":
5202
- switch (current) {
5203
- case "notes": return "raw";
5204
- case "transcript": return "notes";
5205
- case "metadata": return "transcript";
5206
- case "raw": return "metadata";
5207
- }
5208
- break;
5209
- default: return;
5210
- }
5118
+ //#region src/web/generated.ts
5119
+ const granolaWebClientCss = String.raw`:root {
5120
+ --bg: #f2ede2;
5121
+ --panel: rgba(255, 252, 247, 0.86);
5122
+ --panel-strong: #fffaf2;
5123
+ --line: rgba(36, 39, 44, 0.12);
5124
+ --ink: #1d242c;
5125
+ --muted: #5d6b77;
5126
+ --accent: #0d6a6d;
5127
+ --accent-soft: rgba(13, 106, 109, 0.12);
5128
+ --warm: #a34f2f;
5129
+ --ok: #246b4f;
5130
+ --error: #9d2c2c;
5131
+ --shadow: 0 24px 80px rgba(40, 32, 16, 0.12);
5132
+ --radius: 24px;
5133
+ --mono: "SF Mono", "IBM Plex Mono", "Cascadia Code", monospace;
5134
+ --serif: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia, serif;
5135
+ --sans: "Avenir Next", "Segoe UI", sans-serif;
5211
5136
  }
5212
- //#endregion
5213
- //#region src/web/client-script.ts
5214
- const granolaWebClientScript = String.raw`
5215
- const serverConfig = window.__GRANOLA_SERVER__ || { passwordRequired: false };
5216
- const workspaceTabs = ["notes", "transcript", "metadata", "raw"];
5217
- ${parseWorkspaceTab.toString()}
5218
- ${startupSelectionFromSearch.toString()}
5219
- ${buildBrowserUrlPath.toString()}
5220
- ${exportScopeLabel.toString()}
5221
- ${currentFilterSummary.toString()}
5222
- ${selectMeetingId.toString()}
5223
- ${buildMeetingsQuery.toString()}
5224
- ${buildNotesExportRequest.toString()}
5225
- ${buildTranscriptsExportRequest.toString()}
5226
- ${nextWorkspaceTab.toString()}
5227
-
5228
- const state = {
5229
- appState: null,
5230
- detailError: "",
5231
- folderError: "",
5232
- folders: [],
5233
- listError: "",
5234
- meetings: [],
5235
- quickOpen: "",
5236
- search: "",
5237
- selectedFolderId: null,
5238
- selectedMeeting: null,
5239
- selectedMeetingBundle: null,
5240
- selectedMeetingId: null,
5241
- meetingSource: "live",
5242
- serverLocked: Boolean(serverConfig.passwordRequired),
5243
- sort: "updated-desc",
5244
- updatedFrom: "",
5245
- updatedTo: "",
5246
- workspaceTab: "notes",
5247
- };
5248
-
5249
- const els = {
5250
- appState: document.querySelector("[data-app-state]"),
5251
- authPanel: document.querySelector("[data-auth-panel]"),
5252
- detailBody: document.querySelector("[data-detail-body]"),
5253
- detailMeta: document.querySelector("[data-detail-meta]"),
5254
- empty: document.querySelector("[data-empty]"),
5255
- folderList: document.querySelector("[data-folder-list]"),
5256
- jobsList: document.querySelector("[data-jobs-list]"),
5257
- list: document.querySelector("[data-meeting-list]"),
5258
- noteButton: document.querySelector("[data-export-notes]"),
5259
- quickOpen: document.querySelector("[data-quick-open]"),
5260
- quickOpenButton: document.querySelector("[data-quick-open-button]"),
5261
- refreshButton: document.querySelector("[data-refresh]"),
5262
- search: document.querySelector("[data-search]"),
5263
- securityPanel: document.querySelector("[data-security-panel]"),
5264
- serverPassword: document.querySelector("[data-server-password]"),
5265
- lockServerButton: document.querySelector("[data-lock-server]"),
5266
- sort: document.querySelector("[data-sort]"),
5267
- stateBadge: document.querySelector("[data-state-badge]"),
5268
- transcriptButton: document.querySelector("[data-export-transcripts]"),
5269
- unlockServerButton: document.querySelector("[data-unlock-server]"),
5270
- updatedFrom: document.querySelector("[data-updated-from]"),
5271
- updatedTo: document.querySelector("[data-updated-to]"),
5272
- workspaceTabs: document.querySelectorAll("[data-workspace-tab]"),
5273
- };
5274
5137
 
5275
- function syncBrowserUrl() {
5276
- const nextPath = buildBrowserUrlPath(window.location.href, {
5277
- selectedFolderId: state.selectedFolderId,
5278
- selectedMeetingId: state.selectedMeetingId,
5279
- workspaceTab: state.workspaceTab,
5280
- });
5281
- const currentPath = window.location.pathname + window.location.search + window.location.hash;
5282
- if (nextPath !== currentPath) {
5283
- history.replaceState(null, "", nextPath);
5284
- }
5138
+ * {
5139
+ box-sizing: border-box;
5285
5140
  }
5286
5141
 
5287
- function escapeHtml(value) {
5288
- return value
5289
- .replaceAll("&", "&")
5290
- .replaceAll("<", "&lt;")
5291
- .replaceAll(">", "&gt;")
5292
- .replaceAll('"', "&quot;");
5142
+ body {
5143
+ margin: 0;
5144
+ min-height: 100vh;
5145
+ font-family: var(--sans);
5146
+ color: var(--ink);
5147
+ background:
5148
+ radial-gradient(circle at top left, rgba(163, 79, 47, 0.18), transparent 32%),
5149
+ radial-gradient(circle at right 12%, rgba(13, 106, 109, 0.16), transparent 28%),
5150
+ linear-gradient(180deg, #f8f2e8 0%, var(--bg) 100%);
5293
5151
  }
5294
5152
 
5295
- function setStatus(label, tone = "idle") {
5296
- els.stateBadge.textContent = label;
5297
- els.stateBadge.dataset.tone = tone;
5153
+ button,
5154
+ input,
5155
+ select {
5156
+ font: inherit;
5298
5157
  }
5299
5158
 
5300
- function syncFilterInputs() {
5301
- els.quickOpen.value = state.quickOpen;
5302
- els.search.value = state.search;
5303
- els.sort.value = state.sort;
5304
- els.updatedFrom.value = state.updatedFrom;
5305
- els.updatedTo.value = state.updatedTo;
5159
+ #granola-web-root {
5160
+ min-height: 100vh;
5306
5161
  }
5307
5162
 
5308
- function renderWorkspaceTabs() {
5309
- for (const button of els.workspaceTabs) {
5310
- button.dataset.selected = button.dataset.workspaceTab === state.workspaceTab ? "true" : "false";
5311
- }
5163
+ .shell {
5164
+ display: grid;
5165
+ grid-template-columns: 320px minmax(0, 1fr);
5166
+ gap: 18px;
5167
+ min-height: 100vh;
5168
+ padding: 24px;
5312
5169
  }
5313
5170
 
5314
- function renderAppState() {
5315
- if (!state.appState) {
5316
- els.appState.innerHTML = "<p>Waiting for server state…</p>";
5317
- els.authPanel.innerHTML = "<p>Waiting for auth state…</p>";
5318
- renderSecurityPanel();
5319
- return;
5320
- }
5321
-
5322
- const appState = state.appState;
5323
- const authMode =
5324
- appState.auth.mode === "api-key"
5325
- ? "API key"
5326
- : appState.auth.mode === "stored-session"
5327
- ? "Stored session"
5328
- : "supabase.json";
5329
- const docs = appState.documents.loaded ? String(appState.documents.count) : "not loaded";
5330
- const cache = appState.cache.loaded
5331
- ? appState.cache.transcriptCount + " transcript sets"
5332
- : appState.cache.configured
5333
- ? "configured"
5334
- : "not configured";
5335
- const indexStatus = appState.index.loaded
5336
- ? appState.index.meetingCount + " meetings"
5337
- : appState.index.available
5338
- ? "available"
5339
- : "not built";
5340
- const folderStatus = appState.folders.loaded
5341
- ? appState.folders.count + " folders"
5342
- : "not loaded";
5343
- const syncStatus = appState.sync.running
5344
- ? "running"
5345
- : appState.sync.lastError
5346
- ? "error"
5347
- : appState.sync.lastCompletedAt
5348
- ? "last " + appState.sync.lastCompletedAt.slice(11, 19)
5349
- : "idle";
5350
-
5351
- els.appState.innerHTML = [
5352
- '<div class="status-grid">',
5353
- '<div><span class="status-label">Surface</span><strong>' + escapeHtml(appState.ui.surface) + "</strong></div>",
5354
- '<div><span class="status-label">View</span><strong>' + escapeHtml(appState.ui.view) + "</strong></div>",
5355
- '<div><span class="status-label">Auth</span><strong>' + escapeHtml(authMode) + "</strong></div>",
5356
- '<div><span class="status-label">Sync</span><strong>' + escapeHtml(syncStatus) + "</strong></div>",
5357
- '<div><span class="status-label">Documents</span><strong>' + escapeHtml(docs) + "</strong></div>",
5358
- '<div><span class="status-label">Folders</span><strong>' + escapeHtml(folderStatus) + "</strong></div>",
5359
- '<div><span class="status-label">Cache</span><strong>' + escapeHtml(cache) + "</strong></div>",
5360
- '<div><span class="status-label">Index</span><strong>' + escapeHtml(indexStatus) + "</strong></div>",
5361
- "</div>",
5362
- ].join("");
5363
-
5364
- renderSecurityPanel();
5365
- renderAuthPanel();
5366
- renderExportJobs();
5171
+ .pane {
5172
+ background: var(--panel);
5173
+ backdrop-filter: blur(18px);
5174
+ border: 1px solid var(--line);
5175
+ border-radius: var(--radius);
5176
+ box-shadow: var(--shadow);
5367
5177
  }
5368
5178
 
5369
- function renderFolderList() {
5370
- if (state.folderError) {
5371
- els.folderList.innerHTML =
5372
- '<div class="folder-empty folder-empty--error">' + escapeHtml(state.folderError) + "</div>";
5373
- return;
5374
- }
5375
-
5376
- const buttons = [
5377
- [
5378
- '<button class="folder-row"' +
5379
- (state.selectedFolderId ? "" : ' data-selected="true"') +
5380
- ' data-folder-id="">',
5381
- '<span class="folder-row__title">All meetings</span>',
5382
- '<span class="folder-row__meta">Browse the full meeting list.</span>',
5383
- "</button>",
5384
- ].join(""),
5385
- ];
5386
-
5387
- for (const folder of state.folders) {
5388
- buttons.push(
5389
- [
5390
- '<button class="folder-row"' +
5391
- (folder.id === state.selectedFolderId ? ' data-selected="true"' : "") +
5392
- ' data-folder-id="' +
5393
- escapeHtml(folder.id) +
5394
- '">',
5395
- '<span class="folder-row__title">' +
5396
- escapeHtml((folder.isFavourite ? "★ " : "") + (folder.name || folder.id)) +
5397
- "</span>",
5398
- '<span class="folder-row__meta">' +
5399
- escapeHtml(String(folder.documentCount) + " meetings") +
5400
- "</span>",
5401
- "</button>",
5402
- ].join(""),
5403
- );
5404
- }
5405
-
5406
- if (buttons.length === 1) {
5407
- buttons.push('<div class="folder-empty">No folders found.</div>');
5408
- }
5409
-
5410
- els.folderList.innerHTML = buttons.join("");
5179
+ .sidebar {
5180
+ display: grid;
5181
+ grid-template-rows: auto auto auto 1fr;
5182
+ overflow: hidden;
5411
5183
  }
5412
5184
 
5413
- function renderSecurityPanel() {
5414
- els.securityPanel.hidden = !state.serverLocked;
5185
+ .hero,
5186
+ .toolbar,
5187
+ .detail-head,
5188
+ .folder-panel {
5189
+ padding: 22px 24px;
5190
+ border-bottom: 1px solid var(--line);
5415
5191
  }
5416
5192
 
5417
- function authActionButton(label, action, disabled) {
5418
- return (
5419
- '<button class="button button--secondary" data-auth-action="' +
5420
- escapeHtml(action) +
5421
- '"' +
5422
- (disabled ? " disabled" : "") +
5423
- ">" +
5424
- escapeHtml(label) +
5425
- "</button>"
5426
- );
5193
+ .hero h1 {
5194
+ margin: 0;
5195
+ font-family: var(--serif);
5196
+ font-size: clamp(2rem, 3vw, 2.8rem);
5197
+ font-weight: 600;
5198
+ letter-spacing: -0.04em;
5427
5199
  }
5428
5200
 
5429
- function authModeButton(label, mode, disabled) {
5430
- return (
5431
- '<button class="button button--secondary" data-auth-mode="' +
5432
- escapeHtml(mode) +
5433
- '"' +
5434
- (disabled ? " disabled" : "") +
5435
- ">" +
5436
- escapeHtml(label) +
5437
- "</button>"
5438
- );
5201
+ .hero p,
5202
+ .toolbar p {
5203
+ margin: 8px 0 0;
5204
+ color: var(--muted);
5205
+ line-height: 1.5;
5439
5206
  }
5440
5207
 
5441
- function renderAuthPanel() {
5442
- const auth = state.appState?.auth;
5443
- if (!auth) {
5444
- els.authPanel.innerHTML = '<div class="auth-card"><div class="auth-card__meta">Auth state unavailable.</div></div>';
5445
- return;
5446
- }
5447
-
5448
- const activeSource =
5449
- auth.mode === "api-key"
5450
- ? "API key"
5451
- : auth.mode === "stored-session"
5452
- ? "Stored session"
5453
- : "supabase.json";
5454
- const lastError = auth.lastError
5455
- ? '<div class="auth-card__meta auth-card__error">' + escapeHtml(auth.lastError) + "</div>"
5456
- : "";
5457
-
5458
- els.authPanel.innerHTML = [
5459
- '<div class="auth-card">',
5460
- '<div class="status-grid">',
5461
- '<div><span class="status-label">Active</span><strong>' + escapeHtml(activeSource) + "</strong></div>",
5462
- '<div><span class="status-label">API key</span><strong>' + escapeHtml(auth.apiKeyAvailable ? "available" : "missing") + "</strong></div>",
5463
- '<div><span class="status-label">Stored</span><strong>' + escapeHtml(auth.storedSessionAvailable ? "available" : "missing") + "</strong></div>",
5464
- '<div><span class="status-label">supabase.json</span><strong>' + escapeHtml(auth.supabaseAvailable ? "available" : "missing") + "</strong></div>",
5465
- '<div><span class="status-label">Refresh</span><strong>' + escapeHtml(auth.refreshAvailable ? "available" : "missing") + "</strong></div>",
5466
- "</div>",
5467
- auth.clientId
5468
- ? '<div class="auth-card__meta">Client ID: ' + escapeHtml(auth.clientId) + "</div>"
5469
- : "",
5470
- auth.signInMethod
5471
- ? '<div class="auth-card__meta">Sign-in method: ' + escapeHtml(auth.signInMethod) + "</div>"
5472
- : "",
5473
- auth.supabasePath
5474
- ? '<div class="auth-card__meta">supabase path: ' + escapeHtml(auth.supabasePath) + "</div>"
5475
- : "",
5476
- lastError,
5477
- '<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>',
5478
- '<div class="auth-card__actions">',
5479
- '<input class="input" type="password" placeholder="grn_..." data-auth-api-key />',
5480
- authActionButton("Save API key", "login-api-key", false),
5481
- authActionButton("Import desktop session", "login", !auth.supabaseAvailable),
5482
- authActionButton("Refresh stored session", "refresh", !auth.storedSessionAvailable || !auth.refreshAvailable),
5483
- authModeButton("Use API key", "api-key", !auth.apiKeyAvailable || auth.mode === "api-key"),
5484
- authModeButton("Use stored session", "stored-session", !auth.storedSessionAvailable || auth.mode === "stored-session"),
5485
- authModeButton("Use supabase.json", "supabase-file", !auth.supabaseAvailable || auth.mode === "supabase-file"),
5486
- authActionButton("Sign out", "logout", !auth.apiKeyAvailable && !auth.storedSessionAvailable),
5487
- "</div>",
5488
- "</div>",
5489
- ].join("");
5208
+ .search,
5209
+ .select,
5210
+ .field-input,
5211
+ .input {
5212
+ width: 100%;
5213
+ margin-top: 16px;
5214
+ padding: 12px 14px;
5215
+ border: 1px solid var(--line);
5216
+ border-radius: 999px;
5217
+ background: rgba(255, 255, 255, 0.7);
5218
+ color: var(--ink);
5490
5219
  }
5491
5220
 
5492
- function renderMeetingList() {
5493
- if (state.listError) {
5494
- els.list.innerHTML =
5495
- '<div class="meeting-empty meeting-empty--error">' + escapeHtml(state.listError) + "</div>";
5496
- return;
5497
- }
5498
-
5499
- if (state.meetings.length === 0) {
5500
- state.selectedMeetingId = null;
5501
- state.selectedMeeting = null;
5502
- state.selectedMeetingBundle = null;
5503
- syncBrowserUrl();
5504
- const filterSummary = currentFilterSummary({
5505
- folders: state.folders,
5506
- search: state.search,
5507
- selectedFolderId: state.selectedFolderId,
5508
- updatedFrom: state.updatedFrom,
5509
- updatedTo: state.updatedTo,
5510
- });
5511
- const message = filterSummary
5512
- ? "No meetings match " + filterSummary + "."
5513
- : "No meetings yet. Try Sync now.";
5514
- els.list.innerHTML = '<div class="meeting-empty">' + escapeHtml(message) + "</div>";
5515
- renderMeetingDetail();
5516
- return;
5517
- }
5518
-
5519
- state.selectedMeetingId = selectMeetingId(state.meetings, state.selectedMeetingId);
5520
- syncBrowserUrl();
5521
-
5522
- els.list.innerHTML = state.meetings
5523
- .map((meeting) => {
5524
- const selected = meeting.id === state.selectedMeetingId ? ' data-selected="true"' : "";
5525
- const tags = meeting.tags.length ? meeting.tags.map((tag) => "#" + tag).join(" ") : "untagged";
5526
- return [
5527
- '<button class="meeting-row"' + selected + ' data-meeting-id="' + escapeHtml(meeting.id) + '">',
5528
- '<span class="meeting-row__title">' + escapeHtml(meeting.title || meeting.id) + "</span>",
5529
- '<span class="meeting-row__meta">' + escapeHtml(tags) + "</span>",
5530
- '<span class="meeting-row__meta">' + escapeHtml(meeting.updatedAt.slice(0, 10) || "unknown") + "</span>",
5531
- "</button>",
5532
- ].join("");
5533
- })
5534
- .join("");
5221
+ .field-row {
5222
+ display: grid;
5223
+ gap: 10px;
5224
+ margin-top: 12px;
5535
5225
  }
5536
5226
 
5537
- function renderMeetingDetail() {
5538
- renderWorkspaceTabs();
5539
-
5540
- if (state.detailError) {
5541
- els.empty.hidden = false;
5542
- els.empty.textContent = state.detailError;
5543
- els.detailMeta.innerHTML = "";
5544
- els.detailBody.innerHTML = "";
5545
- return;
5546
- }
5547
-
5548
- const record = state.selectedMeeting;
5549
- if (!record) {
5550
- els.empty.hidden = false;
5551
- els.empty.textContent = "Select a meeting to inspect its notes and transcript.";
5552
- els.detailMeta.innerHTML = "";
5553
- els.detailBody.innerHTML = "";
5554
- return;
5555
- }
5556
-
5557
- els.empty.hidden = true;
5558
- els.detailMeta.innerHTML = [
5559
- '<div class="detail-chip">ID: ' + escapeHtml(record.meeting.id) + "</div>",
5560
- '<div class="detail-chip">Source: ' + escapeHtml(record.meeting.noteContentSource) + "</div>",
5561
- '<div class="detail-chip">Transcript: ' + escapeHtml(String(record.meeting.transcriptSegmentCount)) + " segments</div>",
5562
- ].join("");
5563
-
5564
- const bundle = state.selectedMeetingBundle;
5565
- const metadataLines = [
5566
- "Title: " + (record.meeting.title || record.meeting.id),
5567
- "Created: " + record.meeting.createdAt,
5568
- "Updated: " + record.meeting.updatedAt,
5569
- "Folders: " + (record.meeting.folders.length ? record.meeting.folders.map((folder) => folder.name).join(", ") : "none"),
5570
- "Tags: " + (record.meeting.tags.length ? record.meeting.tags.join(", ") : "none"),
5571
- "Transcript loaded: " + (record.meeting.transcriptLoaded ? "yes" : "no"),
5572
- ].join("\n");
5573
-
5574
- let mainTitle = "Notes";
5575
- let mainBody = record.noteMarkdown || "";
5576
-
5577
- switch (state.workspaceTab) {
5578
- case "transcript":
5579
- mainTitle = "Transcript";
5580
- mainBody = record.transcriptText || "(Transcript unavailable)";
5581
- break;
5582
- case "metadata":
5583
- mainTitle = "Metadata";
5584
- mainBody = metadataLines;
5585
- break;
5586
- case "raw":
5587
- mainTitle = "Raw Bundle";
5588
- mainBody = JSON.stringify(bundle || record, null, 2);
5589
- break;
5590
- default:
5591
- break;
5592
- }
5593
-
5594
- els.detailBody.innerHTML = [
5595
- '<div class="workspace-grid">',
5596
- '<aside class="detail-section workspace-sidebar">',
5597
- "<h2>Meeting Metadata</h2>",
5598
- '<pre class="detail-pre">' + escapeHtml(metadataLines) + "</pre>",
5599
- "</aside>",
5600
- '<section class="detail-section workspace-main">',
5601
- "<h2>" + escapeHtml(mainTitle) + "</h2>",
5602
- '<pre class="detail-pre">' + escapeHtml(mainBody) + "</pre>",
5603
- "</section>",
5604
- "</div>",
5605
- ].join("");
5227
+ .field-row--inline {
5228
+ grid-template-columns: repeat(2, minmax(0, 1fr));
5606
5229
  }
5607
5230
 
5608
- function renderExportJobs() {
5609
- const jobs = state.appState?.exports?.jobs || [];
5610
- if (jobs.length === 0) {
5611
- els.jobsList.innerHTML = '<div class="job-empty">No export jobs yet.</div>';
5612
- return;
5613
- }
5614
-
5615
- els.jobsList.innerHTML = jobs
5616
- .slice(0, 6)
5617
- .map((job) => {
5618
- const progress = job.itemCount > 0
5619
- ? job.completedCount + "/" + job.itemCount + " items"
5620
- : "0 items";
5621
- const error = job.error ? '<div class="job-card__meta">' + escapeHtml(job.error) + "</div>" : "";
5622
- const rerunButton =
5623
- job.status === "running"
5624
- ? ""
5625
- : '<button class="button button--secondary" data-rerun-job-id="' + escapeHtml(job.id) + '">Rerun</button>';
5626
-
5627
- return [
5628
- '<article class="job-card">',
5629
- '<div class="job-card__head">',
5630
- '<div>',
5631
- '<div class="job-card__title">' + escapeHtml(job.kind) + " export</div>",
5632
- '<div class="job-card__meta">' + escapeHtml(job.id) + "</div>",
5633
- "</div>",
5634
- '<div class="job-card__status" data-status="' + escapeHtml(job.status) + '">' + escapeHtml(job.status) + "</div>",
5635
- "</div>",
5636
- '<div class="job-card__meta">Format: ' + escapeHtml(job.format) + " • " + escapeHtml(exportScopeLabel(job.scope)) + " • " + escapeHtml(progress) + " • Written: " + escapeHtml(String(job.written)) + "</div>",
5637
- '<div class="job-card__meta">Started: ' + escapeHtml(job.startedAt.slice(0, 19)) + "</div>",
5638
- '<div class="job-card__meta">Output: ' + escapeHtml(job.outputDir) + "</div>",
5639
- error,
5640
- '<div class="job-card__actions">' + rerunButton + "</div>",
5641
- "</article>",
5642
- ].join("");
5643
- })
5644
- .join("");
5231
+ .field-label {
5232
+ display: block;
5233
+ margin-bottom: 6px;
5234
+ color: var(--muted);
5235
+ font-size: 0.78rem;
5236
+ font-weight: 700;
5237
+ letter-spacing: 0.08em;
5238
+ text-transform: uppercase;
5645
5239
  }
5646
5240
 
5647
- async function fetchJson(path, init) {
5648
- const response = await fetch(path, init);
5649
- const payload = await response.json().catch(() => ({}));
5650
- if (!response.ok) {
5651
- if (payload.authRequired) {
5652
- state.serverLocked = true;
5653
- renderSecurityPanel();
5654
- }
5655
-
5656
- const error = new Error(payload.error || response.statusText || "Request failed");
5657
- error.authRequired = Boolean(payload.authRequired);
5658
- throw error;
5659
- }
5660
- return payload;
5241
+ .folder-panel {
5242
+ display: grid;
5243
+ gap: 14px;
5661
5244
  }
5662
5245
 
5663
- async function loadFolders(options = {}) {
5664
- const refresh = options.refresh === true;
5665
-
5666
- try {
5667
- state.folderError = "";
5668
- const params = new URLSearchParams();
5669
- params.set("limit", "100");
5670
- if (refresh) {
5671
- params.set("refresh", "true");
5672
- }
5673
-
5674
- const payload = await fetchJson("/folders?" + params.toString());
5675
- state.folders = payload.folders || [];
5676
- if (
5677
- state.selectedFolderId &&
5678
- !state.folders.some((folder) => folder.id === state.selectedFolderId)
5679
- ) {
5680
- state.selectedFolderId = null;
5681
- }
5682
- } catch (error) {
5683
- if (error.authRequired) {
5684
- throw error;
5685
- }
5686
-
5687
- state.folderError = error instanceof Error ? error.message : String(error);
5688
- state.folders = [];
5689
- state.selectedFolderId = null;
5690
- }
5691
-
5692
- renderFolderList();
5693
- syncBrowserUrl();
5246
+ .folder-panel__head h2 {
5247
+ margin: 0;
5248
+ font-size: 0.92rem;
5249
+ letter-spacing: 0.08em;
5250
+ text-transform: uppercase;
5694
5251
  }
5695
5252
 
5696
- async function loadMeetings(options = {}) {
5697
- const preferredMeetingId = options.preferredMeetingId || state.selectedMeetingId;
5698
- const refresh = options.refresh === true;
5699
-
5700
- try {
5701
- state.listError = "";
5702
- const payload = await fetchJson(
5703
- "/meetings" +
5704
- buildMeetingsQuery(
5705
- {
5706
- search: state.search,
5707
- selectedFolderId: state.selectedFolderId,
5708
- sort: state.sort,
5709
- updatedFrom: state.updatedFrom,
5710
- updatedTo: state.updatedTo,
5711
- },
5712
- {
5713
- limit: 100,
5714
- refresh,
5715
- },
5716
- ),
5717
- );
5718
- state.meetings = payload.meetings || [];
5719
- state.meetingSource = payload.source || "live";
5720
-
5721
- if (preferredMeetingId && state.meetings.some((meeting) => meeting.id === preferredMeetingId)) {
5722
- state.selectedMeetingId = preferredMeetingId;
5723
- }
5724
-
5725
- renderMeetingList();
5726
- if (state.selectedMeetingId) {
5727
- await loadMeeting(state.selectedMeetingId);
5728
- return;
5729
- }
5730
-
5731
- state.detailError = "";
5732
- renderMeetingDetail();
5733
- } catch (error) {
5734
- state.listError = error instanceof Error ? error.message : String(error);
5735
- state.selectedMeeting = null;
5736
- state.selectedMeetingBundle = null;
5737
- state.detailError = state.listError;
5738
- renderMeetingList();
5739
- renderMeetingDetail();
5740
- }
5253
+ .folder-panel__head p {
5254
+ margin: 6px 0 0;
5255
+ color: var(--muted);
5256
+ font-size: 0.9rem;
5741
5257
  }
5742
5258
 
5743
- async function loadMeeting(id) {
5744
- state.selectedMeetingId = id;
5745
- syncBrowserUrl();
5746
- renderMeetingList();
5747
-
5748
- try {
5749
- state.detailError = "";
5750
- const payload = await fetchJson("/meetings/" + encodeURIComponent(id));
5751
- state.selectedMeetingBundle = payload;
5752
- state.selectedMeeting = payload.meeting || null;
5753
- renderMeetingDetail();
5754
- } catch (error) {
5755
- state.selectedMeeting = null;
5756
- state.selectedMeetingBundle = null;
5757
- state.detailError = error instanceof Error ? error.message : String(error);
5758
- renderMeetingDetail();
5759
- }
5259
+ .folder-list,
5260
+ .jobs-list {
5261
+ display: grid;
5262
+ gap: 10px;
5760
5263
  }
5761
5264
 
5762
- async function quickOpenMeeting() {
5763
- const query = els.quickOpen.value.trim();
5764
- if (!query) {
5765
- setStatus("Enter a title or id", "error");
5766
- return;
5767
- }
5768
-
5769
- setStatus("Opening meeting…", "busy");
5770
-
5771
- try {
5772
- state.quickOpen = query;
5773
- const payload = await fetchJson("/meetings/resolve?q=" + encodeURIComponent(query));
5774
- state.selectedFolderId = payload.meeting?.meeting?.folders?.[0]?.id || null;
5775
- state.search = "";
5776
- state.updatedFrom = "";
5777
- state.updatedTo = "";
5778
- syncFilterInputs();
5779
- renderFolderList();
5780
- await loadMeetings({
5781
- preferredMeetingId: payload.document.id,
5782
- });
5783
- setStatus("Connected", "ok");
5784
- } catch (error) {
5785
- state.detailError = error instanceof Error ? error.message : String(error);
5786
- renderMeetingDetail();
5787
- setStatus("Quick open failed", "error");
5788
- }
5265
+ .folder-row,
5266
+ .meeting-row {
5267
+ width: 100%;
5268
+ display: grid;
5269
+ gap: 4px;
5270
+ text-align: left;
5271
+ padding: 12px 14px;
5272
+ border: 1px solid transparent;
5273
+ border-radius: 16px;
5274
+ background: rgba(255, 255, 255, 0.72);
5275
+ color: inherit;
5276
+ cursor: pointer;
5277
+ transition:
5278
+ transform 140ms ease,
5279
+ border-color 140ms ease,
5280
+ background 140ms ease;
5789
5281
  }
5790
5282
 
5791
- async function refreshAll(forceLiveMeetings = false) {
5792
- setStatus(forceLiveMeetings ? "Syncing…" : "Refreshing…", "busy");
5793
- try {
5794
- if (forceLiveMeetings) {
5795
- await fetchJson("/sync", {
5796
- body: JSON.stringify({ forceRefresh: true }),
5797
- headers: { "content-type": "application/json" },
5798
- method: "POST",
5799
- });
5800
- }
5801
-
5802
- await loadFolders({ refresh: forceLiveMeetings });
5803
- const [appState, authState] = await Promise.all([fetchJson("/state"), fetchJson("/auth/status")]);
5804
- await loadMeetings({ refresh: forceLiveMeetings });
5805
- state.serverLocked = false;
5806
- state.appState = {
5807
- ...appState,
5808
- auth: authState,
5809
- };
5810
- renderAppState();
5811
- setStatus(
5812
- forceLiveMeetings
5813
- ? "Sync complete"
5814
- : state.meetingSource === "index"
5815
- ? "Loaded from index"
5816
- : "Connected",
5817
- "ok",
5818
- );
5819
- } catch (error) {
5820
- if (error.authRequired) {
5821
- setStatus("Server locked", "error");
5822
- renderSecurityPanel();
5823
- return;
5824
- }
5283
+ .meeting-row {
5284
+ margin: 0 0 10px;
5285
+ padding: 14px 16px;
5286
+ border-radius: 18px;
5287
+ }
5825
5288
 
5826
- throw error;
5827
- }
5289
+ .folder-row:hover,
5290
+ .folder-row[data-selected="true"] {
5291
+ transform: translateY(-1px);
5292
+ border-color: rgba(163, 79, 47, 0.26);
5293
+ background: var(--panel-strong);
5828
5294
  }
5829
5295
 
5830
- async function syncAuthState() {
5831
- const [appState, authState] = await Promise.all([fetchJson("/state"), fetchJson("/auth/status")]);
5832
- state.appState = {
5833
- ...appState,
5834
- auth: authState,
5835
- };
5836
- renderAppState();
5296
+ .meeting-row:hover,
5297
+ .meeting-row[data-selected="true"] {
5298
+ transform: translateY(-1px);
5299
+ border-color: rgba(13, 106, 109, 0.25);
5300
+ background: var(--panel-strong);
5837
5301
  }
5838
5302
 
5839
- async function exportNotes() {
5840
- setStatus(state.selectedFolderId ? "Exporting folder notes…" : "Exporting notes…", "busy");
5841
- await fetchJson("/exports/notes", {
5842
- body: JSON.stringify(buildNotesExportRequest(state.selectedFolderId)),
5843
- headers: { "content-type": "application/json" },
5844
- method: "POST",
5845
- });
5846
- await refreshAll();
5303
+ .folder-row__title,
5304
+ .job-card__title {
5305
+ font-weight: 700;
5847
5306
  }
5848
5307
 
5849
- async function exportTranscripts() {
5850
- setStatus(
5851
- state.selectedFolderId ? "Exporting folder transcripts…" : "Exporting transcripts…",
5852
- "busy",
5853
- );
5854
- await fetchJson("/exports/transcripts", {
5855
- body: JSON.stringify(buildTranscriptsExportRequest(state.selectedFolderId)),
5856
- headers: { "content-type": "application/json" },
5857
- method: "POST",
5858
- });
5859
- await refreshAll();
5308
+ .meeting-row__title {
5309
+ font-weight: 600;
5860
5310
  }
5861
5311
 
5862
- async function rerunJob(id) {
5863
- setStatus("Rerunning export…", "busy");
5864
- try {
5865
- await fetchJson("/exports/jobs/" + encodeURIComponent(id) + "/rerun", {
5866
- method: "POST",
5867
- });
5868
- await refreshAll();
5869
- } catch (error) {
5870
- setStatus("Rerun failed", "error");
5871
- state.detailError = error instanceof Error ? error.message : String(error);
5872
- renderMeetingDetail();
5873
- }
5312
+ .folder-row__meta,
5313
+ .meeting-row__meta,
5314
+ .auth-card__meta,
5315
+ .job-card__meta,
5316
+ .folder-empty,
5317
+ .job-empty,
5318
+ .meeting-empty {
5319
+ color: var(--muted);
5320
+ font-size: 0.9rem;
5874
5321
  }
5875
5322
 
5876
- async function loginAuth(apiKey) {
5877
- setStatus(apiKey ? "Saving API key…" : "Importing desktop session…", "busy");
5878
- try {
5879
- const body = apiKey ? { apiKey } : undefined;
5880
- await fetchJson("/auth/login", {
5881
- body: body ? JSON.stringify(body) : undefined,
5882
- headers: body ? { "content-type": "application/json" } : undefined,
5883
- method: "POST",
5884
- });
5885
- await refreshAll();
5886
- } catch (error) {
5887
- await syncAuthState();
5888
- setStatus(apiKey ? "API key save failed" : "Auth import failed", "error");
5889
- state.detailError = error instanceof Error ? error.message : String(error);
5890
- renderMeetingDetail();
5891
- }
5323
+ .folder-empty--error,
5324
+ .meeting-empty--error,
5325
+ .auth-card__error {
5326
+ color: var(--error);
5892
5327
  }
5893
5328
 
5894
- async function logoutAuth() {
5895
- setStatus("Signing out…", "busy");
5896
- try {
5897
- await fetchJson("/auth/logout", { method: "POST" });
5898
- await refreshAll();
5899
- } catch (error) {
5900
- await syncAuthState();
5901
- setStatus("Sign out failed", "error");
5902
- state.detailError = error instanceof Error ? error.message : String(error);
5903
- renderMeetingDetail();
5904
- }
5329
+ .meeting-list {
5330
+ padding: 14px;
5331
+ overflow: auto;
5905
5332
  }
5906
5333
 
5907
- async function refreshAuth() {
5908
- setStatus("Refreshing session…", "busy");
5909
- try {
5910
- await fetchJson("/auth/refresh", { method: "POST" });
5911
- await refreshAll();
5912
- } catch (error) {
5913
- await syncAuthState();
5914
- setStatus("Refresh failed", "error");
5915
- state.detailError = error instanceof Error ? error.message : String(error);
5916
- renderMeetingDetail();
5917
- }
5334
+ .detail {
5335
+ display: grid;
5336
+ grid-template-rows: auto auto 1fr;
5337
+ min-width: 0;
5918
5338
  }
5919
5339
 
5920
- async function switchAuthMode(mode) {
5921
- setStatus("Switching auth source…", "busy");
5922
- try {
5923
- await fetchJson("/auth/mode", {
5924
- body: JSON.stringify({ mode }),
5925
- headers: { "content-type": "application/json" },
5926
- method: "POST",
5927
- });
5928
- await refreshAll();
5929
- } catch (error) {
5930
- await syncAuthState();
5931
- setStatus("Switch failed", "error");
5932
- state.detailError = error instanceof Error ? error.message : String(error);
5933
- renderMeetingDetail();
5934
- }
5340
+ .detail-head {
5341
+ display: flex;
5342
+ align-items: center;
5343
+ justify-content: space-between;
5344
+ gap: 18px;
5935
5345
  }
5936
5346
 
5937
- async function unlockServer() {
5938
- const password = els.serverPassword.value;
5939
- if (!password.trim()) {
5940
- setStatus("Enter the server password", "error");
5941
- return;
5942
- }
5943
-
5944
- setStatus("Unlocking server…", "busy");
5945
- try {
5946
- await fetchJson("/auth/unlock", {
5947
- body: JSON.stringify({ password }),
5948
- headers: { "content-type": "application/json" },
5949
- method: "POST",
5950
- });
5951
- els.serverPassword.value = "";
5952
- state.serverLocked = false;
5953
- await refreshAll(true);
5954
- } catch (error) {
5955
- setStatus("Unlock failed", "error");
5956
- state.detailError = error instanceof Error ? error.message : String(error);
5957
- renderMeetingDetail();
5958
- }
5347
+ .detail-head h2 {
5348
+ margin: 0;
5349
+ font-family: var(--serif);
5350
+ font-size: clamp(1.8rem, 2.4vw, 2.4rem);
5351
+ font-weight: 600;
5959
5352
  }
5960
5353
 
5961
- async function lockServer() {
5962
- try {
5963
- await fetchJson("/auth/lock", {
5964
- method: "POST",
5965
- });
5966
- } catch {}
5967
-
5968
- state.serverLocked = true;
5969
- state.appState = null;
5970
- state.folders = [];
5971
- state.meetings = [];
5972
- state.selectedFolderId = null;
5973
- state.selectedMeeting = null;
5974
- state.selectedMeetingBundle = null;
5975
- state.detailError = "";
5976
- state.folderError = "";
5977
- els.serverPassword.value = "";
5978
- renderSecurityPanel();
5979
- renderFolderList();
5980
- renderMeetingList();
5981
- renderMeetingDetail();
5982
- setStatus("Server locked", "error");
5354
+ .state-badge {
5355
+ padding: 10px 14px;
5356
+ border-radius: 999px;
5357
+ background: var(--accent-soft);
5358
+ color: var(--accent);
5359
+ font-size: 0.92rem;
5360
+ font-weight: 700;
5983
5361
  }
5984
5362
 
5985
- els.folderList.addEventListener("click", (event) => {
5986
- if (!(event.target instanceof Element)) {
5987
- return;
5988
- }
5363
+ .state-badge[data-tone="busy"] {
5364
+ background: rgba(163, 79, 47, 0.12);
5365
+ color: var(--warm);
5366
+ }
5989
5367
 
5990
- const button = event.target.closest("[data-folder-id]");
5991
- if (!button) {
5992
- return;
5993
- }
5368
+ .state-badge[data-tone="error"] {
5369
+ background: rgba(157, 44, 44, 0.12);
5370
+ color: var(--error);
5371
+ }
5994
5372
 
5995
- const nextFolderId = button.dataset.folderId || null;
5996
- state.selectedFolderId = nextFolderId;
5997
- state.selectedMeetingId = null;
5998
- state.selectedMeeting = null;
5999
- state.selectedMeetingBundle = null;
6000
- renderFolderList();
6001
- void loadMeetings();
6002
- });
5373
+ .state-badge[data-tone="ok"] {
5374
+ background: rgba(36, 107, 79, 0.12);
5375
+ color: var(--ok);
5376
+ }
6003
5377
 
6004
- els.list.addEventListener("click", (event) => {
6005
- if (!(event.target instanceof Element)) {
6006
- return;
6007
- }
5378
+ .toolbar {
5379
+ display: flex;
5380
+ flex-wrap: wrap;
5381
+ align-items: center;
5382
+ justify-content: space-between;
5383
+ gap: 14px;
5384
+ }
6008
5385
 
6009
- const button = event.target.closest("[data-meeting-id]");
6010
- if (!button) return;
6011
- void loadMeeting(button.dataset.meetingId);
6012
- });
5386
+ .toolbar-actions,
5387
+ .auth-card__actions,
5388
+ .job-card__actions {
5389
+ display: flex;
5390
+ flex-wrap: wrap;
5391
+ gap: 10px;
5392
+ }
6013
5393
 
6014
- els.jobsList.addEventListener("click", (event) => {
6015
- if (!(event.target instanceof Element)) {
6016
- return;
6017
- }
5394
+ .toolbar-form {
5395
+ display: grid;
5396
+ grid-template-columns: minmax(0, 1fr) auto;
5397
+ gap: 10px;
5398
+ width: min(440px, 100%);
5399
+ }
6018
5400
 
6019
- const button = event.target.closest("[data-rerun-job-id]");
6020
- if (!button) {
6021
- return;
6022
- }
5401
+ .security-panel,
5402
+ .auth-panel,
5403
+ .jobs-panel {
5404
+ padding: 0 24px 18px;
5405
+ }
6023
5406
 
6024
- void rerunJob(button.dataset.rerunJobId);
6025
- });
5407
+ .security-panel__head h3,
5408
+ .auth-panel__head h3,
5409
+ .jobs-panel__head h3 {
5410
+ margin: 0;
5411
+ font-size: 0.92rem;
5412
+ letter-spacing: 0.08em;
5413
+ text-transform: uppercase;
5414
+ }
6026
5415
 
6027
- els.authPanel.addEventListener("click", (event) => {
6028
- if (!(event.target instanceof Element)) {
6029
- return;
6030
- }
5416
+ .security-panel__head p,
5417
+ .auth-panel__head p,
5418
+ .jobs-panel__head p {
5419
+ margin: 6px 0 0;
5420
+ color: var(--muted);
5421
+ font-size: 0.9rem;
5422
+ }
6031
5423
 
6032
- const actionButton = event.target.closest("[data-auth-action]");
6033
- if (actionButton) {
6034
- switch (actionButton.dataset.authAction) {
6035
- case "login-api-key": {
6036
- const apiKeyInput = els.authPanel.querySelector("[data-auth-api-key]");
6037
- const apiKey =
6038
- apiKeyInput && "value" in apiKeyInput ? String(apiKeyInput.value || "").trim() : "";
6039
- if (!apiKey) {
6040
- setStatus("Enter a Granola API key", "error");
6041
- return;
6042
- }
6043
- void loginAuth(apiKey);
6044
- return;
6045
- }
6046
- case "login":
6047
- void loginAuth();
6048
- return;
6049
- case "logout":
6050
- void logoutAuth();
6051
- return;
6052
- case "refresh":
6053
- void refreshAuth();
6054
- return;
6055
- default:
6056
- return;
6057
- }
6058
- }
5424
+ .security-panel__body,
5425
+ .auth-panel__body {
5426
+ display: grid;
5427
+ gap: 12px;
5428
+ margin-top: 14px;
5429
+ }
6059
5430
 
6060
- const modeButton = event.target.closest("[data-auth-mode]");
6061
- if (!modeButton) {
6062
- return;
6063
- }
5431
+ .auth-card,
5432
+ .job-card {
5433
+ display: grid;
5434
+ gap: 12px;
5435
+ padding: 14px 16px;
5436
+ border: 1px solid var(--line);
5437
+ border-radius: 18px;
5438
+ background: rgba(255, 255, 255, 0.72);
5439
+ }
6064
5440
 
6065
- void switchAuthMode(modeButton.dataset.authMode);
6066
- });
5441
+ .job-card__head {
5442
+ display: flex;
5443
+ flex-wrap: wrap;
5444
+ align-items: center;
5445
+ justify-content: space-between;
5446
+ gap: 10px;
5447
+ }
6067
5448
 
6068
- els.unlockServerButton.addEventListener("click", () => {
6069
- void unlockServer();
6070
- });
5449
+ .job-card__status {
5450
+ padding: 6px 10px;
5451
+ border-radius: 999px;
5452
+ background: var(--accent-soft);
5453
+ color: var(--accent);
5454
+ font-size: 0.82rem;
5455
+ font-weight: 700;
5456
+ }
6071
5457
 
6072
- els.lockServerButton.addEventListener("click", () => {
6073
- void lockServer();
6074
- });
5458
+ .job-card__status[data-status="running"] {
5459
+ background: rgba(163, 79, 47, 0.12);
5460
+ color: var(--warm);
5461
+ }
6075
5462
 
6076
- els.serverPassword.addEventListener("keydown", (event) => {
6077
- if (!(event.target instanceof HTMLInputElement)) {
6078
- return;
6079
- }
5463
+ .job-card__status[data-status="failed"] {
5464
+ background: rgba(157, 44, 44, 0.12);
5465
+ color: var(--error);
5466
+ }
6080
5467
 
6081
- if (event.key === "Enter") {
6082
- event.preventDefault();
6083
- void unlockServer();
6084
- }
6085
- });
5468
+ .job-card__status[data-status="completed"] {
5469
+ background: rgba(36, 107, 79, 0.12);
5470
+ color: var(--ok);
5471
+ }
6086
5472
 
6087
- els.refreshButton.addEventListener("click", () => {
6088
- void refreshAll(true);
6089
- });
5473
+ .workspace-tabs {
5474
+ display: flex;
5475
+ flex-wrap: wrap;
5476
+ align-items: center;
5477
+ gap: 10px;
5478
+ padding: 0 24px 18px;
5479
+ }
6090
5480
 
6091
- els.noteButton.addEventListener("click", () => {
6092
- void exportNotes();
6093
- });
5481
+ .workspace-tab,
5482
+ .button {
5483
+ border: 1px solid var(--line);
5484
+ border-radius: 999px;
5485
+ padding: 10px 14px;
5486
+ background: rgba(255, 255, 255, 0.72);
5487
+ color: var(--ink);
5488
+ cursor: pointer;
5489
+ font-weight: 700;
5490
+ }
6094
5491
 
6095
- els.transcriptButton.addEventListener("click", () => {
6096
- void exportTranscripts();
6097
- });
5492
+ .button {
5493
+ padding: 12px 16px;
5494
+ }
6098
5495
 
6099
- els.search.addEventListener("input", (event) => {
6100
- if (!(event.target instanceof HTMLInputElement)) {
6101
- return;
6102
- }
5496
+ .workspace-tab[data-selected="true"],
5497
+ .button--primary {
5498
+ background: var(--ink);
5499
+ color: #fff;
5500
+ border-color: var(--ink);
5501
+ }
6103
5502
 
6104
- state.search = event.target.value.trim();
6105
- void loadMeetings();
6106
- });
5503
+ .button--secondary {
5504
+ background: rgba(255, 255, 255, 0.72);
5505
+ }
6107
5506
 
6108
- els.sort.addEventListener("change", (event) => {
6109
- if (!(event.target instanceof HTMLSelectElement)) {
6110
- return;
6111
- }
5507
+ .button:disabled {
5508
+ cursor: not-allowed;
5509
+ opacity: 0.56;
5510
+ }
6112
5511
 
6113
- state.sort = event.target.value;
6114
- void loadMeetings();
6115
- });
5512
+ .workspace-hint {
5513
+ margin-left: auto;
5514
+ color: var(--muted);
5515
+ font-size: 0.86rem;
5516
+ }
6116
5517
 
6117
- els.updatedFrom.addEventListener("change", (event) => {
6118
- if (!(event.target instanceof HTMLInputElement)) {
6119
- return;
6120
- }
5518
+ .status-grid {
5519
+ display: grid;
5520
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
5521
+ gap: 14px;
5522
+ }
6121
5523
 
6122
- state.updatedFrom = event.target.value;
6123
- void loadMeetings();
6124
- });
5524
+ .status-label {
5525
+ display: block;
5526
+ margin-bottom: 6px;
5527
+ color: var(--muted);
5528
+ font-size: 0.78rem;
5529
+ letter-spacing: 0.08em;
5530
+ text-transform: uppercase;
5531
+ }
6125
5532
 
6126
- els.updatedTo.addEventListener("change", (event) => {
6127
- if (!(event.target instanceof HTMLInputElement)) {
6128
- return;
6129
- }
5533
+ .detail-meta {
5534
+ display: flex;
5535
+ flex-wrap: wrap;
5536
+ gap: 10px;
5537
+ padding: 0 24px 18px;
5538
+ }
6130
5539
 
6131
- state.updatedTo = event.target.value;
6132
- void loadMeetings();
6133
- });
5540
+ .detail-chip {
5541
+ padding: 10px 12px;
5542
+ border-radius: 999px;
5543
+ background: rgba(255, 255, 255, 0.72);
5544
+ border: 1px solid var(--line);
5545
+ color: var(--muted);
5546
+ font-size: 0.88rem;
5547
+ }
6134
5548
 
6135
- els.quickOpen.addEventListener("input", (event) => {
6136
- if (!(event.target instanceof HTMLInputElement)) {
6137
- return;
6138
- }
5549
+ .detail-body {
5550
+ padding: 0 24px 24px;
5551
+ overflow: auto;
5552
+ }
6139
5553
 
6140
- state.quickOpen = event.target.value;
6141
- });
5554
+ .workspace-grid {
5555
+ display: grid;
5556
+ grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
5557
+ gap: 18px;
5558
+ }
6142
5559
 
6143
- els.quickOpen.addEventListener("keydown", (event) => {
6144
- if (!(event.target instanceof HTMLInputElement)) {
6145
- return;
6146
- }
5560
+ .detail-section {
5561
+ margin-bottom: 20px;
5562
+ padding: 20px;
5563
+ background: rgba(255, 255, 255, 0.72);
5564
+ border: 1px solid var(--line);
5565
+ border-radius: 20px;
5566
+ }
6147
5567
 
6148
- if (event.key === "Enter") {
6149
- event.preventDefault();
6150
- void quickOpenMeeting();
6151
- }
6152
- });
5568
+ .detail-section h2 {
5569
+ margin: 0 0 14px;
5570
+ font-size: 1rem;
5571
+ letter-spacing: 0.08em;
5572
+ text-transform: uppercase;
5573
+ }
6153
5574
 
6154
- els.quickOpenButton.addEventListener("click", () => {
6155
- void quickOpenMeeting();
6156
- });
5575
+ .detail-pre {
5576
+ margin: 0;
5577
+ white-space: pre-wrap;
5578
+ word-break: break-word;
5579
+ font-family: var(--mono);
5580
+ line-height: 1.55;
5581
+ }
6157
5582
 
6158
- els.workspaceTabs.forEach((button) => {
6159
- button.addEventListener("click", () => {
6160
- state.workspaceTab = button.dataset.workspaceTab || "notes";
6161
- syncBrowserUrl();
6162
- renderMeetingDetail();
6163
- });
6164
- });
5583
+ .empty {
5584
+ margin: 24px;
5585
+ padding: 24px;
5586
+ border-radius: 20px;
5587
+ background: rgba(255, 255, 255, 0.72);
5588
+ color: var(--muted);
5589
+ }
6165
5590
 
6166
- document.addEventListener("keydown", (event) => {
6167
- if (
6168
- event.target instanceof HTMLInputElement ||
6169
- event.target instanceof HTMLSelectElement ||
6170
- event.target instanceof HTMLTextAreaElement
6171
- ) {
6172
- return;
6173
- }
6174
-
6175
- const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);
6176
- if (nextTab) {
6177
- state.workspaceTab = nextTab;
6178
- syncBrowserUrl();
6179
- renderMeetingDetail();
6180
- }
6181
- });
6182
-
6183
- const initialSelection = startupSelectionFromSearch(window.location.search);
6184
- state.selectedFolderId = initialSelection.folderId || null;
6185
- state.selectedMeetingId = initialSelection.meetingId || null;
6186
- state.workspaceTab = initialSelection.workspaceTab;
6187
-
6188
- const events = new EventSource("/events");
6189
- events.addEventListener("state.updated", (event) => {
6190
- const previousLoadedAt = state.appState?.documents?.loadedAt;
6191
- const payload = JSON.parse(event.data);
6192
- state.appState = payload.state;
6193
- renderAppState();
6194
-
6195
- if (
6196
- state.meetingSource === "index" &&
6197
- payload.state.documents?.loadedAt &&
6198
- payload.state.documents.loadedAt !== previousLoadedAt
6199
- ) {
6200
- void (async () => {
6201
- await loadFolders();
6202
- await loadMeetings({
6203
- preferredMeetingId: state.selectedMeetingId,
6204
- });
6205
- })();
5591
+ @media (max-width: 1024px) {
5592
+ .shell,
5593
+ .workspace-grid {
5594
+ grid-template-columns: 1fr;
6206
5595
  }
6207
- });
6208
- events.addEventListener("error", () => {
6209
- setStatus("Disconnected", "error");
6210
- });
6211
-
6212
- syncFilterInputs();
6213
- renderSecurityPanel();
6214
- renderFolderList();
6215
-
6216
- void refreshAll().catch((error) => {
6217
- setStatus("Error", "error");
6218
- els.empty.hidden = false;
6219
- els.empty.textContent = error.message;
6220
- });
6221
- `;
6222
- //#endregion
6223
- //#region src/web/markup.ts
6224
- const granolaWebMarkup = String.raw`
6225
- <div class="shell">
6226
- <aside class="pane sidebar">
6227
- <section class="hero">
6228
- <h1>Granola Toolkit</h1>
6229
- <p>Browser workspace for folders, meetings, notes, transcripts, and export flows on top of one local server instance.</p>
6230
- <input class="search" data-search placeholder="Search meetings, ids, or tags" />
6231
- <div class="field-row field-row--inline">
6232
- <label>
6233
- <span class="field-label">Sort</span>
6234
- <select class="select" data-sort>
6235
- <option value="updated-desc">Newest first</option>
6236
- <option value="updated-asc">Oldest first</option>
6237
- <option value="title-asc">Title A-Z</option>
6238
- <option value="title-desc">Title Z-A</option>
6239
- </select>
6240
- </label>
6241
- <label>
6242
- <span class="field-label">Updated From</span>
6243
- <input class="field-input" data-updated-from type="date" />
6244
- </label>
6245
- </div>
6246
- <label class="field-row">
6247
- <span class="field-label">Updated To</span>
6248
- <input class="field-input" data-updated-to type="date" />
6249
- </label>
6250
- </section>
6251
- <section class="folder-panel">
6252
- <div class="folder-panel__head">
6253
- <h2>Folders</h2>
6254
- <p>Pick a folder to scope the meeting browser, or stay on All meetings.</p>
6255
- </div>
6256
- <div class="folder-list" data-folder-list></div>
6257
- </section>
6258
- <section class="toolbar">
6259
- <div>
6260
- <p>Meetings are loaded from the shared server state so this view can later coexist with the terminal UI.</p>
6261
- </div>
6262
- <div class="toolbar-form">
6263
- <input class="field-input" data-quick-open placeholder="Quick open by id or title" />
6264
- <button class="button button--secondary" data-quick-open-button>Open</button>
6265
- </div>
6266
- </section>
6267
- <section class="meeting-list" data-meeting-list></section>
6268
- </aside>
6269
- <main class="pane detail">
6270
- <section class="detail-head">
6271
- <div>
6272
- <h2>Meeting Workspace</h2>
6273
- <div data-app-state></div>
6274
- </div>
6275
- <div class="state-badge" data-state-badge data-tone="idle">Connecting…</div>
6276
- </section>
6277
- <section class="toolbar">
6278
- <div class="toolbar-actions">
6279
- <button class="button button--primary" data-refresh>Sync now</button>
6280
- <button class="button button--secondary" data-export-notes>Export Notes</button>
6281
- <button class="button button--secondary" data-export-transcripts>Export Transcripts</button>
6282
- </div>
6283
- <p>Initial beta web client. It speaks to the same local API that future TUI and attach flows will use.</p>
6284
- </section>
6285
- <section class="security-panel" data-security-panel hidden>
6286
- <div class="security-panel__head">
6287
- <h3>Server Access</h3>
6288
- <p>This server is locked with a password. Unlock it to load meetings and live state.</p>
6289
- </div>
6290
- <div class="security-panel__body">
6291
- <input class="field-input" data-server-password type="password" placeholder="Server password" />
6292
- <div class="toolbar-actions">
6293
- <button class="button button--primary" data-unlock-server>Unlock</button>
6294
- <button class="button button--secondary" data-lock-server>Lock</button>
6295
- </div>
6296
- </div>
6297
- </section>
6298
- <section class="auth-panel">
6299
- <div class="auth-panel__head">
6300
- <h3>Auth Session</h3>
6301
- <p>Inspect, refresh, and switch between stored session and <code>supabase.json</code>.</p>
6302
- </div>
6303
- <div class="auth-panel__body" data-auth-panel></div>
6304
- </section>
6305
- <section class="jobs-panel">
6306
- <div class="jobs-panel__head">
6307
- <h3>Recent Export Jobs</h3>
6308
- <p>Tracked across CLI and web runs.</p>
6309
- </div>
6310
- <div class="jobs-list" data-jobs-list></div>
6311
- </section>
6312
- <nav class="workspace-tabs">
6313
- <button class="workspace-tab" data-workspace-tab="notes">Notes</button>
6314
- <button class="workspace-tab" data-workspace-tab="transcript">Transcript</button>
6315
- <button class="workspace-tab" data-workspace-tab="metadata">Metadata</button>
6316
- <button class="workspace-tab" data-workspace-tab="raw">Raw</button>
6317
- <span class="workspace-hint">1-4 switch tabs, [ and ] cycle</span>
6318
- </nav>
6319
- <div class="detail-meta" data-detail-meta></div>
6320
- <div class="detail-body" data-detail-body>
6321
- <div class="empty" data-empty>Select a meeting to inspect its notes and transcript.</div>
6322
- </div>
6323
- </main>
6324
- </div>
6325
- `;
6326
- //#endregion
6327
- //#region src/web/styles.ts
6328
- const granolaWebStyles = String.raw`
6329
- :root {
6330
- --bg: #f2ede2;
6331
- --panel: rgba(255, 252, 247, 0.86);
6332
- --panel-strong: #fffaf2;
6333
- --line: rgba(36, 39, 44, 0.12);
6334
- --ink: #1d242c;
6335
- --muted: #5d6b77;
6336
- --accent: #0d6a6d;
6337
- --accent-soft: rgba(13, 106, 109, 0.12);
6338
- --warm: #a34f2f;
6339
- --ok: #246b4f;
6340
- --error: #9d2c2c;
6341
- --shadow: 0 24px 80px rgba(40, 32, 16, 0.12);
6342
- --radius: 24px;
6343
- --mono: "SF Mono", "IBM Plex Mono", "Cascadia Code", monospace;
6344
- --serif: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Georgia, serif;
6345
- --sans: "Avenir Next", "Segoe UI", sans-serif;
6346
- }
6347
-
6348
- * { box-sizing: border-box; }
6349
-
6350
- body {
6351
- margin: 0;
6352
- min-height: 100vh;
6353
- font-family: var(--sans);
6354
- color: var(--ink);
6355
- background:
6356
- radial-gradient(circle at top left, rgba(163, 79, 47, 0.18), transparent 32%),
6357
- radial-gradient(circle at right 12%, rgba(13, 106, 109, 0.16), transparent 28%),
6358
- linear-gradient(180deg, #f8f2e8 0%, var(--bg) 100%);
6359
- }
6360
-
6361
- .shell {
6362
- display: grid;
6363
- grid-template-columns: 320px minmax(0, 1fr);
6364
- gap: 18px;
6365
- min-height: 100vh;
6366
- padding: 24px;
6367
- }
6368
-
6369
- .pane {
6370
- background: var(--panel);
6371
- backdrop-filter: blur(18px);
6372
- border: 1px solid var(--line);
6373
- border-radius: var(--radius);
6374
- box-shadow: var(--shadow);
6375
- }
6376
-
6377
- .sidebar {
6378
- display: grid;
6379
- grid-template-rows: auto auto auto 1fr;
6380
- overflow: hidden;
6381
5596
  }
6382
-
6383
- .hero, .toolbar, .detail-head, .folder-panel {
6384
- padding: 22px 24px;
6385
- border-bottom: 1px solid var(--line);
5597
+ /*$vite$:1*/`;
5598
+ const granolaWebClientJs = String.raw`//#region node_modules/solid-js/dist/solid.js
5599
+ var sharedConfig = {
5600
+ context: void 0,
5601
+ registry: void 0,
5602
+ effects: void 0,
5603
+ done: false,
5604
+ getContextId() {
5605
+ return getContextId(this.context.count);
5606
+ },
5607
+ getNextContextId() {
5608
+ return getContextId(this.context.count++);
5609
+ }
5610
+ };
5611
+ function getContextId(count) {
5612
+ const num = String(count), len = num.length - 1;
5613
+ return sharedConfig.context.id + (len ? String.fromCharCode(96 + len) : "") + num;
6386
5614
  }
6387
-
6388
- .hero h1 {
6389
- margin: 0;
6390
- font-family: var(--serif);
6391
- font-size: clamp(2rem, 3vw, 2.8rem);
6392
- font-weight: 600;
6393
- letter-spacing: -0.04em;
5615
+ function setHydrateContext(context) {
5616
+ sharedConfig.context = context;
6394
5617
  }
6395
-
6396
- .hero p, .toolbar p {
6397
- margin: 8px 0 0;
6398
- color: var(--muted);
6399
- line-height: 1.5;
5618
+ function nextHydrateContext() {
5619
+ return {
5620
+ ...sharedConfig.context,
5621
+ id: sharedConfig.getNextContextId(),
5622
+ count: 0
5623
+ };
6400
5624
  }
6401
-
6402
- .search,
6403
- .select,
6404
- .field-input {
6405
- width: 100%;
6406
- margin-top: 16px;
6407
- padding: 12px 14px;
6408
- border: 1px solid var(--line);
6409
- border-radius: 999px;
6410
- background: rgba(255, 255, 255, 0.7);
6411
- color: var(--ink);
6412
- font: inherit;
5625
+ var equalFn = (a, b) => a === b;
5626
+ var $PROXY = Symbol("solid-proxy");
5627
+ var $TRACK = Symbol("solid-track");
5628
+ var signalOptions = { equals: equalFn };
5629
+ var ERROR = null;
5630
+ var runEffects = runQueue;
5631
+ var STALE = 1;
5632
+ var PENDING = 2;
5633
+ var UNOWNED = {
5634
+ owned: null,
5635
+ cleanups: null,
5636
+ context: null,
5637
+ owner: null
5638
+ };
5639
+ var Owner = null;
5640
+ var Transition = null;
5641
+ var Scheduler = null;
5642
+ var ExternalSourceConfig = null;
5643
+ var Listener = null;
5644
+ var Updates = null;
5645
+ var Effects = null;
5646
+ var ExecCount = 0;
5647
+ function createRoot(fn, detachedOwner) {
5648
+ const listener = Listener, owner = Owner, unowned = fn.length === 0, current = detachedOwner === void 0 ? owner : detachedOwner, root = unowned ? UNOWNED : {
5649
+ owned: null,
5650
+ cleanups: null,
5651
+ context: current ? current.context : null,
5652
+ owner: current
5653
+ }, updateFn = unowned ? fn : () => fn(() => untrack(() => cleanNode(root)));
5654
+ Owner = root;
5655
+ Listener = null;
5656
+ try {
5657
+ return runUpdates(updateFn, true);
5658
+ } finally {
5659
+ Listener = listener;
5660
+ Owner = owner;
5661
+ }
5662
+ }
5663
+ function createSignal(value, options) {
5664
+ options = options ? Object.assign({}, signalOptions, options) : signalOptions;
5665
+ const s = {
5666
+ value,
5667
+ observers: null,
5668
+ observerSlots: null,
5669
+ comparator: options.equals || void 0
5670
+ };
5671
+ const setter = (value) => {
5672
+ if (typeof value === "function") if (Transition && Transition.running && Transition.sources.has(s)) value = value(s.tValue);
5673
+ else value = value(s.value);
5674
+ return writeSignal(s, value);
5675
+ };
5676
+ return [readSignal.bind(s), setter];
5677
+ }
5678
+ function createRenderEffect(fn, value, options) {
5679
+ const c = createComputation(fn, value, false, STALE);
5680
+ if (Scheduler && Transition && Transition.running) Updates.push(c);
5681
+ else updateComputation(c);
5682
+ }
5683
+ function createEffect(fn, value, options) {
5684
+ runEffects = runUserEffects;
5685
+ const c = createComputation(fn, value, false, STALE), s = SuspenseContext && useContext(SuspenseContext);
5686
+ if (s) c.suspense = s;
5687
+ if (!options || !options.render) c.user = true;
5688
+ Effects ? Effects.push(c) : updateComputation(c);
5689
+ }
5690
+ function createMemo(fn, value, options) {
5691
+ options = options ? Object.assign({}, signalOptions, options) : signalOptions;
5692
+ const c = createComputation(fn, value, true, 0);
5693
+ c.observers = null;
5694
+ c.observerSlots = null;
5695
+ c.comparator = options.equals || void 0;
5696
+ if (Scheduler && Transition && Transition.running) {
5697
+ c.tState = STALE;
5698
+ Updates.push(c);
5699
+ } else updateComputation(c);
5700
+ return readSignal.bind(c);
5701
+ }
5702
+ function batch(fn) {
5703
+ return runUpdates(fn, false);
5704
+ }
5705
+ function untrack(fn) {
5706
+ if (!ExternalSourceConfig && Listener === null) return fn();
5707
+ const listener = Listener;
5708
+ Listener = null;
5709
+ try {
5710
+ if (ExternalSourceConfig) return ExternalSourceConfig.untrack(fn);
5711
+ return fn();
5712
+ } finally {
5713
+ Listener = listener;
5714
+ }
5715
+ }
5716
+ function onMount(fn) {
5717
+ createEffect(() => untrack(fn));
5718
+ }
5719
+ function onCleanup(fn) {
5720
+ if (Owner === null);
5721
+ else if (Owner.cleanups === null) Owner.cleanups = [fn];
5722
+ else Owner.cleanups.push(fn);
5723
+ return fn;
5724
+ }
5725
+ function getListener() {
5726
+ return Listener;
5727
+ }
5728
+ function startTransition(fn) {
5729
+ if (Transition && Transition.running) {
5730
+ fn();
5731
+ return Transition.done;
5732
+ }
5733
+ const l = Listener;
5734
+ const o = Owner;
5735
+ return Promise.resolve().then(() => {
5736
+ Listener = l;
5737
+ Owner = o;
5738
+ let t;
5739
+ if (Scheduler || SuspenseContext) {
5740
+ t = Transition || (Transition = {
5741
+ sources: /* @__PURE__ */ new Set(),
5742
+ effects: [],
5743
+ promises: /* @__PURE__ */ new Set(),
5744
+ disposed: /* @__PURE__ */ new Set(),
5745
+ queue: /* @__PURE__ */ new Set(),
5746
+ running: true
5747
+ });
5748
+ t.done || (t.done = new Promise((res) => t.resolve = res));
5749
+ t.running = true;
5750
+ }
5751
+ runUpdates(fn, false);
5752
+ Listener = Owner = null;
5753
+ return t ? t.done : void 0;
5754
+ });
6413
5755
  }
6414
-
6415
- .field-row {
6416
- display: grid;
6417
- gap: 10px;
6418
- margin-top: 12px;
5756
+ var [transPending, setTransPending] = /* @__PURE__ */ createSignal(false);
5757
+ function useContext(context) {
5758
+ let value;
5759
+ return Owner && Owner.context && (value = Owner.context[context.id]) !== void 0 ? value : context.defaultValue;
5760
+ }
5761
+ var SuspenseContext;
5762
+ function readSignal() {
5763
+ const runningTransition = Transition && Transition.running;
5764
+ if (this.sources && (runningTransition ? this.tState : this.state)) if ((runningTransition ? this.tState : this.state) === STALE) updateComputation(this);
5765
+ else {
5766
+ const updates = Updates;
5767
+ Updates = null;
5768
+ runUpdates(() => lookUpstream(this), false);
5769
+ Updates = updates;
5770
+ }
5771
+ if (Listener) {
5772
+ const sSlot = this.observers ? this.observers.length : 0;
5773
+ if (!Listener.sources) {
5774
+ Listener.sources = [this];
5775
+ Listener.sourceSlots = [sSlot];
5776
+ } else {
5777
+ Listener.sources.push(this);
5778
+ Listener.sourceSlots.push(sSlot);
5779
+ }
5780
+ if (!this.observers) {
5781
+ this.observers = [Listener];
5782
+ this.observerSlots = [Listener.sources.length - 1];
5783
+ } else {
5784
+ this.observers.push(Listener);
5785
+ this.observerSlots.push(Listener.sources.length - 1);
5786
+ }
5787
+ }
5788
+ if (runningTransition && Transition.sources.has(this)) return this.tValue;
5789
+ return this.value;
5790
+ }
5791
+ function writeSignal(node, value, isComp) {
5792
+ let current = Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value;
5793
+ if (!node.comparator || !node.comparator(current, value)) {
5794
+ if (Transition) {
5795
+ const TransitionRunning = Transition.running;
5796
+ if (TransitionRunning || !isComp && Transition.sources.has(node)) {
5797
+ Transition.sources.add(node);
5798
+ node.tValue = value;
5799
+ }
5800
+ if (!TransitionRunning) node.value = value;
5801
+ } else node.value = value;
5802
+ if (node.observers && node.observers.length) runUpdates(() => {
5803
+ for (let i = 0; i < node.observers.length; i += 1) {
5804
+ const o = node.observers[i];
5805
+ const TransitionRunning = Transition && Transition.running;
5806
+ if (TransitionRunning && Transition.disposed.has(o)) continue;
5807
+ if (TransitionRunning ? !o.tState : !o.state) {
5808
+ if (o.pure) Updates.push(o);
5809
+ else Effects.push(o);
5810
+ if (o.observers) markDownstream(o);
5811
+ }
5812
+ if (!TransitionRunning) o.state = STALE;
5813
+ else o.tState = STALE;
5814
+ }
5815
+ if (Updates.length > 1e6) {
5816
+ Updates = [];
5817
+ throw new Error();
5818
+ }
5819
+ }, false);
5820
+ }
5821
+ return value;
6419
5822
  }
6420
-
6421
- .field-row--inline {
6422
- grid-template-columns: repeat(2, minmax(0, 1fr));
5823
+ function updateComputation(node) {
5824
+ if (!node.fn) return;
5825
+ cleanNode(node);
5826
+ const time = ExecCount;
5827
+ runComputation(node, Transition && Transition.running && Transition.sources.has(node) ? node.tValue : node.value, time);
5828
+ if (Transition && !Transition.running && Transition.sources.has(node)) queueMicrotask(() => {
5829
+ runUpdates(() => {
5830
+ Transition && (Transition.running = true);
5831
+ Listener = Owner = node;
5832
+ runComputation(node, node.tValue, time);
5833
+ Listener = Owner = null;
5834
+ }, false);
5835
+ });
6423
5836
  }
6424
-
6425
- .field-label {
6426
- display: block;
6427
- margin-bottom: 6px;
6428
- color: var(--muted);
6429
- font-size: 0.78rem;
6430
- font-weight: 700;
6431
- letter-spacing: 0.08em;
6432
- text-transform: uppercase;
5837
+ function runComputation(node, value, time) {
5838
+ let nextValue;
5839
+ const owner = Owner, listener = Listener;
5840
+ Listener = Owner = node;
5841
+ try {
5842
+ nextValue = node.fn(value);
5843
+ } catch (err) {
5844
+ if (node.pure) if (Transition && Transition.running) {
5845
+ node.tState = STALE;
5846
+ node.tOwned && node.tOwned.forEach(cleanNode);
5847
+ node.tOwned = void 0;
5848
+ } else {
5849
+ node.state = STALE;
5850
+ node.owned && node.owned.forEach(cleanNode);
5851
+ node.owned = null;
5852
+ }
5853
+ node.updatedAt = time + 1;
5854
+ return handleError(err);
5855
+ } finally {
5856
+ Listener = listener;
5857
+ Owner = owner;
5858
+ }
5859
+ if (!node.updatedAt || node.updatedAt <= time) {
5860
+ if (node.updatedAt != null && "observers" in node) writeSignal(node, nextValue, true);
5861
+ else if (Transition && Transition.running && node.pure) {
5862
+ if (!Transition.sources.has(node)) node.value = nextValue;
5863
+ Transition.sources.add(node);
5864
+ node.tValue = nextValue;
5865
+ } else node.value = nextValue;
5866
+ node.updatedAt = time;
5867
+ }
5868
+ }
5869
+ function createComputation(fn, init, pure, state = STALE, options) {
5870
+ const c = {
5871
+ fn,
5872
+ state,
5873
+ updatedAt: null,
5874
+ owned: null,
5875
+ sources: null,
5876
+ sourceSlots: null,
5877
+ cleanups: null,
5878
+ value: init,
5879
+ owner: Owner,
5880
+ context: Owner ? Owner.context : null,
5881
+ pure
5882
+ };
5883
+ if (Transition && Transition.running) {
5884
+ c.state = 0;
5885
+ c.tState = state;
5886
+ }
5887
+ if (Owner === null);
5888
+ else if (Owner !== UNOWNED) if (Transition && Transition.running && Owner.pure) if (!Owner.tOwned) Owner.tOwned = [c];
5889
+ else Owner.tOwned.push(c);
5890
+ else if (!Owner.owned) Owner.owned = [c];
5891
+ else Owner.owned.push(c);
5892
+ if (ExternalSourceConfig && c.fn) {
5893
+ const sourceFn = c.fn;
5894
+ const [track, trigger] = createSignal(void 0, { equals: false });
5895
+ const ordinary = ExternalSourceConfig.factory(sourceFn, trigger);
5896
+ onCleanup(() => ordinary.dispose());
5897
+ let inTransition;
5898
+ const triggerInTransition = () => startTransition(trigger).then(() => {
5899
+ if (inTransition) {
5900
+ inTransition.dispose();
5901
+ inTransition = void 0;
5902
+ }
5903
+ });
5904
+ c.fn = (x) => {
5905
+ track();
5906
+ if (Transition && Transition.running) {
5907
+ if (!inTransition) inTransition = ExternalSourceConfig.factory(sourceFn, triggerInTransition);
5908
+ return inTransition.track(x);
5909
+ }
5910
+ return ordinary.track(x);
5911
+ };
5912
+ }
5913
+ return c;
5914
+ }
5915
+ function runTop(node) {
5916
+ const runningTransition = Transition && Transition.running;
5917
+ if ((runningTransition ? node.tState : node.state) === 0) return;
5918
+ if ((runningTransition ? node.tState : node.state) === PENDING) return lookUpstream(node);
5919
+ if (node.suspense && untrack(node.suspense.inFallback)) return node.suspense.effects.push(node);
5920
+ const ancestors = [node];
5921
+ while ((node = node.owner) && (!node.updatedAt || node.updatedAt < ExecCount)) {
5922
+ if (runningTransition && Transition.disposed.has(node)) return;
5923
+ if (runningTransition ? node.tState : node.state) ancestors.push(node);
5924
+ }
5925
+ for (let i = ancestors.length - 1; i >= 0; i--) {
5926
+ node = ancestors[i];
5927
+ if (runningTransition) {
5928
+ let top = node, prev = ancestors[i + 1];
5929
+ while ((top = top.owner) && top !== prev) if (Transition.disposed.has(top)) return;
5930
+ }
5931
+ if ((runningTransition ? node.tState : node.state) === STALE) updateComputation(node);
5932
+ else if ((runningTransition ? node.tState : node.state) === PENDING) {
5933
+ const updates = Updates;
5934
+ Updates = null;
5935
+ runUpdates(() => lookUpstream(node, ancestors[0]), false);
5936
+ Updates = updates;
5937
+ }
5938
+ }
6433
5939
  }
6434
-
6435
- .meeting-list {
6436
- padding: 14px;
6437
- overflow: auto;
5940
+ function runUpdates(fn, init) {
5941
+ if (Updates) return fn();
5942
+ let wait = false;
5943
+ if (!init) Updates = [];
5944
+ if (Effects) wait = true;
5945
+ else Effects = [];
5946
+ ExecCount++;
5947
+ try {
5948
+ const res = fn();
5949
+ completeUpdates(wait);
5950
+ return res;
5951
+ } catch (err) {
5952
+ if (!wait) Effects = null;
5953
+ Updates = null;
5954
+ handleError(err);
5955
+ }
5956
+ }
5957
+ function completeUpdates(wait) {
5958
+ if (Updates) {
5959
+ if (Scheduler && Transition && Transition.running) scheduleQueue(Updates);
5960
+ else runQueue(Updates);
5961
+ Updates = null;
5962
+ }
5963
+ if (wait) return;
5964
+ let res;
5965
+ if (Transition) {
5966
+ if (!Transition.promises.size && !Transition.queue.size) {
5967
+ const sources = Transition.sources;
5968
+ const disposed = Transition.disposed;
5969
+ Effects.push.apply(Effects, Transition.effects);
5970
+ res = Transition.resolve;
5971
+ for (const e of Effects) {
5972
+ "tState" in e && (e.state = e.tState);
5973
+ delete e.tState;
5974
+ }
5975
+ Transition = null;
5976
+ runUpdates(() => {
5977
+ for (const d of disposed) cleanNode(d);
5978
+ for (const v of sources) {
5979
+ v.value = v.tValue;
5980
+ if (v.owned) for (let i = 0, len = v.owned.length; i < len; i++) cleanNode(v.owned[i]);
5981
+ if (v.tOwned) v.owned = v.tOwned;
5982
+ delete v.tValue;
5983
+ delete v.tOwned;
5984
+ v.tState = 0;
5985
+ }
5986
+ setTransPending(false);
5987
+ }, false);
5988
+ } else if (Transition.running) {
5989
+ Transition.running = false;
5990
+ Transition.effects.push.apply(Transition.effects, Effects);
5991
+ Effects = null;
5992
+ setTransPending(true);
5993
+ return;
5994
+ }
5995
+ }
5996
+ const e = Effects;
5997
+ Effects = null;
5998
+ if (e.length) runUpdates(() => runEffects(e), false);
5999
+ if (res) res();
6000
+ }
6001
+ function runQueue(queue) {
6002
+ for (let i = 0; i < queue.length; i++) runTop(queue[i]);
6003
+ }
6004
+ function scheduleQueue(queue) {
6005
+ for (let i = 0; i < queue.length; i++) {
6006
+ const item = queue[i];
6007
+ const tasks = Transition.queue;
6008
+ if (!tasks.has(item)) {
6009
+ tasks.add(item);
6010
+ Scheduler(() => {
6011
+ tasks.delete(item);
6012
+ runUpdates(() => {
6013
+ Transition.running = true;
6014
+ runTop(item);
6015
+ }, false);
6016
+ Transition && (Transition.running = false);
6017
+ });
6018
+ }
6019
+ }
6438
6020
  }
6439
-
6440
- .folder-panel {
6441
- display: grid;
6442
- gap: 14px;
6021
+ function runUserEffects(queue) {
6022
+ let i, userLength = 0;
6023
+ for (i = 0; i < queue.length; i++) {
6024
+ const e = queue[i];
6025
+ if (!e.user) runTop(e);
6026
+ else queue[userLength++] = e;
6027
+ }
6028
+ if (sharedConfig.context) {
6029
+ if (sharedConfig.count) {
6030
+ sharedConfig.effects || (sharedConfig.effects = []);
6031
+ sharedConfig.effects.push(...queue.slice(0, userLength));
6032
+ return;
6033
+ }
6034
+ setHydrateContext();
6035
+ }
6036
+ if (sharedConfig.effects && (sharedConfig.done || !sharedConfig.count)) {
6037
+ queue = [...sharedConfig.effects, ...queue];
6038
+ userLength += sharedConfig.effects.length;
6039
+ delete sharedConfig.effects;
6040
+ }
6041
+ for (i = 0; i < userLength; i++) runTop(queue[i]);
6042
+ }
6043
+ function lookUpstream(node, ignore) {
6044
+ const runningTransition = Transition && Transition.running;
6045
+ if (runningTransition) node.tState = 0;
6046
+ else node.state = 0;
6047
+ for (let i = 0; i < node.sources.length; i += 1) {
6048
+ const source = node.sources[i];
6049
+ if (source.sources) {
6050
+ const state = runningTransition ? source.tState : source.state;
6051
+ if (state === STALE) {
6052
+ if (source !== ignore && (!source.updatedAt || source.updatedAt < ExecCount)) runTop(source);
6053
+ } else if (state === PENDING) lookUpstream(source, ignore);
6054
+ }
6055
+ }
6443
6056
  }
6444
-
6445
- .folder-panel__head h2 {
6446
- margin: 0;
6447
- font-size: 0.92rem;
6448
- letter-spacing: 0.08em;
6449
- text-transform: uppercase;
6057
+ function markDownstream(node) {
6058
+ const runningTransition = Transition && Transition.running;
6059
+ for (let i = 0; i < node.observers.length; i += 1) {
6060
+ const o = node.observers[i];
6061
+ if (runningTransition ? !o.tState : !o.state) {
6062
+ if (runningTransition) o.tState = PENDING;
6063
+ else o.state = PENDING;
6064
+ if (o.pure) Updates.push(o);
6065
+ else Effects.push(o);
6066
+ o.observers && markDownstream(o);
6067
+ }
6068
+ }
6450
6069
  }
6451
-
6452
- .folder-panel__head p {
6453
- margin: 6px 0 0;
6454
- color: var(--muted);
6455
- font-size: 0.9rem;
6070
+ function cleanNode(node) {
6071
+ let i;
6072
+ if (node.sources) while (node.sources.length) {
6073
+ const source = node.sources.pop(), index = node.sourceSlots.pop(), obs = source.observers;
6074
+ if (obs && obs.length) {
6075
+ const n = obs.pop(), s = source.observerSlots.pop();
6076
+ if (index < obs.length) {
6077
+ n.sourceSlots[s] = index;
6078
+ obs[index] = n;
6079
+ source.observerSlots[index] = s;
6080
+ }
6081
+ }
6082
+ }
6083
+ if (node.tOwned) {
6084
+ for (i = node.tOwned.length - 1; i >= 0; i--) cleanNode(node.tOwned[i]);
6085
+ delete node.tOwned;
6086
+ }
6087
+ if (Transition && Transition.running && node.pure) reset(node, true);
6088
+ else if (node.owned) {
6089
+ for (i = node.owned.length - 1; i >= 0; i--) cleanNode(node.owned[i]);
6090
+ node.owned = null;
6091
+ }
6092
+ if (node.cleanups) {
6093
+ for (i = node.cleanups.length - 1; i >= 0; i--) node.cleanups[i]();
6094
+ node.cleanups = null;
6095
+ }
6096
+ if (Transition && Transition.running) node.tState = 0;
6097
+ else node.state = 0;
6456
6098
  }
6457
-
6458
- .folder-list {
6459
- display: grid;
6460
- gap: 10px;
6099
+ function reset(node, top) {
6100
+ if (!top) {
6101
+ node.tState = 0;
6102
+ Transition.disposed.add(node);
6103
+ }
6104
+ if (node.owned) for (let i = 0; i < node.owned.length; i++) reset(node.owned[i]);
6461
6105
  }
6462
-
6463
- .folder-row {
6464
- width: 100%;
6465
- display: grid;
6466
- gap: 4px;
6467
- text-align: left;
6468
- padding: 12px 14px;
6469
- border: 1px solid transparent;
6470
- border-radius: 16px;
6471
- background: rgba(255, 255, 255, 0.72);
6472
- color: inherit;
6473
- cursor: pointer;
6474
- transition: transform 140ms ease, border-color 140ms ease, background 140ms ease;
6106
+ function castError(err) {
6107
+ if (err instanceof Error) return err;
6108
+ return new Error(typeof err === "string" ? err : "Unknown error", { cause: err });
6475
6109
  }
6476
-
6477
- .folder-row:hover,
6478
- .folder-row[data-selected="true"] {
6479
- transform: translateY(-1px);
6480
- border-color: rgba(163, 79, 47, 0.26);
6481
- background: var(--panel-strong);
6110
+ function runErrors(err, fns, owner) {
6111
+ try {
6112
+ for (const f of fns) f(err);
6113
+ } catch (e) {
6114
+ handleError(e, owner && owner.owner || null);
6115
+ }
6116
+ }
6117
+ function handleError(err, owner = Owner) {
6118
+ const fns = ERROR && owner && owner.context && owner.context[ERROR];
6119
+ const error = castError(err);
6120
+ if (!fns) throw error;
6121
+ if (Effects) Effects.push({
6122
+ fn() {
6123
+ runErrors(error, fns, owner);
6124
+ },
6125
+ state: STALE
6126
+ });
6127
+ else runErrors(error, fns, owner);
6128
+ }
6129
+ var FALLBACK = Symbol("fallback");
6130
+ function dispose(d) {
6131
+ for (let i = 0; i < d.length; i++) d[i]();
6132
+ }
6133
+ function mapArray(list, mapFn, options = {}) {
6134
+ let items = [], mapped = [], disposers = [], len = 0, indexes = mapFn.length > 1 ? [] : null;
6135
+ onCleanup(() => dispose(disposers));
6136
+ return () => {
6137
+ let newItems = list() || [], newLen = newItems.length, i, j;
6138
+ newItems[$TRACK];
6139
+ return untrack(() => {
6140
+ let newIndices, newIndicesNext, temp, tempdisposers, tempIndexes, start, end, newEnd, item;
6141
+ if (newLen === 0) {
6142
+ if (len !== 0) {
6143
+ dispose(disposers);
6144
+ disposers = [];
6145
+ items = [];
6146
+ mapped = [];
6147
+ len = 0;
6148
+ indexes && (indexes = []);
6149
+ }
6150
+ if (options.fallback) {
6151
+ items = [FALLBACK];
6152
+ mapped[0] = createRoot((disposer) => {
6153
+ disposers[0] = disposer;
6154
+ return options.fallback();
6155
+ });
6156
+ len = 1;
6157
+ }
6158
+ } else if (len === 0) {
6159
+ mapped = new Array(newLen);
6160
+ for (j = 0; j < newLen; j++) {
6161
+ items[j] = newItems[j];
6162
+ mapped[j] = createRoot(mapper);
6163
+ }
6164
+ len = newLen;
6165
+ } else {
6166
+ temp = new Array(newLen);
6167
+ tempdisposers = new Array(newLen);
6168
+ indexes && (tempIndexes = new Array(newLen));
6169
+ for (start = 0, end = Math.min(len, newLen); start < end && items[start] === newItems[start]; start++);
6170
+ for (end = len - 1, newEnd = newLen - 1; end >= start && newEnd >= start && items[end] === newItems[newEnd]; end--, newEnd--) {
6171
+ temp[newEnd] = mapped[end];
6172
+ tempdisposers[newEnd] = disposers[end];
6173
+ indexes && (tempIndexes[newEnd] = indexes[end]);
6174
+ }
6175
+ newIndices = /* @__PURE__ */ new Map();
6176
+ newIndicesNext = new Array(newEnd + 1);
6177
+ for (j = newEnd; j >= start; j--) {
6178
+ item = newItems[j];
6179
+ i = newIndices.get(item);
6180
+ newIndicesNext[j] = i === void 0 ? -1 : i;
6181
+ newIndices.set(item, j);
6182
+ }
6183
+ for (i = start; i <= end; i++) {
6184
+ item = items[i];
6185
+ j = newIndices.get(item);
6186
+ if (j !== void 0 && j !== -1) {
6187
+ temp[j] = mapped[i];
6188
+ tempdisposers[j] = disposers[i];
6189
+ indexes && (tempIndexes[j] = indexes[i]);
6190
+ j = newIndicesNext[j];
6191
+ newIndices.set(item, j);
6192
+ } else disposers[i]();
6193
+ }
6194
+ for (j = start; j < newLen; j++) if (j in temp) {
6195
+ mapped[j] = temp[j];
6196
+ disposers[j] = tempdisposers[j];
6197
+ if (indexes) {
6198
+ indexes[j] = tempIndexes[j];
6199
+ indexes[j](j);
6200
+ }
6201
+ } else mapped[j] = createRoot(mapper);
6202
+ mapped = mapped.slice(0, len = newLen);
6203
+ items = newItems.slice(0);
6204
+ }
6205
+ return mapped;
6206
+ });
6207
+ function mapper(disposer) {
6208
+ disposers[j] = disposer;
6209
+ if (indexes) {
6210
+ const [s, set] = createSignal(j);
6211
+ indexes[j] = set;
6212
+ return mapFn(newItems[j], s);
6213
+ }
6214
+ return mapFn(newItems[j]);
6215
+ }
6216
+ };
6482
6217
  }
6483
-
6484
- .folder-row__title {
6485
- font-weight: 700;
6218
+ var hydrationEnabled = false;
6219
+ function createComponent(Comp, props) {
6220
+ if (hydrationEnabled) {
6221
+ if (sharedConfig.context) {
6222
+ const c = sharedConfig.context;
6223
+ setHydrateContext(nextHydrateContext());
6224
+ const r = untrack(() => Comp(props || {}));
6225
+ setHydrateContext(c);
6226
+ return r;
6227
+ }
6228
+ }
6229
+ return untrack(() => Comp(props || {}));
6230
+ }
6231
+ var narrowedError = (name) => \`Stale read from <\${name}>.\`;
6232
+ function For(props) {
6233
+ const fallback = "fallback" in props && { fallback: () => props.fallback };
6234
+ return createMemo(mapArray(() => props.each, props.children, fallback || void 0));
6235
+ }
6236
+ function Show(props) {
6237
+ const keyed = props.keyed;
6238
+ const conditionValue = createMemo(() => props.when, void 0, void 0);
6239
+ const condition = keyed ? conditionValue : createMemo(conditionValue, void 0, { equals: (a, b) => !a === !b });
6240
+ return createMemo(() => {
6241
+ const c = condition();
6242
+ if (c) {
6243
+ const child = props.children;
6244
+ return typeof child === "function" && child.length > 0 ? untrack(() => child(keyed ? c : () => {
6245
+ if (!untrack(condition)) throw narrowedError("Show");
6246
+ return conditionValue();
6247
+ })) : child;
6248
+ }
6249
+ return props.fallback;
6250
+ }, void 0, void 0);
6486
6251
  }
6487
-
6488
- .folder-row__meta {
6489
- color: var(--muted);
6490
- font-size: 0.88rem;
6252
+ //#endregion
6253
+ //#region node_modules/solid-js/web/dist/web.js
6254
+ var memo = (fn) => createMemo(() => fn());
6255
+ function reconcileArrays(parentNode, a, b) {
6256
+ let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = a[aEnd - 1].nextSibling, map = null;
6257
+ while (aStart < aEnd || bStart < bEnd) {
6258
+ if (a[aStart] === b[bStart]) {
6259
+ aStart++;
6260
+ bStart++;
6261
+ continue;
6262
+ }
6263
+ while (a[aEnd - 1] === b[bEnd - 1]) {
6264
+ aEnd--;
6265
+ bEnd--;
6266
+ }
6267
+ if (aEnd === aStart) {
6268
+ const node = bEnd < bLength ? bStart ? b[bStart - 1].nextSibling : b[bEnd - bStart] : after;
6269
+ while (bStart < bEnd) parentNode.insertBefore(b[bStart++], node);
6270
+ } else if (bEnd === bStart) while (aStart < aEnd) {
6271
+ if (!map || !map.has(a[aStart])) a[aStart].remove();
6272
+ aStart++;
6273
+ }
6274
+ else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
6275
+ const node = a[--aEnd].nextSibling;
6276
+ parentNode.insertBefore(b[bStart++], a[aStart++].nextSibling);
6277
+ parentNode.insertBefore(b[--bEnd], node);
6278
+ a[aEnd] = b[bEnd];
6279
+ } else {
6280
+ if (!map) {
6281
+ map = /* @__PURE__ */ new Map();
6282
+ let i = bStart;
6283
+ while (i < bEnd) map.set(b[i], i++);
6284
+ }
6285
+ const index = map.get(a[aStart]);
6286
+ if (index != null) if (bStart < index && index < bEnd) {
6287
+ let i = aStart, sequence = 1, t;
6288
+ while (++i < aEnd && i < bEnd) {
6289
+ if ((t = map.get(a[i])) == null || t !== index + sequence) break;
6290
+ sequence++;
6291
+ }
6292
+ if (sequence > index - bStart) {
6293
+ const node = a[aStart];
6294
+ while (bStart < index) parentNode.insertBefore(b[bStart++], node);
6295
+ } else parentNode.replaceChild(b[bStart++], a[aStart++]);
6296
+ } else aStart++;
6297
+ else a[aStart++].remove();
6298
+ }
6299
+ }
6491
6300
  }
6492
-
6493
- .folder-empty {
6494
- color: var(--muted);
6495
- font-size: 0.92rem;
6301
+ var $$EVENTS = "_$DX_DELEGATE";
6302
+ function render(code, element, init, options = {}) {
6303
+ let disposer;
6304
+ createRoot((dispose) => {
6305
+ disposer = dispose;
6306
+ element === document ? code() : insert(element, code(), element.firstChild ? null : void 0, init);
6307
+ }, options.owner);
6308
+ return () => {
6309
+ disposer();
6310
+ element.textContent = "";
6311
+ };
6496
6312
  }
6497
-
6498
- .folder-empty--error {
6499
- color: var(--error);
6313
+ function template(html, isImportNode, isSVG, isMathML) {
6314
+ let node;
6315
+ const create = () => {
6316
+ const t = isMathML ? document.createElementNS("http://www.w3.org/1998/Math/MathML", "template") : document.createElement("template");
6317
+ t.innerHTML = html;
6318
+ return isSVG ? t.content.firstChild.firstChild : isMathML ? t.firstChild : t.content.firstChild;
6319
+ };
6320
+ const fn = isImportNode ? () => untrack(() => document.importNode(node || (node = create()), true)) : () => (node || (node = create())).cloneNode(true);
6321
+ fn.cloneNode = fn;
6322
+ return fn;
6323
+ }
6324
+ function delegateEvents(eventNames, document = window.document) {
6325
+ const e = document[$$EVENTS] || (document[$$EVENTS] = /* @__PURE__ */ new Set());
6326
+ for (let i = 0, l = eventNames.length; i < l; i++) {
6327
+ const name = eventNames[i];
6328
+ if (!e.has(name)) {
6329
+ e.add(name);
6330
+ document.addEventListener(name, eventHandler);
6331
+ }
6332
+ }
6500
6333
  }
6501
-
6502
- .meeting-row {
6503
- width: 100%;
6504
- display: grid;
6505
- gap: 4px;
6506
- text-align: left;
6507
- margin: 0 0 10px;
6508
- padding: 14px 16px;
6509
- border: 1px solid transparent;
6510
- border-radius: 18px;
6511
- background: rgba(255, 255, 255, 0.72);
6512
- color: inherit;
6513
- cursor: pointer;
6514
- transition: transform 140ms ease, border-color 140ms ease, background 140ms ease;
6515
- }
6516
-
6517
- .meeting-row:hover,
6518
- .meeting-row[data-selected="true"] {
6519
- transform: translateY(-1px);
6520
- border-color: rgba(13, 106, 109, 0.25);
6521
- background: var(--panel-strong);
6522
- }
6523
-
6524
- .meeting-row__title {
6525
- font-weight: 600;
6334
+ function setAttribute(node, name, value) {
6335
+ if (isHydrating(node)) return;
6336
+ if (value == null) node.removeAttribute(name);
6337
+ else node.setAttribute(name, value);
6338
+ }
6339
+ function addEventListener(node, name, handler, delegate) {
6340
+ if (delegate) if (Array.isArray(handler)) {
6341
+ node[\`$$\${name}\`] = handler[0];
6342
+ node[\`$$\${name}Data\`] = handler[1];
6343
+ } else node[\`$$\${name}\`] = handler;
6344
+ else if (Array.isArray(handler)) {
6345
+ const handlerFn = handler[0];
6346
+ node.addEventListener(name, handler[0] = (e) => handlerFn.call(node, handler[1], e));
6347
+ } else node.addEventListener(name, handler, typeof handler !== "function" && handler);
6348
+ }
6349
+ function insert(parent, accessor, marker, initial) {
6350
+ if (marker !== void 0 && !initial) initial = [];
6351
+ if (typeof accessor !== "function") return insertExpression(parent, accessor, initial, marker);
6352
+ createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);
6353
+ }
6354
+ function isHydrating(node) {
6355
+ return !!sharedConfig.context && !sharedConfig.done && (!node || node.isConnected);
6356
+ }
6357
+ function eventHandler(e) {
6358
+ if (sharedConfig.registry && sharedConfig.events) {
6359
+ if (sharedConfig.events.find(([el, ev]) => ev === e)) return;
6360
+ }
6361
+ let node = e.target;
6362
+ const key = \`$$\${e.type}\`;
6363
+ const oriTarget = e.target;
6364
+ const oriCurrentTarget = e.currentTarget;
6365
+ const retarget = (value) => Object.defineProperty(e, "target", {
6366
+ configurable: true,
6367
+ value
6368
+ });
6369
+ const handleNode = () => {
6370
+ const handler = node[key];
6371
+ if (handler && !node.disabled) {
6372
+ const data = node[\`\${key}Data\`];
6373
+ data !== void 0 ? handler.call(node, data, e) : handler.call(node, e);
6374
+ if (e.cancelBubble) return;
6375
+ }
6376
+ node.host && typeof node.host !== "string" && !node.host._$host && node.contains(e.target) && retarget(node.host);
6377
+ return true;
6378
+ };
6379
+ const walkUpTree = () => {
6380
+ while (handleNode() && (node = node._$host || node.parentNode || node.host));
6381
+ };
6382
+ Object.defineProperty(e, "currentTarget", {
6383
+ configurable: true,
6384
+ get() {
6385
+ return node || document;
6386
+ }
6387
+ });
6388
+ if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = _$HY.done = true;
6389
+ if (e.composedPath) {
6390
+ const path = e.composedPath();
6391
+ retarget(path[0]);
6392
+ for (let i = 0; i < path.length - 2; i++) {
6393
+ node = path[i];
6394
+ if (!handleNode()) break;
6395
+ if (node._$host) {
6396
+ node = node._$host;
6397
+ walkUpTree();
6398
+ break;
6399
+ }
6400
+ if (node.parentNode === oriCurrentTarget) break;
6401
+ }
6402
+ } else walkUpTree();
6403
+ retarget(oriTarget);
6404
+ }
6405
+ function insertExpression(parent, value, current, marker, unwrapArray) {
6406
+ const hydrating = isHydrating(parent);
6407
+ if (hydrating) {
6408
+ !current && (current = [...parent.childNodes]);
6409
+ let cleaned = [];
6410
+ for (let i = 0; i < current.length; i++) {
6411
+ const node = current[i];
6412
+ if (node.nodeType === 8 && node.data.slice(0, 2) === "!$") node.remove();
6413
+ else cleaned.push(node);
6414
+ }
6415
+ current = cleaned;
6416
+ }
6417
+ while (typeof current === "function") current = current();
6418
+ if (value === current) return current;
6419
+ const t = typeof value, multi = marker !== void 0;
6420
+ parent = multi && current[0] && current[0].parentNode || parent;
6421
+ if (t === "string" || t === "number") {
6422
+ if (hydrating) return current;
6423
+ if (t === "number") {
6424
+ value = value.toString();
6425
+ if (value === current) return current;
6426
+ }
6427
+ if (multi) {
6428
+ let node = current[0];
6429
+ if (node && node.nodeType === 3) node.data !== value && (node.data = value);
6430
+ else node = document.createTextNode(value);
6431
+ current = cleanChildren(parent, current, marker, node);
6432
+ } else if (current !== "" && typeof current === "string") current = parent.firstChild.data = value;
6433
+ else current = parent.textContent = value;
6434
+ } else if (value == null || t === "boolean") {
6435
+ if (hydrating) return current;
6436
+ current = cleanChildren(parent, current, marker);
6437
+ } else if (t === "function") {
6438
+ createRenderEffect(() => {
6439
+ let v = value();
6440
+ while (typeof v === "function") v = v();
6441
+ current = insertExpression(parent, v, current, marker);
6442
+ });
6443
+ return () => current;
6444
+ } else if (Array.isArray(value)) {
6445
+ const array = [];
6446
+ const currentArray = current && Array.isArray(current);
6447
+ if (normalizeIncomingArray(array, value, current, unwrapArray)) {
6448
+ createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));
6449
+ return () => current;
6450
+ }
6451
+ if (hydrating) {
6452
+ if (!array.length) return current;
6453
+ if (marker === void 0) return current = [...parent.childNodes];
6454
+ let node = array[0];
6455
+ if (node.parentNode !== parent) return current;
6456
+ const nodes = [node];
6457
+ while ((node = node.nextSibling) !== marker) nodes.push(node);
6458
+ return current = nodes;
6459
+ }
6460
+ if (array.length === 0) {
6461
+ current = cleanChildren(parent, current, marker);
6462
+ if (multi) return current;
6463
+ } else if (currentArray) if (current.length === 0) appendNodes(parent, array, marker);
6464
+ else reconcileArrays(parent, current, array);
6465
+ else {
6466
+ current && cleanChildren(parent);
6467
+ appendNodes(parent, array);
6468
+ }
6469
+ current = array;
6470
+ } else if (value.nodeType) {
6471
+ if (hydrating && value.parentNode) return current = multi ? [value] : value;
6472
+ if (Array.isArray(current)) {
6473
+ if (multi) return current = cleanChildren(parent, current, marker, value);
6474
+ cleanChildren(parent, current, null, value);
6475
+ } else if (current == null || current === "" || !parent.firstChild) parent.appendChild(value);
6476
+ else parent.replaceChild(value, parent.firstChild);
6477
+ current = value;
6478
+ }
6479
+ return current;
6480
+ }
6481
+ function normalizeIncomingArray(normalized, array, current, unwrap) {
6482
+ let dynamic = false;
6483
+ for (let i = 0, len = array.length; i < len; i++) {
6484
+ let item = array[i], prev = current && current[normalized.length], t;
6485
+ if (item == null || item === true || item === false);
6486
+ else if ((t = typeof item) === "object" && item.nodeType) normalized.push(item);
6487
+ else if (Array.isArray(item)) dynamic = normalizeIncomingArray(normalized, item, prev) || dynamic;
6488
+ else if (t === "function") if (unwrap) {
6489
+ while (typeof item === "function") item = item();
6490
+ dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item], Array.isArray(prev) ? prev : [prev]) || dynamic;
6491
+ } else {
6492
+ normalized.push(item);
6493
+ dynamic = true;
6494
+ }
6495
+ else {
6496
+ const value = String(item);
6497
+ if (prev && prev.nodeType === 3 && prev.data === value) normalized.push(prev);
6498
+ else normalized.push(document.createTextNode(value));
6499
+ }
6500
+ }
6501
+ return dynamic;
6502
+ }
6503
+ function appendNodes(parent, array, marker = null) {
6504
+ for (let i = 0, len = array.length; i < len; i++) parent.insertBefore(array[i], marker);
6505
+ }
6506
+ function cleanChildren(parent, current, marker, replacement) {
6507
+ if (marker === void 0) return parent.textContent = "";
6508
+ const node = replacement || document.createTextNode("");
6509
+ if (current.length) {
6510
+ let inserted = false;
6511
+ for (let i = current.length - 1; i >= 0; i--) {
6512
+ const el = current[i];
6513
+ if (node !== el) {
6514
+ const isParent = el.parentNode === parent;
6515
+ if (!inserted && !i) isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker);
6516
+ else isParent && el.remove();
6517
+ } else inserted = true;
6518
+ }
6519
+ } else parent.insertBefore(node, marker);
6520
+ return [node];
6526
6521
  }
6527
-
6528
- .meeting-row__meta {
6529
- color: var(--muted);
6530
- font-size: 0.92rem;
6522
+ //#endregion
6523
+ //#region node_modules/solid-js/store/dist/store.js
6524
+ var $RAW = Symbol("store-raw"), $NODE = Symbol("store-node"), $HAS = Symbol("store-has"), $SELF = Symbol("store-self");
6525
+ function wrap$1(value) {
6526
+ let p = value[$PROXY];
6527
+ if (!p) {
6528
+ Object.defineProperty(value, $PROXY, { value: p = new Proxy(value, proxyTraps$1) });
6529
+ if (!Array.isArray(value)) {
6530
+ const keys = Object.keys(value), desc = Object.getOwnPropertyDescriptors(value);
6531
+ for (let i = 0, l = keys.length; i < l; i++) {
6532
+ const prop = keys[i];
6533
+ if (desc[prop].get) Object.defineProperty(value, prop, {
6534
+ enumerable: desc[prop].enumerable,
6535
+ get: desc[prop].get.bind(p)
6536
+ });
6537
+ }
6538
+ }
6539
+ }
6540
+ return p;
6541
+ }
6542
+ function isWrappable(obj) {
6543
+ let proto;
6544
+ return obj != null && typeof obj === "object" && (obj[$PROXY] || !(proto = Object.getPrototypeOf(obj)) || proto === Object.prototype || Array.isArray(obj));
6545
+ }
6546
+ function unwrap(item, set = /* @__PURE__ */ new Set()) {
6547
+ let result, unwrapped, v, prop;
6548
+ if (result = item != null && item[$RAW]) return result;
6549
+ if (!isWrappable(item) || set.has(item)) return item;
6550
+ if (Array.isArray(item)) {
6551
+ if (Object.isFrozen(item)) item = item.slice(0);
6552
+ else set.add(item);
6553
+ for (let i = 0, l = item.length; i < l; i++) {
6554
+ v = item[i];
6555
+ if ((unwrapped = unwrap(v, set)) !== v) item[i] = unwrapped;
6556
+ }
6557
+ } else {
6558
+ if (Object.isFrozen(item)) item = Object.assign({}, item);
6559
+ else set.add(item);
6560
+ const keys = Object.keys(item), desc = Object.getOwnPropertyDescriptors(item);
6561
+ for (let i = 0, l = keys.length; i < l; i++) {
6562
+ prop = keys[i];
6563
+ if (desc[prop].get) continue;
6564
+ v = item[prop];
6565
+ if ((unwrapped = unwrap(v, set)) !== v) item[prop] = unwrapped;
6566
+ }
6567
+ }
6568
+ return item;
6531
6569
  }
6532
-
6533
- .meeting-empty {
6534
- padding: 18px;
6535
- color: var(--muted);
6570
+ function getNodes(target, symbol) {
6571
+ let nodes = target[symbol];
6572
+ if (!nodes) Object.defineProperty(target, symbol, { value: nodes = Object.create(null) });
6573
+ return nodes;
6536
6574
  }
6537
-
6538
- .meeting-empty--error {
6539
- color: var(--error);
6575
+ function getNode(nodes, property, value) {
6576
+ if (nodes[property]) return nodes[property];
6577
+ const [s, set] = createSignal(value, {
6578
+ equals: false,
6579
+ internal: true
6580
+ });
6581
+ s.$ = set;
6582
+ return nodes[property] = s;
6583
+ }
6584
+ function proxyDescriptor$1(target, property) {
6585
+ const desc = Reflect.getOwnPropertyDescriptor(target, property);
6586
+ if (!desc || desc.get || !desc.configurable || property === $PROXY || property === $NODE) return desc;
6587
+ delete desc.value;
6588
+ delete desc.writable;
6589
+ desc.get = () => target[$PROXY][property];
6590
+ return desc;
6591
+ }
6592
+ function trackSelf(target) {
6593
+ getListener() && getNode(getNodes(target, $NODE), $SELF)();
6594
+ }
6595
+ function ownKeys(target) {
6596
+ trackSelf(target);
6597
+ return Reflect.ownKeys(target);
6598
+ }
6599
+ var proxyTraps$1 = {
6600
+ get(target, property, receiver) {
6601
+ if (property === $RAW) return target;
6602
+ if (property === $PROXY) return receiver;
6603
+ if (property === $TRACK) {
6604
+ trackSelf(target);
6605
+ return receiver;
6606
+ }
6607
+ const nodes = getNodes(target, $NODE);
6608
+ const tracked = nodes[property];
6609
+ let value = tracked ? tracked() : target[property];
6610
+ if (property === $NODE || property === $HAS || property === "__proto__") return value;
6611
+ if (!tracked) {
6612
+ const desc = Object.getOwnPropertyDescriptor(target, property);
6613
+ if (getListener() && (typeof value !== "function" || target.hasOwnProperty(property)) && !(desc && desc.get)) value = getNode(nodes, property, value)();
6614
+ }
6615
+ return isWrappable(value) ? wrap$1(value) : value;
6616
+ },
6617
+ has(target, property) {
6618
+ if (property === $RAW || property === $PROXY || property === $TRACK || property === $NODE || property === $HAS || property === "__proto__") return true;
6619
+ getListener() && getNode(getNodes(target, $HAS), property)();
6620
+ return property in target;
6621
+ },
6622
+ set() {
6623
+ return true;
6624
+ },
6625
+ deleteProperty() {
6626
+ return true;
6627
+ },
6628
+ ownKeys,
6629
+ getOwnPropertyDescriptor: proxyDescriptor$1
6630
+ };
6631
+ function setProperty(state, property, value, deleting = false) {
6632
+ if (!deleting && state[property] === value) return;
6633
+ const prev = state[property], len = state.length;
6634
+ if (value === void 0) {
6635
+ delete state[property];
6636
+ if (state[$HAS] && state[$HAS][property] && prev !== void 0) state[$HAS][property].$();
6637
+ } else {
6638
+ state[property] = value;
6639
+ if (state[$HAS] && state[$HAS][property] && prev === void 0) state[$HAS][property].$();
6640
+ }
6641
+ let nodes = getNodes(state, $NODE), node;
6642
+ if (node = getNode(nodes, property, prev)) node.$(() => value);
6643
+ if (Array.isArray(state) && state.length !== len) {
6644
+ for (let i = state.length; i < len; i++) (node = nodes[i]) && node.$();
6645
+ (node = getNode(nodes, "length", len)) && node.$(state.length);
6646
+ }
6647
+ (node = nodes[$SELF]) && node.$();
6648
+ }
6649
+ function mergeStoreNode(state, value) {
6650
+ const keys = Object.keys(value);
6651
+ for (let i = 0; i < keys.length; i += 1) {
6652
+ const key = keys[i];
6653
+ setProperty(state, key, value[key]);
6654
+ }
6655
+ }
6656
+ function updateArray(current, next) {
6657
+ if (typeof next === "function") next = next(current);
6658
+ next = unwrap(next);
6659
+ if (Array.isArray(next)) {
6660
+ if (current === next) return;
6661
+ let i = 0, len = next.length;
6662
+ for (; i < len; i++) {
6663
+ const value = next[i];
6664
+ if (current[i] !== value) setProperty(current, i, value);
6665
+ }
6666
+ setProperty(current, "length", len);
6667
+ } else mergeStoreNode(current, next);
6668
+ }
6669
+ function updatePath(current, path, traversed = []) {
6670
+ let part, prev = current;
6671
+ if (path.length > 1) {
6672
+ part = path.shift();
6673
+ const partType = typeof part, isArray = Array.isArray(current);
6674
+ if (Array.isArray(part)) {
6675
+ for (let i = 0; i < part.length; i++) updatePath(current, [part[i]].concat(path), traversed);
6676
+ return;
6677
+ } else if (isArray && partType === "function") {
6678
+ for (let i = 0; i < current.length; i++) if (part(current[i], i)) updatePath(current, [i].concat(path), traversed);
6679
+ return;
6680
+ } else if (isArray && partType === "object") {
6681
+ const { from = 0, to = current.length - 1, by = 1 } = part;
6682
+ for (let i = from; i <= to; i += by) updatePath(current, [i].concat(path), traversed);
6683
+ return;
6684
+ } else if (path.length > 1) {
6685
+ updatePath(current[part], path, [part].concat(traversed));
6686
+ return;
6687
+ }
6688
+ prev = current[part];
6689
+ traversed = [part].concat(traversed);
6690
+ }
6691
+ let value = path[0];
6692
+ if (typeof value === "function") {
6693
+ value = value(prev, traversed);
6694
+ if (value === prev) return;
6695
+ }
6696
+ if (part === void 0 && value == void 0) return;
6697
+ value = unwrap(value);
6698
+ if (part === void 0 || isWrappable(prev) && isWrappable(value) && !Array.isArray(value)) mergeStoreNode(prev, value);
6699
+ else setProperty(current, part, value);
6700
+ }
6701
+ function createStore(...[store, options]) {
6702
+ const unwrappedStore = unwrap(store || {});
6703
+ const isArray = Array.isArray(unwrappedStore);
6704
+ const wrappedStore = wrap$1(unwrappedStore);
6705
+ function setStore(...args) {
6706
+ batch(() => {
6707
+ isArray && args.length === 1 ? updateArray(unwrappedStore, args[0]) : updatePath(unwrappedStore, args);
6708
+ });
6709
+ }
6710
+ return [wrappedStore, setStore];
6540
6711
  }
6541
-
6542
- .detail {
6543
- display: grid;
6544
- grid-template-rows: auto auto 1fr;
6545
- min-width: 0;
6712
+ //#endregion
6713
+ //#region src/transport.ts
6714
+ var granolaTransportPaths = {
6715
+ authLock: "/auth/lock",
6716
+ authLogin: "/auth/login",
6717
+ authLogout: "/auth/logout",
6718
+ authMode: "/auth/mode",
6719
+ authRefresh: "/auth/refresh",
6720
+ authStatus: "/auth/status",
6721
+ authUnlock: "/auth/unlock",
6722
+ events: "/events",
6723
+ exportJobs: "/exports/jobs",
6724
+ exportNotes: "/exports/notes",
6725
+ exportTranscripts: "/exports/transcripts",
6726
+ folderResolve: "/folders/resolve",
6727
+ folders: "/folders",
6728
+ health: "/health",
6729
+ meetingResolve: "/meetings/resolve",
6730
+ meetings: "/meetings",
6731
+ root: "/",
6732
+ serverInfo: "/server/info",
6733
+ syncRun: "/sync",
6734
+ syncEvents: "/sync/events",
6735
+ state: "/state"
6736
+ };
6737
+ function appendSearchParams(path, params) {
6738
+ const url = new URL(path, "http://localhost");
6739
+ for (const [key, value] of Object.entries(params)) {
6740
+ if (value === void 0 || value === false || value === "") continue;
6741
+ url.searchParams.set(key, String(value));
6742
+ }
6743
+ return \`\${url.pathname}\${url.search}\`;
6546
6744
  }
6547
-
6548
- .detail-head {
6549
- display: flex;
6550
- align-items: center;
6551
- justify-content: space-between;
6552
- gap: 18px;
6745
+ function granolaMeetingPath(id) {
6746
+ return \`\${granolaTransportPaths.meetings}/\${encodeURIComponent(id)}\`;
6553
6747
  }
6554
-
6555
- .detail-head h2 {
6556
- margin: 0;
6557
- font-family: var(--serif);
6558
- font-size: clamp(1.8rem, 2.4vw, 2.4rem);
6559
- font-weight: 600;
6748
+ function granolaMeetingResolvePath(query, options = {}) {
6749
+ return appendSearchParams(granolaTransportPaths.meetingResolve, {
6750
+ includeTranscript: options.includeTranscript ? "true" : void 0,
6751
+ q: query
6752
+ });
6560
6753
  }
6561
-
6562
- .state-badge {
6563
- padding: 10px 14px;
6564
- border-radius: 999px;
6565
- background: var(--accent-soft);
6566
- color: var(--accent);
6567
- font-size: 0.92rem;
6568
- font-weight: 700;
6754
+ function granolaMeetingsPath(options = {}) {
6755
+ return appendSearchParams(granolaTransportPaths.meetings, {
6756
+ folderId: options.folderId,
6757
+ limit: options.limit,
6758
+ refresh: options.forceRefresh ? "true" : void 0,
6759
+ search: options.search,
6760
+ sort: options.sort,
6761
+ updatedFrom: options.updatedFrom,
6762
+ updatedTo: options.updatedTo
6763
+ });
6569
6764
  }
6570
-
6571
- .state-badge[data-tone="busy"] { color: var(--warm); background: rgba(163, 79, 47, 0.12); }
6572
- .state-badge[data-tone="error"] { color: var(--error); background: rgba(157, 44, 44, 0.12); }
6573
- .state-badge[data-tone="ok"] { color: var(--ok); background: rgba(36, 107, 79, 0.12); }
6574
-
6575
- .toolbar {
6576
- display: flex;
6577
- flex-wrap: wrap;
6578
- align-items: center;
6579
- justify-content: space-between;
6580
- gap: 14px;
6765
+ function granolaFolderPath(id) {
6766
+ return \`\${granolaTransportPaths.folders}/\${encodeURIComponent(id)}\`;
6581
6767
  }
6582
-
6583
- .toolbar-actions {
6584
- display: flex;
6585
- flex-wrap: wrap;
6586
- gap: 10px;
6768
+ function granolaFolderResolvePath(query) {
6769
+ return appendSearchParams(granolaTransportPaths.folderResolve, { q: query });
6587
6770
  }
6588
-
6589
- .toolbar-form {
6590
- display: grid;
6591
- grid-template-columns: minmax(0, 1fr) auto;
6592
- gap: 10px;
6593
- width: min(440px, 100%);
6771
+ function granolaFoldersPath(options = {}) {
6772
+ return appendSearchParams(granolaTransportPaths.folders, {
6773
+ limit: options.limit,
6774
+ refresh: options.forceRefresh ? "true" : void 0,
6775
+ search: options.search
6776
+ });
6594
6777
  }
6595
-
6596
- .auth-panel,
6597
- .security-panel,
6598
- .jobs-panel {
6599
- padding: 0 24px 18px;
6778
+ function granolaExportJobsPath(options = {}) {
6779
+ return appendSearchParams(granolaTransportPaths.exportJobs, { limit: options.limit });
6600
6780
  }
6601
-
6602
- .security-panel__head h3,
6603
- .auth-panel__head h3,
6604
- .jobs-panel__head h3 {
6605
- margin: 0;
6606
- font-size: 0.92rem;
6607
- letter-spacing: 0.08em;
6608
- text-transform: uppercase;
6781
+ function granolaExportJobRerunPath(id) {
6782
+ return \`\${granolaTransportPaths.exportJobs}/\${encodeURIComponent(id)}/rerun\`;
6609
6783
  }
6610
-
6611
- .security-panel__head p,
6612
- .auth-panel__head p,
6613
- .jobs-panel__head p {
6614
- margin: 6px 0 0;
6615
- color: var(--muted);
6616
- font-size: 0.9rem;
6784
+ //#endregion
6785
+ //#region \0@oxc-project+runtime@0.122.0/helpers/checkPrivateRedeclaration.js
6786
+ function _checkPrivateRedeclaration(e, t) {
6787
+ if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object");
6617
6788
  }
6618
-
6619
- .security-panel__body,
6620
- .auth-panel__body {
6621
- display: grid;
6622
- gap: 12px;
6623
- margin-top: 14px;
6789
+ //#endregion
6790
+ //#region \0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldInitSpec.js
6791
+ function _classPrivateFieldInitSpec(e, t, a) {
6792
+ _checkPrivateRedeclaration(e, t), t.set(e, a);
6624
6793
  }
6625
-
6626
- .auth-card {
6627
- display: grid;
6628
- gap: 12px;
6629
- padding: 14px 16px;
6630
- border: 1px solid var(--line);
6631
- border-radius: 18px;
6632
- background: rgba(255, 255, 255, 0.72);
6794
+ //#endregion
6795
+ //#region \0@oxc-project+runtime@0.122.0/helpers/typeof.js
6796
+ function _typeof(o) {
6797
+ "@babel/helpers - typeof";
6798
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
6799
+ return typeof o;
6800
+ } : function(o) {
6801
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
6802
+ }, _typeof(o);
6633
6803
  }
6634
-
6635
- .auth-card__meta {
6636
- color: var(--muted);
6637
- font-size: 0.9rem;
6804
+ //#endregion
6805
+ //#region \0@oxc-project+runtime@0.122.0/helpers/toPrimitive.js
6806
+ function toPrimitive(t, r) {
6807
+ if ("object" != _typeof(t) || !t) return t;
6808
+ var e = t[Symbol.toPrimitive];
6809
+ if (void 0 !== e) {
6810
+ var i = e.call(t, r || "default");
6811
+ if ("object" != _typeof(i)) return i;
6812
+ throw new TypeError("@@toPrimitive must return a primitive value.");
6813
+ }
6814
+ return ("string" === r ? String : Number)(t);
6638
6815
  }
6639
-
6640
- .auth-card__actions {
6641
- display: flex;
6642
- flex-wrap: wrap;
6643
- gap: 8px;
6816
+ //#endregion
6817
+ //#region \0@oxc-project+runtime@0.122.0/helpers/toPropertyKey.js
6818
+ function toPropertyKey(t) {
6819
+ var i = toPrimitive(t, "string");
6820
+ return "symbol" == _typeof(i) ? i : i + "";
6644
6821
  }
6645
-
6646
- .auth-card__error {
6647
- color: var(--error);
6822
+ //#endregion
6823
+ //#region \0@oxc-project+runtime@0.122.0/helpers/defineProperty.js
6824
+ function _defineProperty(e, r, t) {
6825
+ return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
6826
+ value: t,
6827
+ enumerable: !0,
6828
+ configurable: !0,
6829
+ writable: !0
6830
+ }) : e[r] = t, e;
6648
6831
  }
6649
-
6650
- .jobs-list {
6651
- display: grid;
6652
- gap: 10px;
6653
- margin-top: 14px;
6832
+ //#endregion
6833
+ //#region \0@oxc-project+runtime@0.122.0/helpers/assertClassBrand.js
6834
+ function _assertClassBrand(e, t, n) {
6835
+ if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n;
6836
+ throw new TypeError("Private element is not present on this object");
6654
6837
  }
6655
-
6656
- .job-card {
6657
- display: grid;
6658
- gap: 10px;
6659
- padding: 14px 16px;
6660
- border: 1px solid var(--line);
6661
- border-radius: 18px;
6662
- background: rgba(255, 255, 255, 0.72);
6838
+ //#endregion
6839
+ //#region \0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldSet2.js
6840
+ function _classPrivateFieldSet2(s, a, r) {
6841
+ return s.set(_assertClassBrand(s, a), r), r;
6663
6842
  }
6664
-
6665
- .job-card__head {
6666
- display: flex;
6667
- flex-wrap: wrap;
6668
- align-items: center;
6669
- justify-content: space-between;
6670
- gap: 10px;
6843
+ //#endregion
6844
+ //#region \0@oxc-project+runtime@0.122.0/helpers/classPrivateFieldGet2.js
6845
+ function _classPrivateFieldGet2(s, a) {
6846
+ return s.get(_assertClassBrand(s, a));
6671
6847
  }
6672
-
6673
- .job-card__title {
6674
- font-weight: 700;
6848
+ //#endregion
6849
+ //#region src/server/client.ts
6850
+ function cloneValue(value) {
6851
+ return structuredClone(value);
6675
6852
  }
6676
-
6677
- .job-card__meta {
6678
- color: var(--muted);
6679
- font-size: 0.9rem;
6680
- }
6681
-
6682
- .job-card__status {
6683
- padding: 6px 10px;
6684
- border-radius: 999px;
6685
- background: var(--accent-soft);
6686
- color: var(--accent);
6687
- font-size: 0.82rem;
6688
- font-weight: 700;
6689
- }
6690
-
6691
- .job-card__status[data-status="running"] {
6692
- background: rgba(163, 79, 47, 0.12);
6693
- color: var(--warm);
6694
- }
6695
-
6696
- .job-card__status[data-status="failed"] {
6697
- background: rgba(157, 44, 44, 0.12);
6698
- color: var(--error);
6699
- }
6700
-
6701
- .job-card__status[data-status="completed"] {
6702
- background: rgba(36, 107, 79, 0.12);
6703
- color: var(--ok);
6704
- }
6705
-
6706
- .job-card__actions {
6707
- display: flex;
6708
- flex-wrap: wrap;
6709
- gap: 8px;
6710
- }
6711
-
6712
- .job-empty {
6713
- color: var(--muted);
6714
- font-size: 0.92rem;
6715
- }
6716
-
6717
- .workspace-tabs {
6718
- display: flex;
6719
- flex-wrap: wrap;
6720
- align-items: center;
6721
- gap: 10px;
6722
- padding: 0 24px 18px;
6723
- }
6724
-
6725
- .workspace-tab {
6726
- border: 1px solid var(--line);
6727
- border-radius: 999px;
6728
- padding: 10px 14px;
6729
- background: rgba(255, 255, 255, 0.72);
6730
- color: var(--muted);
6731
- cursor: pointer;
6732
- font: inherit;
6733
- font-weight: 700;
6734
- }
6735
-
6736
- .workspace-tab[data-selected="true"] {
6737
- background: var(--ink);
6738
- color: white;
6739
- border-color: var(--ink);
6853
+ function normaliseServerUrl(serverUrl) {
6854
+ const raw = serverUrl instanceof URL ? serverUrl.href : serverUrl.trim();
6855
+ if (!raw) throw new Error("server URL is required");
6856
+ const withProtocol = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : \`http://\${raw}\`;
6857
+ const parsed = new URL(withProtocol);
6858
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("server URL must use http or https");
6859
+ parsed.pathname = "/";
6860
+ parsed.search = "";
6861
+ parsed.hash = "";
6862
+ return parsed;
6740
6863
  }
6741
-
6742
- .workspace-hint {
6743
- color: var(--muted);
6744
- font-size: 0.86rem;
6745
- margin-left: auto;
6864
+ function mergeHeaders(...values) {
6865
+ const headers = new Headers();
6866
+ for (const value of values) {
6867
+ if (!value) continue;
6868
+ new Headers(value).forEach((headerValue, headerName) => {
6869
+ headers.set(headerName, headerValue);
6870
+ });
6871
+ }
6872
+ return headers;
6746
6873
  }
6747
-
6748
- .button {
6749
- border: 0;
6750
- border-radius: 999px;
6751
- padding: 12px 16px;
6752
- font: inherit;
6753
- font-weight: 700;
6754
- cursor: pointer;
6874
+ async function responseError(response) {
6875
+ let message = \`\${response.status} \${response.statusText}\`.trim();
6876
+ try {
6877
+ const payload = await response.json();
6878
+ if (typeof payload.error === "string" && payload.error.trim()) message = payload.error;
6879
+ else if (typeof payload.message === "string" && payload.message.trim()) message = payload.message;
6880
+ } catch {
6881
+ const text = (await response.text()).trim();
6882
+ if (text) message = text;
6883
+ }
6884
+ return new Error(message);
6755
6885
  }
6756
-
6757
- .button:disabled {
6758
- cursor: not-allowed;
6759
- opacity: 0.56;
6886
+ function parseSseEvent(payload) {
6887
+ const data = payload.replaceAll("\r\n", "\n").split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart()).join("\n");
6888
+ if (!data) return;
6889
+ return JSON.parse(data);
6760
6890
  }
6761
-
6762
- .button--primary {
6763
- background: var(--ink);
6764
- color: white;
6891
+ var _closed = /* @__PURE__ */ new WeakMap();
6892
+ var _eventLoop = /* @__PURE__ */ new WeakMap();
6893
+ var _listeners = /* @__PURE__ */ new WeakMap();
6894
+ var _fetchImpl = /* @__PURE__ */ new WeakMap();
6895
+ var _password = /* @__PURE__ */ new WeakMap();
6896
+ var _reconnectDelayMs = /* @__PURE__ */ new WeakMap();
6897
+ var _streamAbortController = /* @__PURE__ */ new WeakMap();
6898
+ var _state = /* @__PURE__ */ new WeakMap();
6899
+ var GranolaServerClient = class GranolaServerClient {
6900
+ constructor(info, url, initialState, options = {}) {
6901
+ _classPrivateFieldInitSpec(this, _closed, false);
6902
+ _classPrivateFieldInitSpec(this, _eventLoop, void 0);
6903
+ _classPrivateFieldInitSpec(this, _listeners, /* @__PURE__ */ new Set());
6904
+ _classPrivateFieldInitSpec(this, _fetchImpl, void 0);
6905
+ _classPrivateFieldInitSpec(this, _password, void 0);
6906
+ _classPrivateFieldInitSpec(this, _reconnectDelayMs, void 0);
6907
+ _defineProperty(this, "info", void 0);
6908
+ _classPrivateFieldInitSpec(this, _streamAbortController, void 0);
6909
+ _classPrivateFieldInitSpec(this, _state, void 0);
6910
+ this.url = url;
6911
+ _classPrivateFieldSet2(_fetchImpl, this, options.fetchImpl ?? fetch);
6912
+ this.info = cloneValue(info);
6913
+ _classPrivateFieldSet2(_password, this, options.password?.trim() || void 0);
6914
+ _classPrivateFieldSet2(_reconnectDelayMs, this, options.reconnectDelayMs ?? 1e3);
6915
+ _classPrivateFieldSet2(_state, this, cloneValue(initialState));
6916
+ }
6917
+ static async connect(serverUrl, options = {}) {
6918
+ const url = normaliseServerUrl(serverUrl);
6919
+ const fetchImpl = options.fetchImpl ?? fetch;
6920
+ const infoResponse = await fetchImpl(new URL(granolaTransportPaths.serverInfo, url), { headers: mergeHeaders({
6921
+ ...options.password?.trim() ? { "x-granola-password": options.password.trim() } : {},
6922
+ accept: "application/json"
6923
+ }) });
6924
+ if (!infoResponse.ok) throw await responseError(infoResponse);
6925
+ const info = await infoResponse.json();
6926
+ if (info.protocolVersion !== 2) throw new Error(\`unsupported Granola transport protocol: expected 2, got \${info.protocolVersion}\`);
6927
+ const response = await fetchImpl(new URL(granolaTransportPaths.state, url), { headers: mergeHeaders({
6928
+ ...options.password?.trim() ? { "x-granola-password": options.password.trim() } : {},
6929
+ accept: "application/json"
6930
+ }) });
6931
+ if (!response.ok) throw await responseError(response);
6932
+ const client = new GranolaServerClient(info, url, await response.json(), options);
6933
+ client.startEvents();
6934
+ return client;
6935
+ }
6936
+ async close() {
6937
+ _classPrivateFieldSet2(_closed, this, true);
6938
+ _classPrivateFieldGet2(_streamAbortController, this)?.abort();
6939
+ try {
6940
+ await _classPrivateFieldGet2(_eventLoop, this);
6941
+ } catch {}
6942
+ }
6943
+ getState() {
6944
+ return cloneValue(_classPrivateFieldGet2(_state, this));
6945
+ }
6946
+ subscribe(listener) {
6947
+ _classPrivateFieldGet2(_listeners, this).add(listener);
6948
+ return () => {
6949
+ _classPrivateFieldGet2(_listeners, this).delete(listener);
6950
+ };
6951
+ }
6952
+ async inspectAuth() {
6953
+ return await this.requestJson(granolaTransportPaths.authStatus);
6954
+ }
6955
+ async inspectSync() {
6956
+ return cloneValue(_classPrivateFieldGet2(_state, this).sync);
6957
+ }
6958
+ async listSyncEvents(options = {}) {
6959
+ const path = options.limit ? \`\${granolaTransportPaths.syncEvents}?limit=\${encodeURIComponent(String(options.limit))}\` : granolaTransportPaths.syncEvents;
6960
+ return await this.requestJson(path);
6961
+ }
6962
+ async loginAuth(options = {}) {
6963
+ return await this.requestJson(granolaTransportPaths.authLogin, {
6964
+ body: JSON.stringify(options),
6965
+ headers: { "content-type": "application/json" },
6966
+ method: "POST"
6967
+ });
6968
+ }
6969
+ async logoutAuth() {
6970
+ return await this.requestJson(granolaTransportPaths.authLogout, { method: "POST" });
6971
+ }
6972
+ async refreshAuth() {
6973
+ return await this.requestJson(granolaTransportPaths.authRefresh, { method: "POST" });
6974
+ }
6975
+ async switchAuthMode(mode) {
6976
+ return await this.requestJson(granolaTransportPaths.authMode, {
6977
+ body: JSON.stringify({ mode }),
6978
+ headers: { "content-type": "application/json" },
6979
+ method: "POST"
6980
+ });
6981
+ }
6982
+ async sync(options = {}) {
6983
+ return await this.requestJson(granolaTransportPaths.syncRun, {
6984
+ body: JSON.stringify(options),
6985
+ headers: { "content-type": "application/json" },
6986
+ method: "POST"
6987
+ });
6988
+ }
6989
+ async listFolders(options = {}) {
6990
+ return await this.requestJson(granolaFoldersPath(options));
6991
+ }
6992
+ async getFolder(id) {
6993
+ return await this.requestJson(granolaFolderPath(id));
6994
+ }
6995
+ async findFolder(query) {
6996
+ return await this.requestJson(granolaFolderResolvePath(query));
6997
+ }
6998
+ async listMeetings(options = {}) {
6999
+ return await this.requestJson(granolaMeetingsPath(options));
7000
+ }
7001
+ async getMeeting(id, options = {}) {
7002
+ return await this.requestJson(\`\${granolaMeetingPath(id)}\${options.requireCache ? "?includeTranscript=true" : ""}\`);
7003
+ }
7004
+ async findMeeting(query, options = {}) {
7005
+ return await this.requestJson(granolaMeetingResolvePath(query, { includeTranscript: options.requireCache }));
7006
+ }
7007
+ async listExportJobs(options = {}) {
7008
+ return await this.requestJson(granolaExportJobsPath(options));
7009
+ }
7010
+ async exportNotes(format = "markdown", options = {}) {
7011
+ return await this.requestJson(granolaTransportPaths.exportNotes, {
7012
+ body: JSON.stringify({
7013
+ folderId: options.folderId,
7014
+ format
7015
+ }),
7016
+ headers: { "content-type": "application/json" },
7017
+ method: "POST"
7018
+ });
7019
+ }
7020
+ async exportTranscripts(format = "text", options = {}) {
7021
+ return await this.requestJson(granolaTransportPaths.exportTranscripts, {
7022
+ body: JSON.stringify({
7023
+ folderId: options.folderId,
7024
+ format
7025
+ }),
7026
+ headers: { "content-type": "application/json" },
7027
+ method: "POST"
7028
+ });
7029
+ }
7030
+ async rerunExportJob(id) {
7031
+ return await this.requestJson(granolaExportJobRerunPath(id), { method: "POST" });
7032
+ }
7033
+ async request(path, init = {}) {
7034
+ const response = await _classPrivateFieldGet2(_fetchImpl, this).call(this, new URL(path, this.url), {
7035
+ ...init,
7036
+ headers: mergeHeaders({
7037
+ ..._classPrivateFieldGet2(_password, this) ? { "x-granola-password": _classPrivateFieldGet2(_password, this) } : {},
7038
+ accept: "application/json"
7039
+ }, init.headers)
7040
+ });
7041
+ if (!response.ok) throw await responseError(response);
7042
+ return response;
7043
+ }
7044
+ async requestJson(path, init = {}) {
7045
+ return cloneValue(await (await this.request(path, init)).json());
7046
+ }
7047
+ emit(event) {
7048
+ _classPrivateFieldSet2(_state, this, cloneValue(event.state));
7049
+ const nextEvent = cloneValue(event);
7050
+ for (const listener of _classPrivateFieldGet2(_listeners, this)) listener(nextEvent);
7051
+ }
7052
+ startEvents() {
7053
+ if (_classPrivateFieldGet2(_eventLoop, this)) return;
7054
+ _classPrivateFieldSet2(_eventLoop, this, this.runEventsLoop());
7055
+ }
7056
+ async runEventsLoop() {
7057
+ while (!_classPrivateFieldGet2(_closed, this)) {
7058
+ const controller = new AbortController();
7059
+ _classPrivateFieldSet2(_streamAbortController, this, controller);
7060
+ try {
7061
+ const response = await this.request(granolaTransportPaths.events, {
7062
+ headers: { accept: "text/event-stream" },
7063
+ signal: controller.signal
7064
+ });
7065
+ await this.consumeEventStream(response);
7066
+ } catch {
7067
+ if (_classPrivateFieldGet2(_closed, this) || controller.signal.aborted) break;
7068
+ await new Promise((resolve) => {
7069
+ setTimeout(resolve, _classPrivateFieldGet2(_reconnectDelayMs, this));
7070
+ });
7071
+ }
7072
+ }
7073
+ }
7074
+ async consumeEventStream(response) {
7075
+ const reader = response.body?.getReader();
7076
+ if (!reader) throw new Error("server did not provide an event stream");
7077
+ const decoder = new TextDecoder();
7078
+ let buffer = "";
7079
+ while (!_classPrivateFieldGet2(_closed, this)) {
7080
+ const { done, value } = await reader.read();
7081
+ if (done) return;
7082
+ buffer += decoder.decode(value, { stream: true });
7083
+ buffer = buffer.replaceAll("\r\n", "\n");
7084
+ while (true) {
7085
+ const boundary = buffer.indexOf("\n\n");
7086
+ if (boundary < 0) break;
7087
+ const chunk = buffer.slice(0, boundary);
7088
+ buffer = buffer.slice(boundary + 2);
7089
+ const event = parseSseEvent(chunk);
7090
+ if (event) this.emit(event);
7091
+ }
7092
+ }
7093
+ }
7094
+ };
7095
+ async function createGranolaServerClient(serverUrl, options = {}) {
7096
+ return await GranolaServerClient.connect(serverUrl, options);
6765
7097
  }
6766
-
6767
- .button--secondary {
6768
- background: rgba(255, 255, 255, 0.72);
6769
- color: var(--ink);
6770
- border: 1px solid var(--line);
7098
+ //#endregion
7099
+ //#region src/web/client-state.ts
7100
+ function parseWorkspaceTab(value) {
7101
+ switch (value) {
7102
+ case "metadata":
7103
+ case "raw":
7104
+ case "transcript": return value;
7105
+ default: return "notes";
7106
+ }
6771
7107
  }
6772
-
6773
- .status-grid {
6774
- display: grid;
6775
- grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
6776
- gap: 14px;
7108
+ function startupSelectionFromSearch(search) {
7109
+ const params = new URLSearchParams(search);
7110
+ return {
7111
+ folderId: params.get("folder")?.trim() || "",
7112
+ meetingId: params.get("meeting")?.trim() || "",
7113
+ workspaceTab: parseWorkspaceTab(params.get("tab"))
7114
+ };
6777
7115
  }
6778
-
6779
- .status-label {
6780
- display: block;
6781
- margin-bottom: 6px;
6782
- color: var(--muted);
6783
- font-size: 0.78rem;
6784
- letter-spacing: 0.08em;
6785
- text-transform: uppercase;
7116
+ function buildBrowserUrlPath(currentHref, selection) {
7117
+ const url = new URL(currentHref);
7118
+ if (selection.selectedFolderId) url.searchParams.set("folder", selection.selectedFolderId);
7119
+ else url.searchParams.delete("folder");
7120
+ if (selection.selectedMeetingId) url.searchParams.set("meeting", selection.selectedMeetingId);
7121
+ else url.searchParams.delete("meeting");
7122
+ if (parseWorkspaceTab(selection.workspaceTab) !== "notes") url.searchParams.set("tab", parseWorkspaceTab(selection.workspaceTab));
7123
+ else url.searchParams.delete("tab");
7124
+ return \`\${url.pathname}\${url.search}\${url.hash}\`;
6786
7125
  }
6787
-
6788
- .detail-meta {
6789
- display: flex;
6790
- flex-wrap: wrap;
6791
- gap: 10px;
6792
- padding: 0 24px 18px;
7126
+ function exportScopeLabel(scope) {
7127
+ return scope && scope.mode === "folder" ? \`Folder: \${scope.folderName || scope.folderId}\` : "Scope: All meetings";
6793
7128
  }
6794
-
6795
- .detail-chip {
6796
- padding: 10px 12px;
6797
- border-radius: 999px;
6798
- background: rgba(255, 255, 255, 0.72);
6799
- border: 1px solid var(--line);
6800
- color: var(--muted);
6801
- font-size: 0.88rem;
7129
+ function currentFilterSummary(filters) {
7130
+ const parts = [];
7131
+ if (filters.selectedFolderId) {
7132
+ const folder = filters.folders.find((candidate) => candidate.id === filters.selectedFolderId);
7133
+ parts.push(\`folder "\${folder ? folder.name : filters.selectedFolderId}"\`);
7134
+ }
7135
+ if (filters.search) parts.push(\`search "\${filters.search}"\`);
7136
+ if (filters.updatedFrom) parts.push(\`from \${filters.updatedFrom}\`);
7137
+ if (filters.updatedTo) parts.push(\`to \${filters.updatedTo}\`);
7138
+ return parts.join(", ");
6802
7139
  }
6803
-
6804
- .detail-body {
6805
- padding: 0 24px 24px;
6806
- overflow: auto;
7140
+ function selectMeetingId(meetings, selectedMeetingId) {
7141
+ if (selectedMeetingId && meetings.some((meeting) => meeting.id === selectedMeetingId)) return selectedMeetingId;
7142
+ return meetings[0]?.id ?? null;
6807
7143
  }
6808
-
6809
- .workspace-grid {
6810
- display: grid;
6811
- grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
6812
- gap: 18px;
7144
+ function nextWorkspaceTab(currentTab, key) {
7145
+ const current = parseWorkspaceTab(currentTab);
7146
+ switch (key) {
7147
+ case "1": return "notes";
7148
+ case "2": return "transcript";
7149
+ case "3": return "metadata";
7150
+ case "4": return "raw";
7151
+ case "]":
7152
+ switch (current) {
7153
+ case "notes": return "transcript";
7154
+ case "transcript": return "metadata";
7155
+ case "metadata": return "raw";
7156
+ case "raw": return "notes";
7157
+ }
7158
+ break;
7159
+ case "[":
7160
+ switch (current) {
7161
+ case "notes": return "raw";
7162
+ case "transcript": return "notes";
7163
+ case "metadata": return "transcript";
7164
+ case "raw": return "metadata";
7165
+ }
7166
+ break;
7167
+ default: return;
7168
+ }
6813
7169
  }
6814
-
6815
- .workspace-sidebar,
6816
- .workspace-main {
6817
- margin-bottom: 0;
7170
+ //#endregion
7171
+ //#region src/web-app/components.tsx
7172
+ /** @jsxImportSource solid-js */
7173
+ var _tmpl$$1 = /* @__PURE__ */ template(\`<section class=hero><h1>Granola Toolkit</h1><p>Browser workspace for folders, meetings, notes, transcripts, and export flows on top of one local server instance.</p><input class=search placeholder="Search meetings, ids, or tags"><div class="field-row field-row--inline"><label><span class=field-label>Sort</span><select class=select><option value=updated-desc>Newest first</option><option value=updated-asc>Oldest first</option><option value=title-asc>Title A-Z</option><option value=title-desc>Title Z-A</option></select></label><label><span class=field-label>Updated From</span><input class=field-input type=date></label></div><label class=field-row><span class=field-label>Updated To</span><input class=field-input type=date>\`), _tmpl$2 = /* @__PURE__ */ template(\`<section class=toolbar><div><p>Meetings are loaded from the shared server state so this view can stay aligned with the terminal UI and sync loop.</p></div><div class=toolbar-form><input class=field-input placeholder="Quick open by id or title"><button class="button button--secondary"type=button>Open\`), _tmpl$3 = /* @__PURE__ */ template(\`<div class="folder-empty folder-empty--error">\`), _tmpl$4 = /* @__PURE__ */ template(\`<section class=folder-panel><div class=folder-panel__head><h2>Folders</h2><p>Pick a folder to scope the meeting browser, or stay on All meetings.</p></div><div class=folder-list>\`), _tmpl$5 = /* @__PURE__ */ template(\`<button class=folder-row type=button><span class=folder-row__title>All meetings</span><span class=folder-row__meta>Browse the full meeting list.\`), _tmpl$6 = /* @__PURE__ */ template(\`<div class=folder-empty>No folders found.\`), _tmpl$7 = /* @__PURE__ */ template(\`<button class=folder-row type=button><span class=folder-row__title></span><span class=folder-row__meta>\`), _tmpl$8 = /* @__PURE__ */ template(\`<div class="meeting-empty meeting-empty--error">\`), _tmpl$9 = /* @__PURE__ */ template(\`<section class=meeting-list>\`), _tmpl$0 = /* @__PURE__ */ template(\`<div class=meeting-empty>\`), _tmpl$1 = /* @__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$10 = /* @__PURE__ */ template(\`<section class=detail-head><div><h2>Meeting Workspace</h2></div><div class=state-badge>\`), _tmpl$11 = /* @__PURE__ */ template(\`<p>Waiting for server state…\`), _tmpl$12 = /* @__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>\`), _tmpl$13 = /* @__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$14 = /* @__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$15 = /* @__PURE__ */ template(\`<div class=auth-card><div class=auth-card__meta>Auth state unavailable.\`), _tmpl$16 = /* @__PURE__ */ template(\`<div class=auth-card__meta>Client ID: \`), _tmpl$17 = /* @__PURE__ */ template(\`<div class=auth-card__meta>Sign-in method: \`), _tmpl$18 = /* @__PURE__ */ template(\`<div class=auth-card__meta>supabase path: \`), _tmpl$19 = /* @__PURE__ */ template(\`<div class="auth-card__meta auth-card__error">\`), _tmpl$20 = /* @__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$21 = /* @__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$22 = /* @__PURE__ */ template(\`<div class=job-empty>No export jobs yet.\`), _tmpl$23 = /* @__PURE__ */ template(\`<div class=job-card__meta>\`), _tmpl$24 = /* @__PURE__ */ template(\`<button class="button button--secondary"type=button>Rerun\`), _tmpl$25 = /* @__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$26 = /* @__PURE__ */ template(\`<nav class=workspace-tabs><span class=workspace-hint>1-4 switch tabs, [ and ] cycle\`), _tmpl$27 = /* @__PURE__ */ template(\`<button class=workspace-tab type=button>\`), _tmpl$28 = /* @__PURE__ */ template(\`<div class=empty>\`), _tmpl$29 = /* @__PURE__ */ template(\`<div class=detail-meta><div class=detail-chip></div><div class=detail-chip></div><div class=detail-chip>\`), _tmpl$30 = /* @__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>\`);
7174
+ function authModeLabel(mode) {
7175
+ switch (mode) {
7176
+ case "api-key": return "API key";
7177
+ case "stored-session": return "Stored session";
7178
+ default: return "supabase.json";
7179
+ }
6818
7180
  }
6819
-
6820
- .detail-section {
6821
- margin-bottom: 20px;
6822
- padding: 20px;
6823
- background: rgba(255, 255, 255, 0.72);
6824
- border: 1px solid var(--line);
6825
- border-radius: 20px;
7181
+ function metadataLines(record) {
7182
+ return [
7183
+ \`Title: \${record.meeting.title || record.meeting.id}\`,
7184
+ \`Created: \${record.meeting.createdAt}\`,
7185
+ \`Updated: \${record.meeting.updatedAt}\`,
7186
+ \`Folders: \${record.meeting.folders.length ? record.meeting.folders.map((folder) => folder.name).join(", ") : "none"}\`,
7187
+ \`Tags: \${record.meeting.tags.length ? record.meeting.tags.join(", ") : "none"}\`,
7188
+ \`Transcript loaded: \${record.meeting.transcriptLoaded ? "yes" : "no"}\`
7189
+ ].join("\n");
7190
+ }
7191
+ function workspaceBody(bundle, record, tab) {
7192
+ switch (tab) {
7193
+ case "transcript": return {
7194
+ body: record.transcriptText || "(Transcript unavailable)",
7195
+ title: "Transcript"
7196
+ };
7197
+ case "metadata": return {
7198
+ body: metadataLines(record),
7199
+ title: "Metadata"
7200
+ };
7201
+ case "raw": return {
7202
+ body: JSON.stringify(bundle || record, null, 2),
7203
+ title: "Raw Bundle"
7204
+ };
7205
+ default: return {
7206
+ body: record.noteMarkdown || "(No notes available)",
7207
+ title: "Notes"
7208
+ };
7209
+ }
6826
7210
  }
6827
-
6828
- .detail-section h2 {
6829
- margin: 0 0 14px;
6830
- font-size: 1rem;
6831
- letter-spacing: 0.08em;
6832
- text-transform: uppercase;
7211
+ function scopeLabel(scope) {
7212
+ return exportScopeLabel(scope);
6833
7213
  }
6834
-
6835
- .detail-pre {
6836
- margin: 0;
6837
- white-space: pre-wrap;
6838
- word-break: break-word;
6839
- font-family: var(--mono);
6840
- line-height: 1.55;
7214
+ function ToolbarFilters(props) {
7215
+ return [(() => {
7216
+ var _el$ = _tmpl$$1(), _el$4 = _el$.firstChild.nextSibling.nextSibling, _el$5 = _el$4.nextSibling, _el$6 = _el$5.firstChild, _el$8 = _el$6.firstChild.nextSibling, _el$1 = _el$6.nextSibling.firstChild.nextSibling, _el$12 = _el$5.nextSibling.firstChild.nextSibling;
7217
+ _el$4.$$input = (event) => {
7218
+ props.onSearchInput(event.currentTarget.value);
7219
+ };
7220
+ _el$8.addEventListener("change", (event) => {
7221
+ props.onSortChange(event.currentTarget.value);
7222
+ });
7223
+ _el$1.addEventListener("change", (event) => {
7224
+ props.onUpdatedFromChange(event.currentTarget.value);
7225
+ });
7226
+ _el$12.addEventListener("change", (event) => {
7227
+ props.onUpdatedToChange(event.currentTarget.value);
7228
+ });
7229
+ createRenderEffect(() => _el$4.value = props.search);
7230
+ createRenderEffect(() => _el$8.value = props.sort);
7231
+ createRenderEffect(() => _el$1.value = props.updatedFrom);
7232
+ createRenderEffect(() => _el$12.value = props.updatedTo);
7233
+ return _el$;
7234
+ })(), (() => {
7235
+ var _el$13 = _tmpl$2(), _el$16 = _el$13.firstChild.nextSibling.firstChild, _el$17 = _el$16.nextSibling;
7236
+ _el$16.$$keydown = (event) => {
7237
+ if (event.key === "Enter") {
7238
+ event.preventDefault();
7239
+ props.onQuickOpen();
7240
+ }
7241
+ };
7242
+ _el$16.$$input = (event) => {
7243
+ props.onQuickOpenInput(event.currentTarget.value);
7244
+ };
7245
+ addEventListener(_el$17, "click", props.onQuickOpen, true);
7246
+ createRenderEffect(() => _el$16.value = props.quickOpen);
7247
+ return _el$13;
7248
+ })()];
7249
+ }
7250
+ function FolderList(props) {
7251
+ return (() => {
7252
+ var _el$18 = _tmpl$4(), _el$20 = _el$18.firstChild.nextSibling;
7253
+ insert(_el$20, createComponent(Show, {
7254
+ get fallback() {
7255
+ return [
7256
+ (() => {
7257
+ var _el$22 = _tmpl$5();
7258
+ _el$22.$$click = () => {
7259
+ props.onSelect(null);
7260
+ };
7261
+ createRenderEffect(() => setAttribute(_el$22, "data-selected", !props.selectedFolderId ? "true" : void 0));
7262
+ return _el$22;
7263
+ })(),
7264
+ createComponent(For, {
7265
+ get each() {
7266
+ return props.folders;
7267
+ },
7268
+ children: (folder) => (() => {
7269
+ var _el$24 = _tmpl$7(), _el$25 = _el$24.firstChild, _el$26 = _el$25.nextSibling;
7270
+ _el$24.$$click = () => {
7271
+ props.onSelect(folder.id);
7272
+ };
7273
+ insert(_el$25, () => (folder.isFavourite ? "★ " : "") + (folder.name || folder.id));
7274
+ insert(_el$26, () => \`\${folder.documentCount} meetings\`);
7275
+ createRenderEffect(() => setAttribute(_el$24, "data-selected", folder.id === props.selectedFolderId ? "true" : void 0));
7276
+ return _el$24;
7277
+ })()
7278
+ }),
7279
+ createComponent(Show, {
7280
+ get when() {
7281
+ return props.folders.length === 0;
7282
+ },
7283
+ get children() {
7284
+ return _tmpl$6();
7285
+ }
7286
+ })
7287
+ ];
7288
+ },
7289
+ get when() {
7290
+ return !props.error;
7291
+ },
7292
+ get children() {
7293
+ var _el$21 = _tmpl$3();
7294
+ insert(_el$21, () => props.error);
7295
+ return _el$21;
7296
+ }
7297
+ }));
7298
+ return _el$18;
7299
+ })();
7300
+ }
7301
+ function MeetingList(props) {
7302
+ const summary = () => currentFilterSummary({
7303
+ folders: props.folders,
7304
+ search: props.search,
7305
+ selectedFolderId: props.selectedFolderId,
7306
+ updatedFrom: props.updatedFrom,
7307
+ updatedTo: props.updatedTo
7308
+ });
7309
+ return (() => {
7310
+ var _el$27 = _tmpl$9();
7311
+ insert(_el$27, createComponent(Show, {
7312
+ get fallback() {
7313
+ return createComponent(Show, {
7314
+ get fallback() {
7315
+ return (() => {
7316
+ var _el$29 = _tmpl$0();
7317
+ insert(_el$29, (() => {
7318
+ var _c$ = memo(() => !!summary());
7319
+ return () => _c$() ? \`No meetings match \${summary()}.\` : "No meetings yet. Try Sync now.";
7320
+ })());
7321
+ return _el$29;
7322
+ })();
7323
+ },
7324
+ get when() {
7325
+ return props.meetings.length > 0;
7326
+ },
7327
+ get children() {
7328
+ return createComponent(For, {
7329
+ get each() {
7330
+ return props.meetings;
7331
+ },
7332
+ children: (meeting) => (() => {
7333
+ var _el$30 = _tmpl$1(), _el$31 = _el$30.firstChild, _el$32 = _el$31.nextSibling, _el$33 = _el$32.nextSibling;
7334
+ _el$30.$$click = () => {
7335
+ props.onSelect(meeting.id);
7336
+ };
7337
+ insert(_el$31, () => meeting.title || meeting.id);
7338
+ insert(_el$32, (() => {
7339
+ var _c$2 = memo(() => !!meeting.tags.length);
7340
+ return () => _c$2() ? meeting.tags.map((tag) => \`#\${tag}\`).join(" ") : "untagged";
7341
+ })());
7342
+ insert(_el$33, (() => {
7343
+ var _c$3 = memo(() => !!meeting.updatedAt);
7344
+ return () => _c$3() ? meeting.updatedAt.slice(0, 10) : "unknown";
7345
+ })());
7346
+ createRenderEffect(() => setAttribute(_el$30, "data-selected", meeting.id === props.selectedMeetingId ? "true" : void 0));
7347
+ return _el$30;
7348
+ })()
7349
+ });
7350
+ }
7351
+ });
7352
+ },
7353
+ get when() {
7354
+ return props.error;
7355
+ },
7356
+ get children() {
7357
+ var _el$28 = _tmpl$8();
7358
+ insert(_el$28, () => props.error);
7359
+ return _el$28;
7360
+ }
7361
+ }));
7362
+ return _el$27;
7363
+ })();
7364
+ }
7365
+ function AppStatePanel(props) {
7366
+ const syncStatus = () => {
7367
+ const sync = props.appState?.sync;
7368
+ if (!sync) return "idle";
7369
+ if (sync.running) return "running";
7370
+ if (sync.lastError) return "error";
7371
+ if (sync.lastCompletedAt) return \`last \${sync.lastCompletedAt.slice(11, 19)}\`;
7372
+ return "idle";
7373
+ };
7374
+ const authStatus = () => {
7375
+ if (!props.appState) return "Waiting for auth state…";
7376
+ return authModeLabel(props.appState.auth.mode);
7377
+ };
7378
+ return (() => {
7379
+ var _el$34 = _tmpl$10(), _el$35 = _el$34.firstChild;
7380
+ _el$35.firstChild;
7381
+ var _el$37 = _el$35.nextSibling;
7382
+ insert(_el$35, createComponent(Show, {
7383
+ get fallback() {
7384
+ return _tmpl$11();
7385
+ },
7386
+ get when() {
7387
+ return props.appState;
7388
+ },
7389
+ children: (appState) => (() => {
7390
+ var _el$39 = _tmpl$12(), _el$40 = _el$39.firstChild, _el$42 = _el$40.firstChild.nextSibling, _el$43 = _el$40.nextSibling, _el$45 = _el$43.firstChild.nextSibling, _el$46 = _el$43.nextSibling, _el$48 = _el$46.firstChild.nextSibling, _el$49 = _el$46.nextSibling, _el$51 = _el$49.firstChild.nextSibling, _el$52 = _el$49.nextSibling, _el$54 = _el$52.firstChild.nextSibling, _el$55 = _el$52.nextSibling, _el$57 = _el$55.firstChild.nextSibling, _el$58 = _el$55.nextSibling, _el$60 = _el$58.firstChild.nextSibling, _el$63 = _el$58.nextSibling.firstChild.nextSibling;
7391
+ insert(_el$42, () => appState().ui.surface);
7392
+ insert(_el$45, () => appState().ui.view);
7393
+ insert(_el$48, authStatus);
7394
+ insert(_el$51, syncStatus);
7395
+ insert(_el$54, (() => {
7396
+ var _c$4 = memo(() => !!appState().documents.loaded);
7397
+ return () => _c$4() ? String(appState().documents.count) : "not loaded";
7398
+ })());
7399
+ insert(_el$57, (() => {
7400
+ var _c$5 = memo(() => !!appState().folders.loaded);
7401
+ return () => _c$5() ? String(appState().folders.count) : "not loaded";
7402
+ })());
7403
+ insert(_el$60, (() => {
7404
+ var _c$6 = memo(() => !!appState().cache.loaded);
7405
+ return () => _c$6() ? \`\${appState().cache.transcriptCount} transcript sets\` : appState().cache.configured ? "configured" : "not configured";
7406
+ })());
7407
+ insert(_el$63, (() => {
7408
+ var _c$7 = memo(() => !!appState().index.loaded);
7409
+ return () => _c$7() ? \`\${appState().index.meetingCount} meetings\` : appState().index.available ? "available" : "not built";
7410
+ })());
7411
+ return _el$39;
7412
+ })()
7413
+ }), null);
7414
+ insert(_el$37, () => props.statusLabel);
7415
+ createRenderEffect(() => setAttribute(_el$37, "data-tone", props.statusTone));
7416
+ return _el$34;
7417
+ })();
7418
+ }
7419
+ function SecurityPanel(props) {
7420
+ return createComponent(Show, {
7421
+ get when() {
7422
+ return props.visible;
7423
+ },
7424
+ get children() {
7425
+ var _el$64 = _tmpl$13(), _el$67 = _el$64.firstChild.nextSibling.firstChild, _el$69 = _el$67.nextSibling.firstChild, _el$70 = _el$69.nextSibling;
7426
+ _el$67.$$keydown = (event) => {
7427
+ if (event.key === "Enter") {
7428
+ event.preventDefault();
7429
+ props.onUnlock();
7430
+ }
7431
+ };
7432
+ _el$67.$$input = (event) => {
7433
+ props.onPasswordChange(event.currentTarget.value);
7434
+ };
7435
+ addEventListener(_el$69, "click", props.onUnlock, true);
7436
+ addEventListener(_el$70, "click", props.onLock, true);
7437
+ createRenderEffect(() => _el$67.value = props.password);
7438
+ return _el$64;
7439
+ }
7440
+ });
6841
7441
  }
6842
-
6843
- .empty {
6844
- margin: 24px;
6845
- padding: 24px;
6846
- border-radius: 20px;
6847
- background: rgba(255, 255, 255, 0.72);
6848
- border: 1px dashed rgba(36, 39, 44, 0.2);
6849
- color: var(--muted);
7442
+ function AuthPanel(props) {
7443
+ return (() => {
7444
+ var _el$71 = _tmpl$14(), _el$73 = _el$71.firstChild.nextSibling;
7445
+ insert(_el$73, createComponent(Show, {
7446
+ get fallback() {
7447
+ return _tmpl$15();
7448
+ },
7449
+ get when() {
7450
+ return props.auth;
7451
+ },
7452
+ children: (auth) => (() => {
7453
+ var _el$75 = _tmpl$20(), _el$76 = _el$75.firstChild, _el$77 = _el$76.firstChild, _el$79 = _el$77.firstChild.nextSibling, _el$80 = _el$77.nextSibling, _el$82 = _el$80.firstChild.nextSibling, _el$83 = _el$80.nextSibling, _el$85 = _el$83.firstChild.nextSibling, _el$86 = _el$83.nextSibling, _el$88 = _el$86.firstChild.nextSibling, _el$91 = _el$86.nextSibling.firstChild.nextSibling, _el$99 = _el$76.nextSibling, _el$101 = _el$99.nextSibling.firstChild, _el$102 = _el$101.nextSibling, _el$103 = _el$102.nextSibling, _el$104 = _el$103.nextSibling, _el$105 = _el$104.nextSibling, _el$106 = _el$105.nextSibling, _el$107 = _el$106.nextSibling, _el$108 = _el$107.nextSibling;
7454
+ insert(_el$79, () => authModeLabel(auth().mode));
7455
+ insert(_el$82, () => auth().apiKeyAvailable ? "available" : "missing");
7456
+ insert(_el$85, () => auth().storedSessionAvailable ? "available" : "missing");
7457
+ insert(_el$88, () => auth().supabaseAvailable ? "available" : "missing");
7458
+ insert(_el$91, () => auth().refreshAvailable ? "available" : "missing");
7459
+ insert(_el$75, createComponent(Show, {
7460
+ get when() {
7461
+ return auth().clientId;
7462
+ },
7463
+ get children() {
7464
+ var _el$92 = _tmpl$16();
7465
+ _el$92.firstChild;
7466
+ insert(_el$92, () => auth().clientId, null);
7467
+ return _el$92;
7468
+ }
7469
+ }), _el$99);
7470
+ insert(_el$75, createComponent(Show, {
7471
+ get when() {
7472
+ return auth().signInMethod;
7473
+ },
7474
+ get children() {
7475
+ var _el$94 = _tmpl$17();
7476
+ _el$94.firstChild;
7477
+ insert(_el$94, () => auth().signInMethod, null);
7478
+ return _el$94;
7479
+ }
7480
+ }), _el$99);
7481
+ insert(_el$75, createComponent(Show, {
7482
+ get when() {
7483
+ return auth().supabasePath;
7484
+ },
7485
+ get children() {
7486
+ var _el$96 = _tmpl$18();
7487
+ _el$96.firstChild;
7488
+ insert(_el$96, () => auth().supabasePath, null);
7489
+ return _el$96;
7490
+ }
7491
+ }), _el$99);
7492
+ insert(_el$75, createComponent(Show, {
7493
+ get when() {
7494
+ return auth().lastError;
7495
+ },
7496
+ get children() {
7497
+ var _el$98 = _tmpl$19();
7498
+ insert(_el$98, () => auth().lastError);
7499
+ return _el$98;
7500
+ }
7501
+ }), _el$99);
7502
+ _el$101.$$input = (event) => {
7503
+ props.onApiKeyDraftChange(event.currentTarget.value);
7504
+ };
7505
+ addEventListener(_el$102, "click", props.onSaveApiKey, true);
7506
+ addEventListener(_el$103, "click", props.onImportDesktopSession, true);
7507
+ addEventListener(_el$104, "click", props.onRefresh, true);
7508
+ _el$105.$$click = () => {
7509
+ props.onSwitchMode("api-key");
7510
+ };
7511
+ _el$106.$$click = () => {
7512
+ props.onSwitchMode("stored-session");
7513
+ };
7514
+ _el$107.$$click = () => {
7515
+ props.onSwitchMode("supabase-file");
7516
+ };
7517
+ addEventListener(_el$108, "click", props.onLogout, true);
7518
+ createRenderEffect((_p$) => {
7519
+ 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;
7520
+ _v$ !== _p$.e && (_el$103.disabled = _p$.e = _v$);
7521
+ _v$2 !== _p$.t && (_el$104.disabled = _p$.t = _v$2);
7522
+ _v$3 !== _p$.a && (_el$105.disabled = _p$.a = _v$3);
7523
+ _v$4 !== _p$.o && (_el$106.disabled = _p$.o = _v$4);
7524
+ _v$5 !== _p$.i && (_el$107.disabled = _p$.i = _v$5);
7525
+ _v$6 !== _p$.n && (_el$108.disabled = _p$.n = _v$6);
7526
+ return _p$;
7527
+ }, {
7528
+ e: void 0,
7529
+ t: void 0,
7530
+ a: void 0,
7531
+ o: void 0,
7532
+ i: void 0,
7533
+ n: void 0
7534
+ });
7535
+ createRenderEffect(() => _el$101.value = props.apiKeyDraft);
7536
+ return _el$75;
7537
+ })()
7538
+ }));
7539
+ return _el$71;
7540
+ })();
7541
+ }
7542
+ function ExportJobsPanel(props) {
7543
+ return (() => {
7544
+ var _el$109 = _tmpl$21(), _el$111 = _el$109.firstChild.nextSibling;
7545
+ insert(_el$111, createComponent(Show, {
7546
+ get when() {
7547
+ return props.jobs.length > 0;
7548
+ },
7549
+ get fallback() {
7550
+ return _tmpl$22();
7551
+ },
7552
+ get children() {
7553
+ return createComponent(For, {
7554
+ get each() {
7555
+ return props.jobs.slice(0, 6);
7556
+ },
7557
+ children: (job) => (() => {
7558
+ var _el$113 = _tmpl$25(), _el$114 = _el$113.firstChild, _el$115 = _el$114.firstChild, _el$116 = _el$115.firstChild, _el$117 = _el$116.firstChild, _el$118 = _el$116.nextSibling, _el$119 = _el$115.nextSibling, _el$120 = _el$114.nextSibling, _el$121 = _el$120.nextSibling;
7559
+ _el$121.firstChild;
7560
+ var _el$123 = _el$121.nextSibling;
7561
+ _el$123.firstChild;
7562
+ var _el$126 = _el$123.nextSibling;
7563
+ insert(_el$116, () => job.kind, _el$117);
7564
+ insert(_el$118, () => job.id);
7565
+ insert(_el$119, () => job.status);
7566
+ insert(_el$120, () => \`Format: \${job.format} • \${scopeLabel(job.scope)} • \${job.itemCount > 0 ? \`\${job.completedCount}/\${job.itemCount} items\` : "0 items"} • Written: \${job.written}\`);
7567
+ insert(_el$121, () => job.startedAt.slice(0, 19), null);
7568
+ insert(_el$123, () => job.outputDir, null);
7569
+ insert(_el$113, createComponent(Show, {
7570
+ get when() {
7571
+ return job.error;
7572
+ },
7573
+ get children() {
7574
+ var _el$125 = _tmpl$23();
7575
+ insert(_el$125, () => job.error);
7576
+ return _el$125;
7577
+ }
7578
+ }), _el$126);
7579
+ insert(_el$126, createComponent(Show, {
7580
+ get when() {
7581
+ return job.status !== "running";
7582
+ },
7583
+ get children() {
7584
+ var _el$127 = _tmpl$24();
7585
+ _el$127.$$click = () => {
7586
+ props.onRerun(job.id);
7587
+ };
7588
+ return _el$127;
7589
+ }
7590
+ }));
7591
+ createRenderEffect(() => setAttribute(_el$119, "data-status", job.status));
7592
+ return _el$113;
7593
+ })()
7594
+ });
7595
+ }
7596
+ }));
7597
+ return _el$109;
7598
+ })();
7599
+ }
7600
+ function Workspace(props) {
7601
+ const parsedTab = () => parseWorkspaceTab(props.tab);
7602
+ const details = () => {
7603
+ if (!props.selectedMeeting) return null;
7604
+ return workspaceBody(props.bundle, props.selectedMeeting, parsedTab());
7605
+ };
7606
+ return [(() => {
7607
+ var _el$128 = _tmpl$26(), _el$129 = _el$128.firstChild;
7608
+ insert(_el$128, createComponent(For, {
7609
+ each: [
7610
+ "notes",
7611
+ "transcript",
7612
+ "metadata",
7613
+ "raw"
7614
+ ],
7615
+ children: (tab) => (() => {
7616
+ var _el$130 = _tmpl$27();
7617
+ _el$130.$$click = () => {
7618
+ props.onSelectTab(tab);
7619
+ };
7620
+ insert(_el$130, tab === "notes" ? "Notes" : tab === "transcript" ? "Transcript" : tab === "metadata" ? "Metadata" : "Raw");
7621
+ createRenderEffect(() => setAttribute(_el$130, "data-selected", parsedTab() === tab ? "true" : void 0));
7622
+ return _el$130;
7623
+ })()
7624
+ }), _el$129);
7625
+ return _el$128;
7626
+ })(), createComponent(Show, {
7627
+ get when() {
7628
+ return props.selectedMeeting;
7629
+ },
7630
+ get fallback() {
7631
+ return (() => {
7632
+ var _el$131 = _tmpl$28();
7633
+ insert(_el$131, () => props.detailError || "Select a meeting to inspect its notes and transcript.");
7634
+ return _el$131;
7635
+ })();
7636
+ },
7637
+ children: (meeting) => [(() => {
7638
+ var _el$132 = _tmpl$29(), _el$133 = _el$132.firstChild, _el$134 = _el$133.nextSibling, _el$135 = _el$134.nextSibling;
7639
+ insert(_el$133, () => \`ID: \${meeting().meeting.id}\`);
7640
+ insert(_el$134, () => \`Source: \${meeting().meeting.noteContentSource}\`);
7641
+ insert(_el$135, () => \`Transcript: \${meeting().meeting.transcriptSegmentCount} segments\`);
7642
+ return _el$132;
7643
+ })(), createComponent(Show, {
7644
+ get when() {
7645
+ return !props.detailError;
7646
+ },
7647
+ get fallback() {
7648
+ return (() => {
7649
+ var _el$144 = _tmpl$28();
7650
+ insert(_el$144, () => props.detailError);
7651
+ return _el$144;
7652
+ })();
7653
+ },
7654
+ get children() {
7655
+ var _el$136 = _tmpl$30(), _el$138 = _el$136.firstChild.firstChild, _el$140 = _el$138.firstChild.nextSibling, _el$142 = _el$138.nextSibling.firstChild, _el$143 = _el$142.nextSibling;
7656
+ insert(_el$140, () => metadataLines(meeting()));
7657
+ insert(_el$142, () => details()?.title);
7658
+ insert(_el$143, () => details()?.body);
7659
+ return _el$136;
7660
+ }
7661
+ })]
7662
+ })];
6850
7663
  }
6851
-
6852
- @media (max-width: 900px) {
6853
- .shell {
6854
- grid-template-columns: 1fr;
6855
- }
6856
-
6857
- .field-row--inline,
6858
- .toolbar-form,
6859
- .workspace-grid {
6860
- grid-template-columns: 1fr;
6861
- }
6862
-
6863
- .workspace-hint {
6864
- margin-left: 0;
6865
- }
7664
+ delegateEvents([
7665
+ "input",
7666
+ "keydown",
7667
+ "click"
7668
+ ]);
7669
+ //#endregion
7670
+ //#region src/web-app/App.tsx
7671
+ /** @jsxImportSource solid-js */
7672
+ var _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>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.\`);
7673
+ function browserConfig() {
7674
+ return { passwordRequired: Boolean(window.__GRANOLA_SERVER__?.passwordRequired) };
7675
+ }
7676
+ async function requestJson(path, init) {
7677
+ const response = await fetch(path, init);
7678
+ const payload = await response.json().catch(() => ({}));
7679
+ if (!response.ok) {
7680
+ const error = typeof payload.error === "string" && payload.error.trim() ? payload.error : response.statusText || "Request failed";
7681
+ throw new Error(error);
7682
+ }
7683
+ return payload;
7684
+ }
7685
+ function App() {
7686
+ const startup = startupSelectionFromSearch(window.location.search);
7687
+ const [state, setState] = createStore({
7688
+ apiKeyDraft: "",
7689
+ appState: null,
7690
+ detailError: "",
7691
+ folderError: "",
7692
+ folders: [],
7693
+ listError: "",
7694
+ meetingSource: "live",
7695
+ meetings: [],
7696
+ quickOpen: "",
7697
+ search: "",
7698
+ selectedFolderId: startup.folderId || null,
7699
+ selectedMeetingBundle: null,
7700
+ selectedMeetingId: startup.meetingId || null,
7701
+ selectedMeeting: null,
7702
+ serverLocked: browserConfig().passwordRequired,
7703
+ serverPassword: "",
7704
+ sort: "updated-desc",
7705
+ statusLabel: browserConfig().passwordRequired ? "Server locked" : "Connecting…",
7706
+ statusTone: browserConfig().passwordRequired ? "error" : "idle",
7707
+ updatedFrom: "",
7708
+ updatedTo: "",
7709
+ workspaceTab: parseWorkspaceTab(startup.workspaceTab)
7710
+ });
7711
+ let client = null;
7712
+ let unsubscribe;
7713
+ const setStatus = (label, tone = "idle") => {
7714
+ setState({
7715
+ statusLabel: label,
7716
+ statusTone: tone
7717
+ });
7718
+ };
7719
+ const mergeAuthState = async (authState) => {
7720
+ if (!client) return;
7721
+ setState("appState", {
7722
+ ...client.getState(),
7723
+ auth: authState ?? await client.inspectAuth()
7724
+ });
7725
+ };
7726
+ const detachClient = async () => {
7727
+ unsubscribe?.();
7728
+ unsubscribe = void 0;
7729
+ if (client) {
7730
+ await client.close().catch(() => void 0);
7731
+ client = null;
7732
+ }
7733
+ };
7734
+ const attachClient = async () => {
7735
+ await detachClient();
7736
+ client = await createGranolaServerClient(window.location.origin);
7737
+ unsubscribe = client.subscribe((event) => {
7738
+ setState("appState", event.state);
7739
+ });
7740
+ await mergeAuthState();
7741
+ };
7742
+ const loadFolders = async (refresh = false) => {
7743
+ if (!client) return;
7744
+ try {
7745
+ setState("folderError", "");
7746
+ const result = await client.listFolders({
7747
+ forceRefresh: refresh,
7748
+ limit: 100
7749
+ });
7750
+ setState("folders", result.folders);
7751
+ if (state.selectedFolderId && !result.folders.some((folder) => folder.id === state.selectedFolderId)) setState("selectedFolderId", null);
7752
+ } catch (error) {
7753
+ setState("folderError", error instanceof Error ? error.message : String(error));
7754
+ setState("folders", []);
7755
+ setState("selectedFolderId", null);
7756
+ }
7757
+ };
7758
+ const loadMeeting = async (meetingId) => {
7759
+ if (!client) return;
7760
+ setState("selectedMeetingId", meetingId);
7761
+ try {
7762
+ setState("detailError", "");
7763
+ const bundle = await client.getMeeting(meetingId);
7764
+ setState("selectedMeetingBundle", bundle);
7765
+ setState("selectedMeeting", bundle.meeting);
7766
+ } catch (error) {
7767
+ setState("selectedMeetingBundle", null);
7768
+ setState("selectedMeeting", null);
7769
+ setState("detailError", error instanceof Error ? error.message : String(error));
7770
+ }
7771
+ };
7772
+ const loadMeetings = async (options = {}) => {
7773
+ if (!client) return;
7774
+ try {
7775
+ setState("listError", "");
7776
+ const result = await client.listMeetings({
7777
+ folderId: state.selectedFolderId || void 0,
7778
+ forceRefresh: options.refresh,
7779
+ limit: 100,
7780
+ search: state.search || void 0,
7781
+ sort: state.sort,
7782
+ updatedFrom: state.updatedFrom || void 0,
7783
+ updatedTo: state.updatedTo || void 0
7784
+ });
7785
+ const preferredMeetingId = options.preferredMeetingId ?? state.selectedMeetingId;
7786
+ const nextMeetingId = selectMeetingId(result.meetings, preferredMeetingId);
7787
+ setState("meetings", result.meetings);
7788
+ setState("meetingSource", result.source);
7789
+ setState("selectedMeetingId", nextMeetingId);
7790
+ if (nextMeetingId) await loadMeeting(nextMeetingId);
7791
+ else {
7792
+ setState("selectedMeeting", null);
7793
+ setState("selectedMeetingBundle", null);
7794
+ setState("detailError", "");
7795
+ }
7796
+ } catch (error) {
7797
+ const message = error instanceof Error ? error.message : String(error);
7798
+ setState("listError", message);
7799
+ setState("selectedMeeting", null);
7800
+ setState("selectedMeetingBundle", null);
7801
+ setState("detailError", message);
7802
+ }
7803
+ };
7804
+ const refreshAll = async (forceRefresh = false) => {
7805
+ if (!client) await attachClient();
7806
+ setStatus(forceRefresh ? "Syncing…" : "Refreshing…", "busy");
7807
+ if (forceRefresh) await client?.sync({
7808
+ forceRefresh: true,
7809
+ foreground: true
7810
+ });
7811
+ await Promise.all([loadFolders(forceRefresh), mergeAuthState()]);
7812
+ await loadMeetings({ refresh: forceRefresh });
7813
+ setState("serverLocked", false);
7814
+ setStatus(forceRefresh ? "Sync complete" : state.meetingSource === "index" ? "Loaded from index" : "Connected", "ok");
7815
+ };
7816
+ const connectAndRefresh = async (forceRefresh = false) => {
7817
+ try {
7818
+ await refreshAll(forceRefresh);
7819
+ } catch (error) {
7820
+ setStatus("Connection failed", "error");
7821
+ setState("detailError", error instanceof Error ? error.message : String(error));
7822
+ }
7823
+ };
7824
+ const quickOpenMeeting = async () => {
7825
+ if (!client) return;
7826
+ const query = state.quickOpen.trim();
7827
+ if (!query) {
7828
+ setStatus("Enter a title or id", "error");
7829
+ return;
7830
+ }
7831
+ setStatus("Opening meeting…", "busy");
7832
+ try {
7833
+ const bundle = await client.findMeeting(query);
7834
+ setState("selectedFolderId", bundle.meeting.meeting.folders[0]?.id || null);
7835
+ setState("search", "");
7836
+ setState("updatedFrom", "");
7837
+ setState("updatedTo", "");
7838
+ await loadMeetings({ preferredMeetingId: bundle.document.id });
7839
+ setStatus("Connected", "ok");
7840
+ } catch (error) {
7841
+ setState("detailError", error instanceof Error ? error.message : String(error));
7842
+ setStatus("Quick open failed", "error");
7843
+ }
7844
+ };
7845
+ const saveApiKey = async () => {
7846
+ if (!client) return;
7847
+ if (!state.apiKeyDraft.trim()) {
7848
+ setStatus("Enter a Granola API key", "error");
7849
+ return;
7850
+ }
7851
+ setStatus("Saving API key…", "busy");
7852
+ try {
7853
+ const auth = await client.loginAuth({ apiKey: state.apiKeyDraft.trim() });
7854
+ setState("apiKeyDraft", "");
7855
+ await mergeAuthState(auth);
7856
+ await refreshAll();
7857
+ } catch (error) {
7858
+ await mergeAuthState();
7859
+ setState("detailError", error instanceof Error ? error.message : String(error));
7860
+ setStatus("API key save failed", "error");
7861
+ }
7862
+ };
7863
+ const importDesktopSession = async () => {
7864
+ if (!client) return;
7865
+ setStatus("Importing desktop session…", "busy");
7866
+ try {
7867
+ await mergeAuthState(await client.loginAuth());
7868
+ await refreshAll();
7869
+ } catch (error) {
7870
+ await mergeAuthState();
7871
+ setState("detailError", error instanceof Error ? error.message : String(error));
7872
+ setStatus("Auth import failed", "error");
7873
+ }
7874
+ };
7875
+ const refreshAuth = async () => {
7876
+ if (!client) return;
7877
+ setStatus("Refreshing session…", "busy");
7878
+ try {
7879
+ await mergeAuthState(await client.refreshAuth());
7880
+ await refreshAll();
7881
+ } catch (error) {
7882
+ await mergeAuthState();
7883
+ setState("detailError", error instanceof Error ? error.message : String(error));
7884
+ setStatus("Refresh failed", "error");
7885
+ }
7886
+ };
7887
+ const switchAuthMode = async (mode) => {
7888
+ if (!client) return;
7889
+ setStatus("Switching auth source…", "busy");
7890
+ try {
7891
+ await mergeAuthState(await client.switchAuthMode(mode));
7892
+ await refreshAll();
7893
+ } catch (error) {
7894
+ await mergeAuthState();
7895
+ setState("detailError", error instanceof Error ? error.message : String(error));
7896
+ setStatus("Switch failed", "error");
7897
+ }
7898
+ };
7899
+ const logout = async () => {
7900
+ if (!client) return;
7901
+ setStatus("Signing out…", "busy");
7902
+ try {
7903
+ await mergeAuthState(await client.logoutAuth());
7904
+ await refreshAll();
7905
+ } catch (error) {
7906
+ await mergeAuthState();
7907
+ setState("detailError", error instanceof Error ? error.message : String(error));
7908
+ setStatus("Sign out failed", "error");
7909
+ }
7910
+ };
7911
+ const exportNotes = async () => {
7912
+ if (!client) return;
7913
+ setStatus(state.selectedFolderId ? "Exporting folder notes…" : "Exporting notes…", "busy");
7914
+ try {
7915
+ await client.exportNotes("markdown", { folderId: state.selectedFolderId || void 0 });
7916
+ await refreshAll();
7917
+ } catch (error) {
7918
+ setState("detailError", error instanceof Error ? error.message : String(error));
7919
+ setStatus("Export failed", "error");
7920
+ }
7921
+ };
7922
+ const exportTranscripts = async () => {
7923
+ if (!client) return;
7924
+ setStatus(state.selectedFolderId ? "Exporting folder transcripts…" : "Exporting transcripts…", "busy");
7925
+ try {
7926
+ await client.exportTranscripts("text", { folderId: state.selectedFolderId || void 0 });
7927
+ await refreshAll();
7928
+ } catch (error) {
7929
+ setState("detailError", error instanceof Error ? error.message : String(error));
7930
+ setStatus("Export failed", "error");
7931
+ }
7932
+ };
7933
+ const rerunJob = async (jobId) => {
7934
+ if (!client) return;
7935
+ setStatus("Rerunning export…", "busy");
7936
+ try {
7937
+ await client.rerunExportJob(jobId);
7938
+ await refreshAll();
7939
+ } catch (error) {
7940
+ setState("detailError", error instanceof Error ? error.message : String(error));
7941
+ setStatus("Rerun failed", "error");
7942
+ }
7943
+ };
7944
+ const unlockServer = async () => {
7945
+ if (!state.serverPassword.trim()) {
7946
+ setStatus("Enter the server password", "error");
7947
+ return;
7948
+ }
7949
+ setStatus("Unlocking server…", "busy");
7950
+ try {
7951
+ await requestJson("/auth/unlock", {
7952
+ body: JSON.stringify({ password: state.serverPassword }),
7953
+ headers: { "content-type": "application/json" },
7954
+ method: "POST"
7955
+ });
7956
+ setState("serverPassword", "");
7957
+ setState("serverLocked", false);
7958
+ await connectAndRefresh(true);
7959
+ } catch (error) {
7960
+ setState("detailError", error instanceof Error ? error.message : String(error));
7961
+ setStatus("Unlock failed", "error");
7962
+ }
7963
+ };
7964
+ const lockServer = async () => {
7965
+ try {
7966
+ await requestJson("/auth/lock", { method: "POST" });
7967
+ } catch {}
7968
+ await detachClient();
7969
+ setState({
7970
+ appState: null,
7971
+ detailError: "",
7972
+ folderError: "",
7973
+ folders: [],
7974
+ listError: "",
7975
+ meetings: [],
7976
+ selectedFolderId: null,
7977
+ selectedMeeting: null,
7978
+ selectedMeetingBundle: null,
7979
+ selectedMeetingId: null,
7980
+ serverLocked: true,
7981
+ serverPassword: ""
7982
+ });
7983
+ setStatus("Server locked", "error");
7984
+ };
7985
+ createEffect(() => {
7986
+ const nextPath = buildBrowserUrlPath(window.location.href, {
7987
+ selectedFolderId: state.selectedFolderId,
7988
+ selectedMeetingId: state.selectedMeetingId,
7989
+ workspaceTab: state.workspaceTab
7990
+ });
7991
+ if (nextPath !== \`\${window.location.pathname}\${window.location.search}\${window.location.hash}\`) history.replaceState(null, "", nextPath);
7992
+ });
7993
+ onMount(() => {
7994
+ const onKeyDown = (event) => {
7995
+ const target = event.target;
7996
+ if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement || target instanceof HTMLTextAreaElement) return;
7997
+ const nextTab = nextWorkspaceTab(state.workspaceTab, event.key);
7998
+ if (nextTab) setState("workspaceTab", nextTab);
7999
+ };
8000
+ document.addEventListener("keydown", onKeyDown);
8001
+ onCleanup(() => {
8002
+ document.removeEventListener("keydown", onKeyDown);
8003
+ });
8004
+ if (!state.serverLocked) connectAndRefresh();
8005
+ });
8006
+ onCleanup(() => {
8007
+ detachClient();
8008
+ });
8009
+ return (() => {
8010
+ 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;
8011
+ insert(_el$2, createComponent(ToolbarFilters, {
8012
+ onQuickOpen: () => {
8013
+ quickOpenMeeting();
8014
+ },
8015
+ onQuickOpenInput: (value) => {
8016
+ setState("quickOpen", value);
8017
+ },
8018
+ onSearchInput: (value) => {
8019
+ setState("search", value.trim());
8020
+ loadMeetings();
8021
+ },
8022
+ onSortChange: (value) => {
8023
+ setState("sort", value);
8024
+ loadMeetings();
8025
+ },
8026
+ onUpdatedFromChange: (value) => {
8027
+ setState("updatedFrom", value);
8028
+ loadMeetings();
8029
+ },
8030
+ onUpdatedToChange: (value) => {
8031
+ setState("updatedTo", value);
8032
+ loadMeetings();
8033
+ },
8034
+ get quickOpen() {
8035
+ return state.quickOpen;
8036
+ },
8037
+ get search() {
8038
+ return state.search;
8039
+ },
8040
+ get sort() {
8041
+ return state.sort;
8042
+ },
8043
+ get updatedFrom() {
8044
+ return state.updatedFrom;
8045
+ },
8046
+ get updatedTo() {
8047
+ return state.updatedTo;
8048
+ }
8049
+ }), null);
8050
+ insert(_el$2, createComponent(FolderList, {
8051
+ get error() {
8052
+ return state.folderError;
8053
+ },
8054
+ get folders() {
8055
+ return state.folders;
8056
+ },
8057
+ onSelect: (folderId) => {
8058
+ setState("selectedFolderId", folderId);
8059
+ setState("selectedMeetingId", null);
8060
+ setState("selectedMeeting", null);
8061
+ setState("selectedMeetingBundle", null);
8062
+ loadMeetings();
8063
+ },
8064
+ get selectedFolderId() {
8065
+ return state.selectedFolderId;
8066
+ }
8067
+ }), null);
8068
+ insert(_el$2, createComponent(MeetingList, {
8069
+ get error() {
8070
+ return state.listError;
8071
+ },
8072
+ get folders() {
8073
+ return state.folders;
8074
+ },
8075
+ get meetings() {
8076
+ return state.meetings;
8077
+ },
8078
+ onSelect: (meetingId) => {
8079
+ loadMeeting(meetingId);
8080
+ },
8081
+ get search() {
8082
+ return state.search;
8083
+ },
8084
+ get selectedFolderId() {
8085
+ return state.selectedFolderId;
8086
+ },
8087
+ get selectedMeetingId() {
8088
+ return state.selectedMeetingId;
8089
+ },
8090
+ get updatedFrom() {
8091
+ return state.updatedFrom;
8092
+ },
8093
+ get updatedTo() {
8094
+ return state.updatedTo;
8095
+ }
8096
+ }), null);
8097
+ insert(_el$3, createComponent(AppStatePanel, {
8098
+ get appState() {
8099
+ return state.appState;
8100
+ },
8101
+ get statusLabel() {
8102
+ return state.statusLabel;
8103
+ },
8104
+ get statusTone() {
8105
+ return state.statusTone;
8106
+ }
8107
+ }), _el$4);
8108
+ _el$6.$$click = () => {
8109
+ connectAndRefresh(true);
8110
+ };
8111
+ _el$7.$$click = () => {
8112
+ exportNotes();
8113
+ };
8114
+ _el$8.$$click = () => {
8115
+ exportTranscripts();
8116
+ };
8117
+ insert(_el$3, createComponent(SecurityPanel, {
8118
+ onLock: () => {
8119
+ lockServer();
8120
+ },
8121
+ onPasswordChange: (value) => {
8122
+ setState("serverPassword", value);
8123
+ },
8124
+ onUnlock: () => {
8125
+ unlockServer();
8126
+ },
8127
+ get password() {
8128
+ return state.serverPassword;
8129
+ },
8130
+ get visible() {
8131
+ return state.serverLocked;
8132
+ }
8133
+ }), null);
8134
+ insert(_el$3, createComponent(AuthPanel, {
8135
+ get apiKeyDraft() {
8136
+ return state.apiKeyDraft;
8137
+ },
8138
+ get auth() {
8139
+ return state.appState?.auth;
8140
+ },
8141
+ onApiKeyDraftChange: (value) => {
8142
+ setState("apiKeyDraft", value);
8143
+ },
8144
+ onImportDesktopSession: () => {
8145
+ importDesktopSession();
8146
+ },
8147
+ onLogout: () => {
8148
+ logout();
8149
+ },
8150
+ onRefresh: () => {
8151
+ refreshAuth();
8152
+ },
8153
+ onSaveApiKey: () => {
8154
+ saveApiKey();
8155
+ },
8156
+ onSwitchMode: (mode) => {
8157
+ switchAuthMode(mode);
8158
+ }
8159
+ }), null);
8160
+ insert(_el$3, createComponent(ExportJobsPanel, {
8161
+ get jobs() {
8162
+ return state.appState?.exports.jobs || [];
8163
+ },
8164
+ onRerun: (jobId) => {
8165
+ rerunJob(jobId);
8166
+ }
8167
+ }), null);
8168
+ insert(_el$3, createComponent(Workspace, {
8169
+ get bundle() {
8170
+ return state.selectedMeetingBundle;
8171
+ },
8172
+ get detailError() {
8173
+ return state.detailError;
8174
+ },
8175
+ onSelectTab: (tab) => {
8176
+ setState("workspaceTab", tab);
8177
+ },
8178
+ get selectedMeeting() {
8179
+ return state.selectedMeeting;
8180
+ },
8181
+ get tab() {
8182
+ return state.workspaceTab;
8183
+ }
8184
+ }), null);
8185
+ return _el$;
8186
+ })();
6866
8187
  }
8188
+ delegateEvents(["click"]);
8189
+ //#endregion
8190
+ //#region src/web-app/main.tsx
8191
+ /** @jsxImportSource solid-js */
8192
+ var root = document.getElementById("granola-web-root");
8193
+ if (!root) throw new Error("Granola web root element not found");
8194
+ render(() => createComponent(App, {}), root);
8195
+ //#endregion
6867
8196
  `;
6868
8197
  //#endregion
8198
+ //#region src/web/assets.ts
8199
+ const granolaWebAssetPaths = {
8200
+ script: "/assets/granola-web-client.js",
8201
+ stylesheet: "/assets/granola-web-client.css"
8202
+ };
8203
+ function granolaWebAssetForPath(path) {
8204
+ switch (path) {
8205
+ case granolaWebAssetPaths.script: return {
8206
+ body: granolaWebClientJs,
8207
+ contentType: "text/javascript; charset=utf-8"
8208
+ };
8209
+ case granolaWebAssetPaths.stylesheet: return {
8210
+ body: granolaWebClientCss,
8211
+ contentType: "text/css; charset=utf-8"
8212
+ };
8213
+ default: return;
8214
+ }
8215
+ }
8216
+ //#endregion
6869
8217
  //#region src/server/web.ts
6870
8218
  function renderGranolaWebPage(options = {}) {
6871
8219
  return `<!doctype html>
@@ -6874,16 +8222,14 @@ function renderGranolaWebPage(options = {}) {
6874
8222
  <meta charset="utf-8" />
6875
8223
  <meta name="viewport" content="width=device-width, initial-scale=1" />
6876
8224
  <title>Granola Toolkit</title>
6877
- <style>
6878
- ${granolaWebStyles}
6879
- </style>
8225
+ <link rel="stylesheet" href="${granolaWebAssetPaths.stylesheet}" />
6880
8226
  </head>
6881
8227
  <body>
6882
- ${granolaWebMarkup}
6883
- <script type="module">
8228
+ <div id="granola-web-root"></div>
8229
+ <script>
6884
8230
  window.__GRANOLA_SERVER__ = ${JSON.stringify({ passwordRequired: options.serverPasswordRequired ?? false })};
6885
- ${granolaWebClientScript}
6886
8231
  <\/script>
8232
+ <script type="module" src="${granolaWebAssetPaths.script}"><\/script>
6887
8233
  </body>
6888
8234
  </html>`;
6889
8235
  }
@@ -7028,7 +8374,7 @@ function isPasswordAuthenticated(request, password) {
7028
8374
  return parseCookies(request)[PASSWORD_COOKIE_NAME] === password;
7029
8375
  }
7030
8376
  function publicRoute(path, enableWebClient) {
7031
- return path === granolaTransportPaths.health || path === granolaTransportPaths.serverInfo || path === granolaTransportPaths.authUnlock || enableWebClient && path === granolaTransportPaths.root;
8377
+ return path === granolaTransportPaths.health || path === granolaTransportPaths.serverInfo || path === granolaTransportPaths.authUnlock || enableWebClient && (path === granolaTransportPaths.root || Boolean(granolaWebAssetForPath(path)));
7032
8378
  }
7033
8379
  async function startGranolaServer(app, options = {}) {
7034
8380
  const enableWebClient = options.enableWebClient ?? false;
@@ -7091,6 +8437,18 @@ async function startGranolaServer(app, options = {}) {
7091
8437
  sendHtml(response, renderGranolaWebPage({ serverPasswordRequired: Boolean(security.password) }), 200, originHeaders);
7092
8438
  return;
7093
8439
  }
8440
+ if (method === "GET" && enableWebClient) {
8441
+ const asset = granolaWebAssetForPath(path);
8442
+ if (asset) {
8443
+ response.writeHead(200, {
8444
+ "content-length": Buffer.byteLength(asset.body),
8445
+ "content-type": asset.contentType,
8446
+ ...originHeaders
8447
+ });
8448
+ response.end(asset.body);
8449
+ return;
8450
+ }
8451
+ }
7094
8452
  if (method === "GET" && path === granolaTransportPaths.health) {
7095
8453
  sendJson(response, {
7096
8454
  ok: true,