hotsheet 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -163,68 +163,6 @@ var init_file_settings = __esm({
163
163
  }
164
164
  });
165
165
 
166
- // src/gitignore.ts
167
- var gitignore_exports = {};
168
- __export(gitignore_exports, {
169
- addHotsheetToGitignore: () => addHotsheetToGitignore,
170
- ensureGitignore: () => ensureGitignore,
171
- getGitRoot: () => getGitRoot,
172
- isGitRepo: () => isGitRepo,
173
- isHotsheetGitignored: () => isHotsheetGitignored
174
- });
175
- import { execSync } from "child_process";
176
- import { appendFileSync, existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
177
- import { join as join5 } from "path";
178
- function isHotsheetGitignored(repoRoot) {
179
- try {
180
- execSync("git check-ignore -q .hotsheet", { cwd: repoRoot, stdio: "ignore" });
181
- return true;
182
- } catch {
183
- return false;
184
- }
185
- }
186
- function isGitRepo(dir) {
187
- try {
188
- execSync("git rev-parse --is-inside-work-tree", { cwd: dir, stdio: "ignore" });
189
- return true;
190
- } catch {
191
- return false;
192
- }
193
- }
194
- function getGitRoot(dir) {
195
- try {
196
- return execSync("git rev-parse --show-toplevel", { cwd: dir, encoding: "utf-8" }).trim();
197
- } catch {
198
- return null;
199
- }
200
- }
201
- function addHotsheetToGitignore(repoRoot) {
202
- const gitignorePath = join5(repoRoot, ".gitignore");
203
- if (existsSync4(gitignorePath)) {
204
- const content = readFileSync4(gitignorePath, "utf-8");
205
- if (content.includes(".hotsheet")) return;
206
- const prefix = content.endsWith("\n") ? "" : "\n";
207
- appendFileSync(gitignorePath, `${prefix}.hotsheet/
208
- `);
209
- } else {
210
- appendFileSync(gitignorePath, ".hotsheet/\n");
211
- }
212
- }
213
- function ensureGitignore(cwd) {
214
- if (!isGitRepo(cwd)) return;
215
- const gitRoot = getGitRoot(cwd);
216
- if (gitRoot === null) return;
217
- if (!isHotsheetGitignored(gitRoot)) {
218
- addHotsheetToGitignore(gitRoot);
219
- console.log(" Added .hotsheet/ to .gitignore");
220
- }
221
- }
222
- var init_gitignore = __esm({
223
- "src/gitignore.ts"() {
224
- "use strict";
225
- }
226
- });
227
-
228
166
  // src/db/stats.ts
229
167
  var stats_exports = {};
230
168
  __export(stats_exports, {
@@ -420,10 +358,173 @@ var init_stats = __esm({
420
358
  }
421
359
  });
422
360
 
361
+ // src/gitignore.ts
362
+ var gitignore_exports = {};
363
+ __export(gitignore_exports, {
364
+ addHotsheetToGitignore: () => addHotsheetToGitignore,
365
+ ensureGitignore: () => ensureGitignore,
366
+ getGitRoot: () => getGitRoot,
367
+ isGitRepo: () => isGitRepo,
368
+ isHotsheetGitignored: () => isHotsheetGitignored
369
+ });
370
+ import { execSync } from "child_process";
371
+ import { appendFileSync, existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
372
+ import { join as join5 } from "path";
373
+ function isHotsheetGitignored(repoRoot) {
374
+ try {
375
+ execSync("git check-ignore -q .hotsheet", { cwd: repoRoot, stdio: "ignore" });
376
+ return true;
377
+ } catch {
378
+ return false;
379
+ }
380
+ }
381
+ function isGitRepo(dir) {
382
+ try {
383
+ execSync("git rev-parse --is-inside-work-tree", { cwd: dir, stdio: "ignore" });
384
+ return true;
385
+ } catch {
386
+ return false;
387
+ }
388
+ }
389
+ function getGitRoot(dir) {
390
+ try {
391
+ return execSync("git rev-parse --show-toplevel", { cwd: dir, encoding: "utf-8" }).trim();
392
+ } catch {
393
+ return null;
394
+ }
395
+ }
396
+ function addHotsheetToGitignore(repoRoot) {
397
+ const gitignorePath = join5(repoRoot, ".gitignore");
398
+ if (existsSync4(gitignorePath)) {
399
+ const content = readFileSync4(gitignorePath, "utf-8");
400
+ if (content.includes(".hotsheet")) return;
401
+ const prefix = content.endsWith("\n") ? "" : "\n";
402
+ appendFileSync(gitignorePath, `${prefix}.hotsheet/
403
+ `);
404
+ } else {
405
+ appendFileSync(gitignorePath, ".hotsheet/\n");
406
+ }
407
+ }
408
+ function ensureGitignore(cwd) {
409
+ if (!isGitRepo(cwd)) return;
410
+ const gitRoot = getGitRoot(cwd);
411
+ if (gitRoot === null) return;
412
+ if (!isHotsheetGitignored(gitRoot)) {
413
+ addHotsheetToGitignore(gitRoot);
414
+ console.log(" Added .hotsheet/ to .gitignore");
415
+ }
416
+ }
417
+ var init_gitignore = __esm({
418
+ "src/gitignore.ts"() {
419
+ "use strict";
420
+ }
421
+ });
422
+
423
+ // src/channel-config.ts
424
+ var channel_config_exports = {};
425
+ __export(channel_config_exports, {
426
+ getChannelPort: () => getChannelPort,
427
+ isChannelAlive: () => isChannelAlive,
428
+ registerChannel: () => registerChannel,
429
+ triggerChannel: () => triggerChannel,
430
+ unregisterChannel: () => unregisterChannel
431
+ });
432
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
433
+ import { join as join8, resolve } from "path";
434
+ function getChannelServerPath() {
435
+ const cwd = process.cwd();
436
+ const distPath = resolve(cwd, "dist", "channel.js");
437
+ if (existsSync6(distPath)) {
438
+ return { command: "node", args: [distPath] };
439
+ }
440
+ const srcPath = resolve(cwd, "src", "channel.ts");
441
+ if (existsSync6(srcPath)) {
442
+ return { command: "npx", args: ["tsx", srcPath] };
443
+ }
444
+ return { command: "node", args: [distPath] };
445
+ }
446
+ function registerChannel(dataDir2) {
447
+ const cwd = process.cwd();
448
+ const mcpPath = join8(cwd, ".mcp.json");
449
+ const { command, args } = getChannelServerPath();
450
+ let config = {};
451
+ if (existsSync6(mcpPath)) {
452
+ try {
453
+ config = JSON.parse(readFileSync6(mcpPath, "utf-8"));
454
+ } catch {
455
+ }
456
+ }
457
+ if (!config.mcpServers) config.mcpServers = {};
458
+ config.mcpServers[MCP_SERVER_KEY] = {
459
+ command,
460
+ args: [...args, "--data-dir", dataDir2]
461
+ };
462
+ writeFileSync6(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
463
+ }
464
+ function unregisterChannel() {
465
+ const cwd = process.cwd();
466
+ const mcpPath = join8(cwd, ".mcp.json");
467
+ if (!existsSync6(mcpPath)) return;
468
+ try {
469
+ const config = JSON.parse(readFileSync6(mcpPath, "utf-8"));
470
+ if (config.mcpServers?.[MCP_SERVER_KEY]) {
471
+ delete config.mcpServers[MCP_SERVER_KEY];
472
+ writeFileSync6(mcpPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
473
+ }
474
+ } catch {
475
+ }
476
+ }
477
+ function getChannelPort(dataDir2) {
478
+ try {
479
+ const portStr = readFileSync6(join8(dataDir2, "channel-port"), "utf-8").trim();
480
+ const port2 = parseInt(portStr, 10);
481
+ return isNaN(port2) ? null : port2;
482
+ } catch {
483
+ return null;
484
+ }
485
+ }
486
+ async function isChannelAlive(dataDir2) {
487
+ const port2 = getChannelPort(dataDir2);
488
+ if (!port2) return false;
489
+ try {
490
+ const res = await fetch(`http://127.0.0.1:${port2}/health`);
491
+ const data = await res.json();
492
+ return data.ok === true;
493
+ } catch {
494
+ return false;
495
+ }
496
+ }
497
+ async function triggerChannel(dataDir2, serverPort, message) {
498
+ const port2 = getChannelPort(dataDir2);
499
+ if (!port2) return false;
500
+ const defaultMessage = [
501
+ "Process the Hot Sheet worklist. Run /hotsheet to work through the current Up Next items.",
502
+ "",
503
+ `When you are completely finished processing all items (or if the worklist was empty), signal completion by running:`,
504
+ `curl -s -X POST http://localhost:${serverPort}/api/channel/done`
505
+ ].join("\n");
506
+ try {
507
+ const res = await fetch(`http://127.0.0.1:${port2}/trigger`, {
508
+ method: "POST",
509
+ body: message || defaultMessage
510
+ });
511
+ return res.ok;
512
+ } catch {
513
+ return false;
514
+ }
515
+ }
516
+ var MCP_SERVER_KEY;
517
+ var init_channel_config = __esm({
518
+ "src/channel-config.ts"() {
519
+ "use strict";
520
+ MCP_SERVER_KEY = "hotsheet-channel";
521
+ }
522
+ });
523
+
423
524
  // src/cli.ts
424
525
  import { mkdirSync as mkdirSync6 } from "fs";
425
526
  import { tmpdir } from "os";
426
- import { join as join11, resolve } from "path";
527
+ import { join as join12, resolve as resolve2 } from "path";
427
528
 
428
529
  // src/backup.ts
429
530
  init_connection();
@@ -712,23 +813,23 @@ function parseNotes(raw) {
712
813
  }
713
814
  return [{ id: generateNoteId(), text: raw, created_at: (/* @__PURE__ */ new Date()).toISOString() }];
714
815
  }
715
- async function editNote(ticketId, noteId, text) {
816
+ async function editNote(ticketId, noteId2, text) {
716
817
  const db2 = await getDb();
717
818
  const result = await db2.query(`SELECT notes FROM tickets WHERE id = $1`, [ticketId]);
718
819
  if (result.rows.length === 0) return null;
719
820
  const notes = parseNotes(result.rows[0].notes);
720
- const note = notes.find((n) => n.id === noteId);
821
+ const note = notes.find((n) => n.id === noteId2);
721
822
  if (!note) return null;
722
823
  note.text = text;
723
824
  await db2.query(`UPDATE tickets SET notes = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(notes), ticketId]);
724
825
  return notes;
725
826
  }
726
- async function deleteNote(ticketId, noteId) {
827
+ async function deleteNote(ticketId, noteId2) {
727
828
  const db2 = await getDb();
728
829
  const result = await db2.query(`SELECT notes FROM tickets WHERE id = $1`, [ticketId]);
729
830
  if (result.rows.length === 0) return null;
730
831
  const notes = parseNotes(result.rows[0].notes);
731
- const idx = notes.findIndex((n) => n.id === noteId);
832
+ const idx = notes.findIndex((n) => n.id === noteId2);
732
833
  if (idx === -1) return null;
733
834
  notes.splice(idx, 1);
734
835
  await db2.query(`UPDATE tickets SET notes = $1, updated_at = NOW() WHERE id = $2`, [JSON.stringify(notes), ticketId]);
@@ -1254,20 +1355,26 @@ init_connection();
1254
1355
  var DEMO_SCENARIOS = [
1255
1356
  { id: 1, label: "Main UI \u2014 all tickets with detail panel" },
1256
1357
  { id: 2, label: "Quick entry \u2014 bullet-list ticket creation" },
1257
- { id: 3, label: "Sidebar filtering \u2014 category view" },
1358
+ { id: 3, label: "Sidebar filtering \u2014 custom views and categories" },
1258
1359
  { id: 4, label: "AI worklist \u2014 Up Next tickets with notes" },
1259
1360
  { id: 5, label: "Batch operations \u2014 multi-select toolbar" },
1260
- { id: 6, label: "Detail panel \u2014 bottom orientation with notes" },
1261
- { id: 7, label: "Column view \u2014 kanban board by status" }
1361
+ { id: 6, label: "Detail panel \u2014 bottom orientation with tags and notes" },
1362
+ { id: 7, label: "Column view \u2014 kanban board by status" },
1363
+ { id: 8, label: "Dashboard \u2014 stats and charts" }
1262
1364
  ];
1263
1365
  function daysAgo(days) {
1264
1366
  const d = /* @__PURE__ */ new Date();
1265
1367
  d.setTime(d.getTime() - days * 24 * 60 * 60 * 1e3);
1266
1368
  return d.toISOString();
1267
1369
  }
1370
+ var noteId = 0;
1268
1371
  function notesJson(entries) {
1269
1372
  if (entries.length === 0) return "";
1270
- return JSON.stringify(entries.map((e) => ({ text: e.text, created_at: daysAgo(e.days_ago) })));
1373
+ return JSON.stringify(entries.map((e) => ({
1374
+ id: `n_demo_${noteId++}`,
1375
+ text: e.text,
1376
+ created_at: daysAgo(e.days_ago)
1377
+ })));
1271
1378
  }
1272
1379
  var SCENARIO_1 = [
1273
1380
  {
@@ -1277,6 +1384,7 @@ var SCENARIO_1 = [
1277
1384
  priority: "highest",
1278
1385
  status: "started",
1279
1386
  up_next: true,
1387
+ tags: ["checkout", "shipping"],
1280
1388
  notes: notesJson([{ text: "Confirmed the issue is in ShippingCalculator.consolidate(). It uses a single rate lookup instead of per-item calculation. Working on a fix that groups items by shipping method and merges the rates.", days_ago: 0.5 }]),
1281
1389
  days_ago: 5,
1282
1390
  updated_ago: 0.5
@@ -1288,6 +1396,7 @@ var SCENARIO_1 = [
1288
1396
  priority: "high",
1289
1397
  status: "not_started",
1290
1398
  up_next: true,
1399
+ tags: ["ux", "product-pages"],
1291
1400
  notes: "",
1292
1401
  days_ago: 4,
1293
1402
  updated_ago: 4
@@ -1299,6 +1408,7 @@ var SCENARIO_1 = [
1299
1408
  priority: "high",
1300
1409
  status: "started",
1301
1410
  up_next: true,
1411
+ tags: ["infrastructure", "devops"],
1302
1412
  notes: notesJson([{ text: "Created the backup script and IAM role. Testing the S3 lifecycle policy for retention.", days_ago: 1 }]),
1303
1413
  days_ago: 7,
1304
1414
  updated_ago: 1
@@ -1310,6 +1420,7 @@ var SCENARIO_1 = [
1310
1420
  priority: "high",
1311
1421
  status: "not_started",
1312
1422
  up_next: true,
1423
+ tags: ["payments"],
1313
1424
  notes: "",
1314
1425
  days_ago: 3,
1315
1426
  updated_ago: 3
@@ -1321,6 +1432,7 @@ var SCENARIO_1 = [
1321
1432
  priority: "default",
1322
1433
  status: "not_started",
1323
1434
  up_next: false,
1435
+ tags: ["performance", "images"],
1324
1436
  notes: "",
1325
1437
  days_ago: 6,
1326
1438
  updated_ago: 6
@@ -1332,6 +1444,7 @@ var SCENARIO_1 = [
1332
1444
  priority: "default",
1333
1445
  status: "not_started",
1334
1446
  up_next: false,
1447
+ tags: ["checkout", "accounts"],
1335
1448
  notes: "",
1336
1449
  days_ago: 10,
1337
1450
  updated_ago: 10
@@ -1343,6 +1456,7 @@ var SCENARIO_1 = [
1343
1456
  priority: "default",
1344
1457
  status: "started",
1345
1458
  up_next: false,
1459
+ tags: ["tax", "eu", "compliance"],
1346
1460
  notes: "",
1347
1461
  days_ago: 8,
1348
1462
  updated_ago: 2
@@ -1354,6 +1468,7 @@ var SCENARIO_1 = [
1354
1468
  priority: "default",
1355
1469
  status: "completed",
1356
1470
  up_next: false,
1471
+ tags: ["docs", "api"],
1357
1472
  notes: notesJson([{ text: "Documented all 12 order endpoints with examples. Published to /docs.", days_ago: 1 }]),
1358
1473
  days_ago: 12,
1359
1474
  updated_ago: 1,
@@ -1366,6 +1481,7 @@ var SCENARIO_1 = [
1366
1481
  priority: "highest",
1367
1482
  status: "verified",
1368
1483
  up_next: false,
1484
+ tags: ["mobile", "api"],
1369
1485
  notes: notesJson([
1370
1486
  { text: "Added CORS middleware with correct origins. Tested against staging with the mobile app builds.", days_ago: 3 },
1371
1487
  { text: "Verified fix is working in production. No more CORS errors in mobile app error logs.", days_ago: 2 }
@@ -1382,6 +1498,7 @@ var SCENARIO_1 = [
1382
1498
  priority: "low",
1383
1499
  status: "not_started",
1384
1500
  up_next: false,
1501
+ tags: ["ux", "theming"],
1385
1502
  notes: "",
1386
1503
  days_ago: 15,
1387
1504
  updated_ago: 15
@@ -1393,6 +1510,7 @@ var SCENARIO_1 = [
1393
1510
  priority: "default",
1394
1511
  status: "completed",
1395
1512
  up_next: false,
1513
+ tags: ["infrastructure", "database"],
1396
1514
  notes: notesJson([{ text: "Migrated to pg pool with max 20 connections. Load tested successfully at 500 concurrent requests.", days_ago: 4 }]),
1397
1515
  days_ago: 18,
1398
1516
  updated_ago: 4,
@@ -1405,6 +1523,7 @@ var SCENARIO_1 = [
1405
1523
  priority: "lowest",
1406
1524
  status: "not_started",
1407
1525
  up_next: false,
1526
+ tags: ["frontend", "seo"],
1408
1527
  notes: "",
1409
1528
  days_ago: 20,
1410
1529
  updated_ago: 20
@@ -1418,6 +1537,7 @@ var SCENARIO_2 = [
1418
1537
  priority: "high",
1419
1538
  status: "not_started",
1420
1539
  up_next: true,
1540
+ tags: ["auth"],
1421
1541
  notes: "",
1422
1542
  days_ago: 2,
1423
1543
  updated_ago: 2
@@ -1429,6 +1549,7 @@ var SCENARIO_2 = [
1429
1549
  priority: "default",
1430
1550
  status: "not_started",
1431
1551
  up_next: false,
1552
+ tags: ["admin"],
1432
1553
  notes: "",
1433
1554
  days_ago: 3,
1434
1555
  updated_ago: 3
@@ -1440,6 +1561,7 @@ var SCENARIO_2 = [
1440
1561
  priority: "default",
1441
1562
  status: "started",
1442
1563
  up_next: false,
1564
+ tags: ["maintenance"],
1443
1565
  notes: "",
1444
1566
  days_ago: 1,
1445
1567
  updated_ago: 0.5
@@ -1453,17 +1575,19 @@ var SCENARIO_3 = [
1453
1575
  priority: "highest",
1454
1576
  status: "started",
1455
1577
  up_next: true,
1578
+ tags: ["checkout", "pricing"],
1456
1579
  notes: "",
1457
1580
  days_ago: 3,
1458
1581
  updated_ago: 1
1459
1582
  },
1460
1583
  {
1461
1584
  title: "Search returns stale results after product update",
1462
- details: "The search index isn't being refreshed when product details change. Need to trigger re-index on product save.",
1585
+ details: "The search index isn't being refreshed when product details change.",
1463
1586
  category: "bug",
1464
1587
  priority: "high",
1465
1588
  status: "not_started",
1466
1589
  up_next: true,
1590
+ tags: ["search"],
1467
1591
  notes: "",
1468
1592
  days_ago: 5,
1469
1593
  updated_ago: 5
@@ -1475,17 +1599,19 @@ var SCENARIO_3 = [
1475
1599
  priority: "default",
1476
1600
  status: "not_started",
1477
1601
  up_next: false,
1602
+ tags: ["notifications"],
1478
1603
  notes: "",
1479
1604
  days_ago: 7,
1480
1605
  updated_ago: 7
1481
1606
  },
1482
1607
  {
1483
1608
  title: "Implement real-time inventory tracking",
1484
- details: 'Use WebSocket connections to push stock level changes to the product page. Show "Only X left" badges.',
1609
+ details: "Use WebSocket connections to push stock level changes to the product page.",
1485
1610
  category: "feature",
1486
1611
  priority: "high",
1487
1612
  status: "started",
1488
1613
  up_next: true,
1614
+ tags: ["real-time", "inventory"],
1489
1615
  notes: notesJson([{ text: "WebSocket server is set up. Working on the client-side stock badge component.", days_ago: 0.5 }]),
1490
1616
  days_ago: 6,
1491
1617
  updated_ago: 0.5
@@ -1497,84 +1623,67 @@ var SCENARIO_3 = [
1497
1623
  priority: "default",
1498
1624
  status: "not_started",
1499
1625
  up_next: false,
1626
+ tags: ["social"],
1500
1627
  notes: "",
1501
1628
  days_ago: 9,
1502
1629
  updated_ago: 9
1503
1630
  },
1504
1631
  {
1505
1632
  title: "Product video support on detail pages",
1506
- details: "Allow merchants to upload product videos alongside photos. Support mp4 and embedded YouTube URLs.",
1633
+ details: "Allow merchants to upload product videos alongside photos.",
1507
1634
  category: "feature",
1508
1635
  priority: "low",
1509
1636
  status: "not_started",
1510
1637
  up_next: false,
1638
+ tags: ["media"],
1511
1639
  notes: "",
1512
1640
  days_ago: 12,
1513
1641
  updated_ago: 12
1514
1642
  },
1515
1643
  {
1516
1644
  title: "Migrate image storage to CDN",
1517
- details: "Move product images from local disk to CloudFront. Needs URL rewriting for existing images.",
1645
+ details: "Move product images from local disk to CloudFront.",
1518
1646
  category: "task",
1519
1647
  priority: "high",
1520
1648
  status: "started",
1521
1649
  up_next: false,
1650
+ tags: ["infrastructure", "images"],
1522
1651
  notes: "",
1523
1652
  days_ago: 4,
1524
1653
  updated_ago: 2
1525
1654
  },
1526
- {
1527
- title: "Set up error monitoring with Sentry",
1528
- details: "Configure Sentry for both server and client-side error tracking. Set up alert rules for critical errors.",
1529
- category: "task",
1530
- priority: "default",
1531
- status: "completed",
1532
- up_next: false,
1533
- notes: notesJson([{ text: "Sentry configured for Node.js backend and React frontend. Alert rules set for 5xx errors.", days_ago: 3 }]),
1534
- days_ago: 10,
1535
- updated_ago: 3,
1536
- completed_ago: 3
1537
- },
1538
1655
  {
1539
1656
  title: "Support guest checkout without account creation",
1540
- details: "High-priority requirement change from product. Many users abandon at the registration step. Allow checkout with just email.",
1657
+ details: "Many users abandon at the registration step. Allow checkout with just email.",
1541
1658
  category: "requirement_change",
1542
1659
  priority: "high",
1543
1660
  status: "not_started",
1544
1661
  up_next: true,
1662
+ tags: ["checkout", "conversion"],
1545
1663
  notes: "",
1546
1664
  days_ago: 2,
1547
1665
  updated_ago: 2
1548
1666
  },
1549
- {
1550
- title: "Update return policy to 60-day window",
1551
- details: "Legal team requires extending the return window from 30 to 60 days. Update all customer-facing copy and the returns API logic.",
1552
- category: "requirement_change",
1553
- priority: "default",
1554
- status: "started",
1555
- up_next: false,
1556
- notes: "",
1557
- days_ago: 8,
1558
- updated_ago: 3
1559
- },
1560
1667
  {
1561
1668
  title: "Compare Redis vs Memcached for session storage",
1562
- details: "Current in-memory sessions don't survive restarts. Evaluate Redis and Memcached for persistence, speed, and ops complexity.",
1669
+ details: "Evaluate Redis and Memcached for persistence, speed, and ops complexity.",
1563
1670
  category: "investigation",
1564
1671
  priority: "high",
1565
1672
  status: "not_started",
1566
1673
  up_next: false,
1674
+ tags: ["infrastructure"],
1567
1675
  notes: "",
1568
1676
  days_ago: 6,
1569
1677
  updated_ago: 6
1570
1678
  },
1571
1679
  {
1572
1680
  title: "Analyze mobile conversion drop-off funnel",
1573
- details: "Mobile users convert at 1.2% vs 3.8% desktop. Investigate where in the funnel mobile users are dropping off.",
1681
+ details: "Mobile users convert at 1.2% vs 3.8% desktop. Investigate where users drop off.",
1574
1682
  category: "investigation",
1575
1683
  priority: "default",
1576
1684
  status: "not_started",
1577
1685
  up_next: false,
1686
+ tags: ["analytics", "mobile"],
1578
1687
  notes: "",
1579
1688
  days_ago: 11,
1580
1689
  updated_ago: 11
@@ -1588,6 +1697,7 @@ var SCENARIO_4 = [
1588
1697
  priority: "highest",
1589
1698
  status: "started",
1590
1699
  up_next: true,
1700
+ tags: ["concurrency", "orders"],
1591
1701
  notes: notesJson([
1592
1702
  { text: "Reproduced the issue with a concurrent request test. The problem is in OrderService.place() \u2014 it reads inventory, then decrements in a separate query without locking.", days_ago: 1 },
1593
1703
  { text: "Implemented SELECT ... FOR UPDATE on the inventory row. Running stress tests to confirm the fix holds under load.", days_ago: 0.3 }
@@ -1602,6 +1712,7 @@ var SCENARIO_4 = [
1602
1712
  priority: "high",
1603
1713
  status: "not_started",
1604
1714
  up_next: true,
1715
+ tags: ["webhooks", "api"],
1605
1716
  notes: "",
1606
1717
  days_ago: 3,
1607
1718
  updated_ago: 3
@@ -1613,6 +1724,7 @@ var SCENARIO_4 = [
1613
1724
  priority: "high",
1614
1725
  status: "not_started",
1615
1726
  up_next: true,
1727
+ tags: ["security", "api"],
1616
1728
  notes: "",
1617
1729
  days_ago: 5,
1618
1730
  updated_ago: 5
@@ -1624,31 +1736,34 @@ var SCENARIO_4 = [
1624
1736
  priority: "default",
1625
1737
  status: "not_started",
1626
1738
  up_next: true,
1739
+ tags: ["pricing"],
1627
1740
  notes: "",
1628
1741
  days_ago: 6,
1629
1742
  updated_ago: 6
1630
1743
  },
1631
1744
  {
1632
1745
  title: "Evaluate caching strategies for product catalog",
1633
- details: "Product pages are slow under load. Investigate Redis caching, CDN edge caching, and stale-while-revalidate patterns. Need to maintain cache coherency on product updates.",
1746
+ details: "Product pages are slow under load. Investigate Redis caching, CDN edge caching, and stale-while-revalidate patterns.",
1634
1747
  category: "investigation",
1635
1748
  priority: "default",
1636
1749
  status: "not_started",
1637
1750
  up_next: true,
1751
+ tags: ["performance", "caching"],
1638
1752
  notes: "",
1639
1753
  days_ago: 7,
1640
1754
  updated_ago: 7
1641
1755
  },
1642
1756
  {
1643
1757
  title: "Add bulk product import from CSV",
1644
- details: "Merchants need to upload a CSV of products to create/update inventory in batch. Support create, update, and skip-on-conflict modes.",
1758
+ details: "Merchants need to upload a CSV of products to create/update inventory in batch.",
1645
1759
  category: "feature",
1646
1760
  priority: "low",
1647
1761
  status: "completed",
1648
1762
  up_next: false,
1763
+ tags: ["admin", "import"],
1649
1764
  notes: notesJson([
1650
1765
  { text: "Implemented CSV parser using papaparse. Supports create and update modes with duplicate detection by SKU.", days_ago: 3 },
1651
- { text: "Added validation for required fields (name, price, SKU) and friendly error messages with row numbers for malformed data.", days_ago: 2 }
1766
+ { text: "Added validation for required fields (name, price, SKU) and friendly error messages with row numbers.", days_ago: 2 }
1652
1767
  ]),
1653
1768
  days_ago: 10,
1654
1769
  updated_ago: 2,
@@ -1656,14 +1771,15 @@ var SCENARIO_4 = [
1656
1771
  },
1657
1772
  {
1658
1773
  title: "Normalize database schema for customer addresses",
1659
- details: "Addresses are currently embedded as JSON in the customers table. Extract to a separate addresses table with proper foreign keys.",
1774
+ details: "Addresses are currently embedded as JSON in the customers table. Extract to a separate addresses table.",
1660
1775
  category: "task",
1661
1776
  priority: "default",
1662
1777
  status: "verified",
1663
1778
  up_next: false,
1779
+ tags: ["database", "schema"],
1664
1780
  notes: notesJson([
1665
1781
  { text: "Created migration to extract addresses into a new table. Backfilled 12,400 existing address records.", days_ago: 5 },
1666
- { text: "Verified the migration ran correctly. All address lookups use the new table. Old JSON column can be dropped in next release.", days_ago: 3 }
1782
+ { text: "Verified the migration ran correctly. All address lookups use the new table.", days_ago: 3 }
1667
1783
  ]),
1668
1784
  days_ago: 14,
1669
1785
  updated_ago: 3,
@@ -1674,55 +1790,60 @@ var SCENARIO_4 = [
1674
1790
  var SCENARIO_5 = [
1675
1791
  {
1676
1792
  title: "Fix email template rendering in Outlook",
1677
- details: "Order confirmation emails break in Outlook due to unsupported CSS flexbox. Use table-based layout.",
1793
+ details: "Order confirmation emails break in Outlook due to unsupported CSS flexbox.",
1678
1794
  category: "bug",
1679
1795
  priority: "default",
1680
1796
  status: "not_started",
1681
1797
  up_next: false,
1798
+ tags: ["email"],
1682
1799
  notes: "",
1683
1800
  days_ago: 3,
1684
1801
  updated_ago: 3
1685
1802
  },
1686
1803
  {
1687
1804
  title: "Handle timeout on third-party shipping rate API",
1688
- details: "When the shipping provider API times out, the checkout page shows a generic 500 error. Show a retry prompt instead.",
1805
+ details: "When the shipping provider API times out, show a retry prompt instead of a 500 error.",
1689
1806
  category: "bug",
1690
1807
  priority: "default",
1691
1808
  status: "not_started",
1692
1809
  up_next: false,
1810
+ tags: ["shipping", "error-handling"],
1693
1811
  notes: "",
1694
1812
  days_ago: 4,
1695
1813
  updated_ago: 4
1696
1814
  },
1697
1815
  {
1698
1816
  title: "Fix pagination on search results page",
1699
- details: "Page 2+ of search results shows duplicate items. The OFFSET calculation is wrong when filters change.",
1817
+ details: "Page 2+ of search results shows duplicate items.",
1700
1818
  category: "bug",
1701
1819
  priority: "default",
1702
1820
  status: "not_started",
1703
1821
  up_next: false,
1822
+ tags: ["search"],
1704
1823
  notes: "",
1705
1824
  days_ago: 5,
1706
1825
  updated_ago: 5
1707
1826
  },
1708
1827
  {
1709
1828
  title: "Cart badge count not updating after item removal",
1710
- details: "The header cart icon shows the old count until a full page refresh. The client state isn't being updated.",
1829
+ details: "The header cart icon shows the old count until a full page refresh.",
1711
1830
  category: "bug",
1712
1831
  priority: "high",
1713
1832
  status: "not_started",
1714
1833
  up_next: false,
1834
+ tags: ["cart", "ui"],
1715
1835
  notes: "",
1716
1836
  days_ago: 2,
1717
1837
  updated_ago: 2
1718
1838
  },
1719
1839
  {
1720
1840
  title: "Add order tracking page for customers",
1721
- details: "Customers need a page showing shipment status, tracking number, and estimated delivery. Pull data from the shipping provider API.",
1841
+ details: "Customers need a page showing shipment status, tracking number, and estimated delivery.",
1722
1842
  category: "feature",
1723
1843
  priority: "default",
1724
1844
  status: "not_started",
1725
1845
  up_next: false,
1846
+ tags: ["orders", "ux"],
1726
1847
  notes: "",
1727
1848
  days_ago: 6,
1728
1849
  updated_ago: 6
@@ -1734,17 +1855,19 @@ var SCENARIO_5 = [
1734
1855
  priority: "default",
1735
1856
  status: "not_started",
1736
1857
  up_next: false,
1858
+ tags: ["admin", "reviews"],
1737
1859
  notes: "",
1738
1860
  days_ago: 7,
1739
1861
  updated_ago: 7
1740
1862
  },
1741
1863
  {
1742
1864
  title: "Add rate limiting to public API endpoints",
1743
- details: "Protect against abuse with per-IP rate limiting. Use a sliding window algorithm. Target: 100 req/min for anonymous, 500 for authenticated.",
1865
+ details: "Protect against abuse with per-IP rate limiting. Target: 100 req/min anonymous, 500 authenticated.",
1744
1866
  category: "task",
1745
1867
  priority: "default",
1746
1868
  status: "not_started",
1747
1869
  up_next: false,
1870
+ tags: ["security", "api"],
1748
1871
  notes: "",
1749
1872
  days_ago: 8,
1750
1873
  updated_ago: 8
@@ -1756,6 +1879,7 @@ var SCENARIO_5 = [
1756
1879
  priority: "default",
1757
1880
  status: "not_started",
1758
1881
  up_next: false,
1882
+ tags: ["infrastructure", "devops"],
1759
1883
  notes: "",
1760
1884
  days_ago: 9,
1761
1885
  updated_ago: 9
@@ -1767,6 +1891,7 @@ var SCENARIO_5 = [
1767
1891
  priority: "low",
1768
1892
  status: "not_started",
1769
1893
  up_next: false,
1894
+ tags: ["cleanup"],
1770
1895
  notes: "",
1771
1896
  days_ago: 12,
1772
1897
  updated_ago: 12
@@ -1778,6 +1903,7 @@ var SCENARIO_5 = [
1778
1903
  priority: "low",
1779
1904
  status: "not_started",
1780
1905
  up_next: false,
1906
+ tags: ["cleanup", "database"],
1781
1907
  notes: "",
1782
1908
  days_ago: 14,
1783
1909
  updated_ago: 14
@@ -1791,6 +1917,7 @@ var SCENARIO_6 = [
1791
1917
  priority: "highest",
1792
1918
  status: "started",
1793
1919
  up_next: true,
1920
+ tags: ["real-time", "orders", "websocket"],
1794
1921
  notes: notesJson([
1795
1922
  { text: "Set up the WebSocket server using ws library. Basic connection lifecycle working \u2014 connect, heartbeat, disconnect with cleanup.", days_ago: 3 },
1796
1923
  { text: "Implemented the event broadcast system. When an order status changes in the API, all connected clients for that order receive a push event. Added Redis pub/sub for multi-server support.", days_ago: 2 },
@@ -1806,6 +1933,7 @@ var SCENARIO_6 = [
1806
1933
  priority: "high",
1807
1934
  status: "started",
1808
1935
  up_next: true,
1936
+ tags: ["performance", "memory", "search"],
1809
1937
  notes: notesJson([{ text: "Heap snapshot shows the BatchProcessor holding references to completed batches. The onComplete callbacks are never cleaned up.", days_ago: 1 }]),
1810
1938
  days_ago: 5,
1811
1939
  updated_ago: 1
@@ -1817,17 +1945,19 @@ var SCENARIO_6 = [
1817
1945
  priority: "high",
1818
1946
  status: "not_started",
1819
1947
  up_next: true,
1948
+ tags: ["testing", "payments"],
1820
1949
  notes: "",
1821
1950
  days_ago: 4,
1822
1951
  updated_ago: 4
1823
1952
  },
1824
1953
  {
1825
1954
  title: "Add product recommendations based on purchase history",
1826
- details: 'Show "Customers also bought" recommendations on product pages using collaborative filtering on order history.',
1955
+ details: 'Show "Customers also bought" recommendations on product pages using collaborative filtering.',
1827
1956
  category: "feature",
1828
1957
  priority: "default",
1829
1958
  status: "completed",
1830
1959
  up_next: false,
1960
+ tags: ["ml", "recommendations", "product-pages"],
1831
1961
  notes: notesJson([
1832
1962
  { text: "Implemented a simple collaborative filtering algorithm. Computes item-item similarity from co-purchase frequency in the last 90 days.", days_ago: 5 },
1833
1963
  { text: "Added the recommendations API endpoint and the product page widget. Limited to 4 recommendations. Recalculation runs nightly via cron.", days_ago: 3 }
@@ -1838,14 +1968,15 @@ var SCENARIO_6 = [
1838
1968
  },
1839
1969
  {
1840
1970
  title: "Migrate static assets to CDN",
1841
- details: "Product images, CSS, and JS bundles should be served from CloudFront. Reduces server load and improves page load times globally.",
1971
+ details: "Product images, CSS, and JS bundles should be served from CloudFront.",
1842
1972
  category: "task",
1843
1973
  priority: "default",
1844
1974
  status: "verified",
1845
1975
  up_next: false,
1976
+ tags: ["infrastructure", "cdn", "performance"],
1846
1977
  notes: notesJson([
1847
- { text: "Configured CloudFront distribution with S3 origin. Migrated all product images (42GB) using the AWS CLI sync command.", days_ago: 7 },
1848
- { text: "Updated asset URLs in the application to use the CDN domain. Cache hit rate is at 94% after 48 hours. TTFB improved from 240ms to 35ms for static assets.", days_ago: 5 }
1978
+ { text: "Configured CloudFront distribution with S3 origin. Migrated all product images (42GB).", days_ago: 7 },
1979
+ { text: "Updated asset URLs. Cache hit rate is at 94% after 48 hours. TTFB improved from 240ms to 35ms.", days_ago: 5 }
1849
1980
  ]),
1850
1981
  days_ago: 16,
1851
1982
  updated_ago: 5,
@@ -1859,6 +1990,7 @@ var SCENARIO_6 = [
1859
1990
  priority: "default",
1860
1991
  status: "not_started",
1861
1992
  up_next: false,
1993
+ tags: ["navigation", "ui"],
1862
1994
  notes: "",
1863
1995
  days_ago: 8,
1864
1996
  updated_ago: 8
@@ -1867,88 +1999,96 @@ var SCENARIO_6 = [
1867
1999
  var SCENARIO_7 = [
1868
2000
  {
1869
2001
  title: "Implement product search autocomplete",
1870
- details: "Add typeahead suggestions to the search bar using the product name index. Show top 5 matches with thumbnails.",
2002
+ details: "Add typeahead suggestions to the search bar using the product name index.",
1871
2003
  category: "feature",
1872
2004
  priority: "highest",
1873
2005
  status: "not_started",
1874
2006
  up_next: true,
2007
+ tags: ["search", "ux"],
1875
2008
  notes: "",
1876
2009
  days_ago: 2,
1877
2010
  updated_ago: 2
1878
2011
  },
1879
2012
  {
1880
2013
  title: "Fix broken password reset flow for SSO users",
1881
- details: "SSO users who try to reset their password get a generic error. Should redirect them to their identity provider instead.",
2014
+ details: "SSO users who try to reset their password get a generic error.",
1882
2015
  category: "bug",
1883
2016
  priority: "high",
1884
2017
  status: "not_started",
1885
2018
  up_next: true,
2019
+ tags: ["auth", "sso"],
1886
2020
  notes: "",
1887
2021
  days_ago: 3,
1888
2022
  updated_ago: 3
1889
2023
  },
1890
2024
  {
1891
2025
  title: "Add support for gift cards at checkout",
1892
- details: "Customers should be able to apply gift card codes during checkout. Support partial redemption and balance tracking.",
2026
+ details: "Support gift card codes during checkout with partial redemption and balance tracking.",
1893
2027
  category: "feature",
1894
2028
  priority: "default",
1895
2029
  status: "not_started",
1896
2030
  up_next: false,
2031
+ tags: ["checkout", "payments"],
1897
2032
  notes: "",
1898
2033
  days_ago: 5,
1899
2034
  updated_ago: 5
1900
2035
  },
1901
2036
  {
1902
2037
  title: "Investigate slow query on order history page",
1903
- details: "The order history page takes 4+ seconds for users with 200+ orders. Profile the query and add proper indexing.",
2038
+ details: "The order history page takes 4+ seconds for users with 200+ orders.",
1904
2039
  category: "investigation",
1905
2040
  priority: "high",
1906
2041
  status: "not_started",
1907
2042
  up_next: false,
2043
+ tags: ["performance", "database"],
1908
2044
  notes: "",
1909
2045
  days_ago: 4,
1910
2046
  updated_ago: 4
1911
2047
  },
1912
2048
  {
1913
2049
  title: "Refactor authentication middleware to support API keys",
1914
- details: "Third-party integrations need API key auth in addition to session cookies. Extract auth into a strategy pattern.",
2050
+ details: "Third-party integrations need API key auth in addition to session cookies.",
1915
2051
  category: "task",
1916
2052
  priority: "high",
1917
2053
  status: "started",
1918
2054
  up_next: true,
1919
- notes: notesJson([{ text: "Created the AuthStrategy interface and migrated session auth to use it. Working on the API key strategy next.", days_ago: 0.5 }]),
2055
+ tags: ["auth", "api"],
2056
+ notes: notesJson([{ text: "Created the AuthStrategy interface and migrated session auth. Working on the API key strategy next.", days_ago: 0.5 }]),
1920
2057
  days_ago: 4,
1921
2058
  updated_ago: 0.5
1922
2059
  },
1923
2060
  {
1924
2061
  title: "Fix cart not clearing after successful checkout",
1925
- details: "After a successful order placement, the cart retains all items. The clearCart() call is inside a catch block by mistake.",
2062
+ details: "The clearCart() call is inside a catch block by mistake.",
1926
2063
  category: "bug",
1927
2064
  priority: "highest",
1928
2065
  status: "started",
1929
2066
  up_next: true,
2067
+ tags: ["checkout", "cart"],
1930
2068
  notes: notesJson([{ text: "Found the issue \u2014 clearCart() was moved into the catch block during a refactor. Fixing and adding a test.", days_ago: 0.3 }]),
1931
2069
  days_ago: 1,
1932
2070
  updated_ago: 0.3
1933
2071
  },
1934
2072
  {
1935
2073
  title: "Update shipping rate calculation for oversized items",
1936
- details: "Dimensional weight pricing is required for packages over 1 cubic foot. Current flat-rate calculation undercharges.",
2074
+ details: "Dimensional weight pricing is required for packages over 1 cubic foot.",
1937
2075
  category: "requirement_change",
1938
2076
  priority: "default",
1939
2077
  status: "started",
1940
2078
  up_next: false,
1941
- notes: notesJson([{ text: "Implemented dim weight formula. Comparing rates against the carrier API to validate accuracy.", days_ago: 1 }]),
2079
+ tags: ["shipping", "pricing"],
2080
+ notes: notesJson([{ text: "Implemented dim weight formula. Comparing rates against the carrier API.", days_ago: 1 }]),
1942
2081
  days_ago: 6,
1943
2082
  updated_ago: 1
1944
2083
  },
1945
2084
  {
1946
2085
  title: "Add end-to-end tests for the checkout flow",
1947
- details: "Write Playwright tests covering: add to cart, apply coupon, enter shipping, pay, and confirm. Cover happy path and key error cases.",
2086
+ details: "Write Playwright tests covering: add to cart, apply coupon, enter shipping, pay, and confirm.",
1948
2087
  category: "task",
1949
2088
  priority: "default",
1950
2089
  status: "completed",
1951
2090
  up_next: false,
2091
+ tags: ["testing", "e2e"],
1952
2092
  notes: notesJson([{ text: "Wrote 8 E2E tests covering the full checkout flow including coupon application and payment decline handling.", days_ago: 1 }]),
1953
2093
  days_ago: 8,
1954
2094
  updated_ago: 1,
@@ -1956,29 +2096,54 @@ var SCENARIO_7 = [
1956
2096
  },
1957
2097
  {
1958
2098
  title: "Fix product image carousel swipe on mobile",
1959
- details: "Swipe gestures on the product image carousel conflict with the browser back gesture. Use a swipe threshold to disambiguate.",
2099
+ details: "Swipe gestures conflict with the browser back gesture.",
1960
2100
  category: "bug",
1961
2101
  priority: "default",
1962
2102
  status: "completed",
1963
2103
  up_next: false,
1964
- notes: notesJson([{ text: "Added a 30px horizontal threshold before initiating carousel swipe. Tested on iOS Safari and Chrome Android.", days_ago: 2 }]),
2104
+ tags: ["mobile", "ui"],
2105
+ notes: notesJson([{ text: "Added a 30px horizontal threshold. Tested on iOS Safari and Chrome Android.", days_ago: 2 }]),
1965
2106
  days_ago: 7,
1966
2107
  updated_ago: 2,
1967
2108
  completed_ago: 2
1968
2109
  },
1969
2110
  {
1970
2111
  title: "Set up log aggregation with structured JSON logging",
1971
- details: "Replace console.log calls with a structured logger (pino). Send logs to a central aggregation service for search and alerting.",
2112
+ details: "Replace console.log calls with pino. Send logs to a central aggregation service.",
1972
2113
  category: "task",
1973
2114
  priority: "low",
1974
2115
  status: "completed",
1975
2116
  up_next: false,
1976
- notes: notesJson([{ text: "Replaced all console.log calls with pino. Configured log shipping to the aggregation service. Alert rules set for error-level logs.", days_ago: 3 }]),
2117
+ tags: ["observability", "logging"],
2118
+ notes: notesJson([{ text: "Replaced all console.log with pino. Configured log shipping. Alert rules set for error-level logs.", days_ago: 3 }]),
1977
2119
  days_ago: 10,
1978
2120
  updated_ago: 3,
1979
2121
  completed_ago: 3
1980
2122
  }
1981
2123
  ];
2124
+ var SCENARIO_8 = [];
2125
+ for (let i = 0; i < 30; i++) {
2126
+ const cats = ["bug", "feature", "task", "investigation", "requirement_change", "issue"];
2127
+ const pris = ["highest", "high", "default", "low", "lowest"];
2128
+ const statuses = ["not_started", "started", "completed", "verified"];
2129
+ const status = statuses[i < 8 ? 0 : i < 14 ? 1 : i < 24 ? 2 : 3];
2130
+ const completed = status === "completed" || status === "verified" ? 30 - i + Math.floor(Math.random() * 5) : void 0;
2131
+ const verified = status === "verified" ? completed - 2 : void 0;
2132
+ SCENARIO_8.push({
2133
+ title: `Dashboard ticket ${i + 1} \u2014 ${cats[i % cats.length]} work item`,
2134
+ details: "",
2135
+ category: cats[i % cats.length],
2136
+ priority: pris[i % pris.length],
2137
+ status,
2138
+ up_next: i < 3,
2139
+ tags: [],
2140
+ notes: status === "completed" || status === "verified" ? notesJson([{ text: "Completed work.", days_ago: completed }]) : "",
2141
+ days_ago: 30 - i + Math.floor(Math.random() * 10),
2142
+ updated_ago: completed || Math.floor(Math.random() * 10),
2143
+ completed_ago: completed,
2144
+ verified_ago: verified
2145
+ });
2146
+ }
1982
2147
  var SCENARIO_DATA = {
1983
2148
  1: SCENARIO_1,
1984
2149
  2: SCENARIO_2,
@@ -1986,8 +2151,29 @@ var SCENARIO_DATA = {
1986
2151
  4: SCENARIO_4,
1987
2152
  5: SCENARIO_5,
1988
2153
  6: SCENARIO_6,
1989
- 7: SCENARIO_7
2154
+ 7: SCENARIO_7,
2155
+ 8: SCENARIO_8
1990
2156
  };
2157
+ var SCENARIO_3_VIEWS = [
2158
+ {
2159
+ id: "high-priority-bugs",
2160
+ name: "High Priority Bugs",
2161
+ logic: "all",
2162
+ conditions: [
2163
+ { field: "category", operator: "equals", value: "bug" },
2164
+ { field: "priority", operator: "lte", value: "high" }
2165
+ ]
2166
+ },
2167
+ {
2168
+ id: "active-features",
2169
+ name: "Active Features",
2170
+ logic: "all",
2171
+ conditions: [
2172
+ { field: "category", operator: "equals", value: "feature" },
2173
+ { field: "status", operator: "lte", value: "started" }
2174
+ ]
2175
+ }
2176
+ ];
1991
2177
  async function seedDemoData(scenario) {
1992
2178
  const db2 = await getDb();
1993
2179
  const tickets = SCENARIO_DATA[scenario];
@@ -1999,12 +2185,19 @@ async function seedDemoData(scenario) {
1999
2185
  const updatedAt = daysAgo(t.updated_ago);
2000
2186
  const completedAt = t.completed_ago !== void 0 ? daysAgo(t.completed_ago) : null;
2001
2187
  const verifiedAt = t.verified_ago !== void 0 ? daysAgo(t.verified_ago) : null;
2188
+ const tags = JSON.stringify(t.tags);
2002
2189
  await db2.query(`
2003
- INSERT INTO tickets (ticket_number, title, details, category, priority, status, up_next, notes, created_at, updated_at, completed_at, verified_at)
2004
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::timestamp, $10::timestamp, $11::timestamp, $12::timestamp)
2005
- `, [ticketNumber, t.title, t.details, t.category, t.priority, t.status, t.up_next, t.notes, createdAt, updatedAt, completedAt, verifiedAt]);
2190
+ INSERT INTO tickets (ticket_number, title, details, category, priority, status, up_next, notes, tags, created_at, updated_at, completed_at, verified_at)
2191
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10::timestamp, $11::timestamp, $12::timestamp, $13::timestamp)
2192
+ `, [ticketNumber, t.title, t.details, t.category, t.priority, t.status, t.up_next, t.notes, tags, createdAt, updatedAt, completedAt, verifiedAt]);
2006
2193
  }
2007
2194
  await db2.query(`SELECT setval('ticket_seq', $1)`, [tickets.length]);
2195
+ if (scenario === 3) {
2196
+ await db2.query(
2197
+ `INSERT INTO settings (key, value) VALUES ('custom_views', $1) ON CONFLICT (key) DO UPDATE SET value = $1`,
2198
+ [JSON.stringify(SCENARIO_3_VIEWS)]
2199
+ );
2200
+ }
2008
2201
  if (scenario === 6) {
2009
2202
  await db2.query(`UPDATE settings SET value = 'bottom' WHERE key = 'detail_position'`);
2010
2203
  await db2.query(`UPDATE settings SET value = '280' WHERE key = 'detail_height'`);
@@ -2012,6 +2205,11 @@ async function seedDemoData(scenario) {
2012
2205
  if (scenario === 7) {
2013
2206
  await db2.query(`INSERT INTO settings (key, value) VALUES ('layout', 'columns') ON CONFLICT (key) DO UPDATE SET value = 'columns'`);
2014
2207
  }
2208
+ if (scenario === 8) {
2209
+ const { backfillSnapshots: backfillSnapshots2, recordDailySnapshot: recordDailySnapshot2 } = await Promise.resolve().then(() => (init_stats(), stats_exports));
2210
+ await backfillSnapshots2();
2211
+ await recordDailySnapshot2();
2212
+ }
2015
2213
  }
2016
2214
 
2017
2215
  // src/cli.ts
@@ -2020,15 +2218,15 @@ init_gitignore();
2020
2218
  // src/server.ts
2021
2219
  import { serve } from "@hono/node-server";
2022
2220
  import { exec } from "child_process";
2023
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
2221
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
2024
2222
  import { Hono as Hono4 } from "hono";
2025
- import { dirname, join as join9 } from "path";
2223
+ import { dirname, join as join10 } from "path";
2026
2224
  import { fileURLToPath } from "url";
2027
2225
 
2028
2226
  // src/routes/api.ts
2029
- import { existsSync as existsSync6, mkdirSync as mkdirSync4, rmSync as rmSync5 } from "fs";
2227
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, rmSync as rmSync5 } from "fs";
2030
2228
  import { Hono } from "hono";
2031
- import { basename, extname, join as join8, relative as relative2 } from "path";
2229
+ import { basename, extname, join as join9, relative as relative2 } from "path";
2032
2230
 
2033
2231
  // src/skills.ts
2034
2232
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
@@ -2479,8 +2677,8 @@ function notifyChange() {
2479
2677
  changeVersion++;
2480
2678
  const waiters = pollWaiters;
2481
2679
  pollWaiters = [];
2482
- for (const resolve2 of waiters) {
2483
- resolve2(changeVersion);
2680
+ for (const resolve3 of waiters) {
2681
+ resolve3(changeVersion);
2484
2682
  }
2485
2683
  }
2486
2684
  apiRoutes.get("/poll", async (c) => {
@@ -2489,11 +2687,11 @@ apiRoutes.get("/poll", async (c) => {
2489
2687
  return c.json({ version: changeVersion });
2490
2688
  }
2491
2689
  const version = await Promise.race([
2492
- new Promise((resolve2) => {
2493
- pollWaiters.push(resolve2);
2690
+ new Promise((resolve3) => {
2691
+ pollWaiters.push(resolve3);
2494
2692
  }),
2495
- new Promise((resolve2) => {
2496
- setTimeout(() => resolve2(changeVersion), 3e4);
2693
+ new Promise((resolve3) => {
2694
+ setTimeout(() => resolve3(changeVersion), 3e4);
2497
2695
  })
2498
2696
  ]);
2499
2697
  return c.json({ version });
@@ -2548,11 +2746,24 @@ apiRoutes.delete("/tickets/:id", async (c) => {
2548
2746
  notifyChange();
2549
2747
  return c.json({ ok: true });
2550
2748
  });
2749
+ apiRoutes.put("/tickets/:id/notes-bulk", async (c) => {
2750
+ const id = parseInt(c.req.param("id"), 10);
2751
+ const body = await c.req.json();
2752
+ const db2 = await Promise.resolve().then(() => (init_connection(), connection_exports)).then((m) => m.getDb());
2753
+ const result = await (await db2).query(
2754
+ `UPDATE tickets SET notes = $1, updated_at = NOW() WHERE id = $2 RETURNING id`,
2755
+ [body.notes, id]
2756
+ );
2757
+ if (result.rows.length === 0) return c.json({ error: "Not found" }, 404);
2758
+ scheduleAllSync();
2759
+ notifyChange();
2760
+ return c.json({ ok: true });
2761
+ });
2551
2762
  apiRoutes.patch("/tickets/:id/notes/:noteId", async (c) => {
2552
2763
  const id = parseInt(c.req.param("id"), 10);
2553
- const noteId = c.req.param("noteId");
2764
+ const noteId2 = c.req.param("noteId");
2554
2765
  const body = await c.req.json();
2555
- const notes = await editNote(id, noteId, body.text);
2766
+ const notes = await editNote(id, noteId2, body.text);
2556
2767
  if (!notes) return c.json({ error: "Not found" }, 404);
2557
2768
  scheduleAllSync();
2558
2769
  notifyChange();
@@ -2560,8 +2771,8 @@ apiRoutes.patch("/tickets/:id/notes/:noteId", async (c) => {
2560
2771
  });
2561
2772
  apiRoutes.delete("/tickets/:id/notes/:noteId", async (c) => {
2562
2773
  const id = parseInt(c.req.param("id"), 10);
2563
- const noteId = c.req.param("noteId");
2564
- const notes = await deleteNote(id, noteId);
2774
+ const noteId2 = c.req.param("noteId");
2775
+ const notes = await deleteNote(id, noteId2);
2565
2776
  if (!notes) return c.json({ error: "Not found" }, 404);
2566
2777
  scheduleAllSync();
2567
2778
  notifyChange();
@@ -2660,12 +2871,12 @@ apiRoutes.post("/tickets/:id/attachments", async (c) => {
2660
2871
  const ext = extname(originalName);
2661
2872
  const baseName = basename(originalName, ext);
2662
2873
  const storedName = `${ticket.ticket_number}_${baseName}${ext}`;
2663
- const attachDir = join8(dataDir2, "attachments");
2874
+ const attachDir = join9(dataDir2, "attachments");
2664
2875
  mkdirSync4(attachDir, { recursive: true });
2665
- const storedPath = join8(attachDir, storedName);
2876
+ const storedPath = join9(attachDir, storedName);
2666
2877
  const buffer = Buffer.from(await file.arrayBuffer());
2667
- const { writeFileSync: writeFileSync7 } = await import("fs");
2668
- writeFileSync7(storedPath, buffer);
2878
+ const { writeFileSync: writeFileSync8 } = await import("fs");
2879
+ writeFileSync8(storedPath, buffer);
2669
2880
  const attachment = await addAttachment(id, originalName, storedPath);
2670
2881
  scheduleAllSync();
2671
2882
  notifyChange();
@@ -2687,7 +2898,7 @@ apiRoutes.post("/attachments/:id/reveal", async (c) => {
2687
2898
  const id = parseInt(c.req.param("id"), 10);
2688
2899
  const attachment = await getAttachment(id);
2689
2900
  if (!attachment) return c.json({ error: "Not found" }, 404);
2690
- if (!existsSync6(attachment.stored_path)) return c.json({ error: "File not found on disk" }, 404);
2901
+ if (!existsSync7(attachment.stored_path)) return c.json({ error: "File not found on disk" }, 404);
2691
2902
  const { execFile } = await import("child_process");
2692
2903
  const { dirname: dirname3 } = await import("path");
2693
2904
  const platform = process.platform;
@@ -2703,12 +2914,12 @@ apiRoutes.post("/attachments/:id/reveal", async (c) => {
2703
2914
  apiRoutes.get("/attachments/file/*", async (c) => {
2704
2915
  const filePath = c.req.path.replace("/api/attachments/file/", "");
2705
2916
  const dataDir2 = c.get("dataDir");
2706
- const fullPath = join8(dataDir2, "attachments", filePath);
2707
- if (!existsSync6(fullPath)) {
2917
+ const fullPath = join9(dataDir2, "attachments", filePath);
2918
+ if (!existsSync7(fullPath)) {
2708
2919
  return c.json({ error: "File not found" }, 404);
2709
2920
  }
2710
- const { readFileSync: readFileSync8 } = await import("fs");
2711
- const content = readFileSync8(fullPath);
2921
+ const { readFileSync: readFileSync9 } = await import("fs");
2922
+ const content = readFileSync9(fullPath);
2712
2923
  const ext = extname(fullPath).toLowerCase();
2713
2924
  const mimeTypes = {
2714
2925
  ".png": "image/png",
@@ -2788,7 +2999,7 @@ apiRoutes.patch("/file-settings", async (c) => {
2788
2999
  apiRoutes.get("/worklist-info", (c) => {
2789
3000
  const dataDir2 = c.get("dataDir");
2790
3001
  const cwd = process.cwd();
2791
- const worklistRel = relative2(cwd, join8(dataDir2, "worklist.md"));
3002
+ const worklistRel = relative2(cwd, join9(dataDir2, "worklist.md"));
2792
3003
  const prompt = `Read ${worklistRel} for current work items.`;
2793
3004
  ensureSkills();
2794
3005
  const skillCreated = consumeSkillsCreatedFlag();
@@ -2828,14 +3039,68 @@ apiRoutes.post("/gitignore/add", async (c) => {
2828
3039
  ensureGitignore2(process.cwd());
2829
3040
  return c.json({ ok: true });
2830
3041
  });
3042
+ apiRoutes.get("/channel/claude-check", async (c) => {
3043
+ const { execFileSync } = await import("child_process");
3044
+ try {
3045
+ const version = execFileSync("claude", ["--version"], { timeout: 5e3, encoding: "utf-8" }).trim();
3046
+ const match = version.match(/(\d+\.\d+\.\d+)/);
3047
+ const versionNum = match ? match[1] : null;
3048
+ const parts = versionNum ? versionNum.split(".").map(Number) : [];
3049
+ const meetsMinimum = parts.length === 3 && (parts[0] > 2 || parts[0] === 2 && parts[1] > 1 || parts[0] === 2 && parts[1] === 1 && parts[2] >= 80);
3050
+ return c.json({ installed: true, version: versionNum, meetsMinimum });
3051
+ } catch {
3052
+ return c.json({ installed: false, version: null, meetsMinimum: false });
3053
+ }
3054
+ });
3055
+ var channelDoneFlag = false;
3056
+ apiRoutes.get("/channel/status", async (c) => {
3057
+ const { isChannelAlive: isChannelAlive2, getChannelPort: getChannelPort2 } = await Promise.resolve().then(() => (init_channel_config(), channel_config_exports));
3058
+ const dataDir2 = c.get("dataDir");
3059
+ const settings = await getSettings();
3060
+ const enabled = settings.channel_enabled === "true";
3061
+ const port2 = getChannelPort2(dataDir2);
3062
+ const alive = enabled ? await isChannelAlive2(dataDir2) : false;
3063
+ const done = channelDoneFlag;
3064
+ if (done) channelDoneFlag = false;
3065
+ return c.json({ enabled, alive, port: port2, done });
3066
+ });
3067
+ apiRoutes.post("/channel/trigger", async (c) => {
3068
+ const { triggerChannel: triggerChannel2 } = await Promise.resolve().then(() => (init_channel_config(), channel_config_exports));
3069
+ const dataDir2 = c.get("dataDir");
3070
+ const serverPort = parseInt(new URL(c.req.url).port || "4174", 10);
3071
+ const body = await c.req.json().catch(() => ({ message: void 0 }));
3072
+ channelDoneFlag = false;
3073
+ const ok = await triggerChannel2(dataDir2, serverPort, body.message);
3074
+ return c.json({ ok });
3075
+ });
3076
+ apiRoutes.post("/channel/done", async (_c) => {
3077
+ channelDoneFlag = true;
3078
+ notifyChange();
3079
+ return _c.json({ ok: true });
3080
+ });
3081
+ apiRoutes.post("/channel/enable", async (c) => {
3082
+ const { registerChannel: registerChannel2 } = await Promise.resolve().then(() => (init_channel_config(), channel_config_exports));
3083
+ const dataDir2 = c.get("dataDir");
3084
+ await updateSetting("channel_enabled", "true");
3085
+ registerChannel2(dataDir2);
3086
+ notifyChange();
3087
+ return c.json({ ok: true });
3088
+ });
3089
+ apiRoutes.post("/channel/disable", async (c) => {
3090
+ const { unregisterChannel: unregisterChannel2 } = await Promise.resolve().then(() => (init_channel_config(), channel_config_exports));
3091
+ await updateSetting("channel_enabled", "false");
3092
+ unregisterChannel2();
3093
+ notifyChange();
3094
+ return c.json({ ok: true });
3095
+ });
2831
3096
  apiRoutes.post("/print", async (c) => {
2832
3097
  const { html } = await c.req.json();
2833
- const { writeFileSync: writeFileSync7 } = await import("fs");
3098
+ const { writeFileSync: writeFileSync8 } = await import("fs");
2834
3099
  const { tmpdir: tmpdir2 } = await import("os");
2835
3100
  const { join: pathJoin } = await import("path");
2836
3101
  const { execFile } = await import("child_process");
2837
3102
  const tmpPath = pathJoin(tmpdir2(), `hotsheet-print-${Date.now()}.html`);
2838
- writeFileSync7(tmpPath, html, "utf-8");
3103
+ writeFileSync8(tmpPath, html, "utf-8");
2839
3104
  const platform = process.platform;
2840
3105
  if (platform === "darwin") {
2841
3106
  execFile("open", [tmpPath]);
@@ -3165,6 +3430,13 @@ pageRoutes.get("/", (c) => {
3165
3430
  ] }),
3166
3431
  /* @__PURE__ */ jsx("div", { className: "app-body", children: [
3167
3432
  /* @__PURE__ */ jsx("nav", { className: "sidebar", children: [
3433
+ /* @__PURE__ */ jsx("div", { className: "sidebar-channel-play", id: "channel-play-section", style: "display:none", children: /* @__PURE__ */ jsx("button", { className: "channel-play-btn", id: "channel-play-btn", title: "Run worklist (double-click for auto mode)", children: [
3434
+ /* @__PURE__ */ jsx("span", { className: "channel-play-icon", id: "channel-play-icon", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "currentColor", stroke: "none", children: /* @__PURE__ */ jsx("polygon", { points: "6 3 20 12 6 21 6 3" }) }) }),
3435
+ /* @__PURE__ */ jsx("span", { className: "channel-auto-icon", id: "channel-auto-icon", style: "display:none", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "white", stroke: "none", children: [
3436
+ /* @__PURE__ */ jsx("path", { d: "M12 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 12 18z" }),
3437
+ /* @__PURE__ */ jsx("path", { d: "M2 6a2 2 0 0 1 3.414-1.414l6 6a2 2 0 0 1 0 2.828l-6 6A2 2 0 0 1 2 18z" })
3438
+ ] }) })
3439
+ ] }) }),
3168
3440
  /* @__PURE__ */ jsx("div", { className: "sidebar-copy-prompt", id: "copy-prompt-section", style: "display:none", children: /* @__PURE__ */ jsx("button", { className: "copy-prompt-btn", id: "copy-prompt-btn", title: "Copy worklist prompt to clipboard", children: [
3169
3441
  /* @__PURE__ */ jsx("span", { className: "copy-prompt-icon", id: "copy-prompt-icon", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
3170
3442
  /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
@@ -3193,7 +3465,7 @@ pageRoutes.get("/", (c) => {
3193
3465
  /* @__PURE__ */ jsx("button", { className: "sidebar-item", "data-view": "archive", children: "Archive" }),
3194
3466
  /* @__PURE__ */ jsx("button", { className: "sidebar-item", "data-view": "trash", children: "Trash" })
3195
3467
  ] }),
3196
- /* @__PURE__ */ jsx("div", { className: "sidebar-section", children: [
3468
+ /* @__PURE__ */ jsx("div", { className: "sidebar-section", id: "sidebar-categories", children: [
3197
3469
  /* @__PURE__ */ jsx("div", { className: "sidebar-label", children: "Category" }),
3198
3470
  /* @__PURE__ */ jsx("button", { className: "sidebar-item", "data-view": "category:issue", children: [
3199
3471
  /* @__PURE__ */ jsx("span", { className: "cat-dot", style: "background:#6b7280" }),
@@ -3346,7 +3618,10 @@ pageRoutes.get("/", (c) => {
3346
3618
  " close"
3347
3619
  ] })
3348
3620
  ] }),
3349
- /* @__PURE__ */ jsx("div", { id: "status-bar", className: "status-bar" })
3621
+ /* @__PURE__ */ jsx("div", { className: "status-bar-right", children: [
3622
+ /* @__PURE__ */ jsx("div", { id: "status-bar", className: "status-bar" }),
3623
+ /* @__PURE__ */ jsx("span", { id: "channel-status-indicator", className: "channel-status-indicator", style: "display:none" })
3624
+ ] })
3350
3625
  ] })
3351
3626
  ] }),
3352
3627
  /* @__PURE__ */ jsx("div", { className: "settings-overlay", id: "settings-overlay", style: "display:none", children: /* @__PURE__ */ jsx("div", { className: "settings-dialog", children: [
@@ -3408,7 +3683,24 @@ pageRoutes.get("/", (c) => {
3408
3683
  /* @__PURE__ */ jsx("div", { className: "settings-field", children: [
3409
3684
  /* @__PURE__ */ jsx("label", { children: "Auto-clear verified after (days)" }),
3410
3685
  /* @__PURE__ */ jsx("input", { type: "number", id: "settings-verified-days", min: "1", value: "30" })
3411
- ] })
3686
+ ] }),
3687
+ /* @__PURE__ */ jsx("div", { id: "settings-channel-section", style: "display:none", children: /* @__PURE__ */ jsx("div", { className: "settings-section", style: "margin-top:16px", children: [
3688
+ /* @__PURE__ */ jsx("h3", { className: "settings-experimental-heading", children: "Experimental" }),
3689
+ /* @__PURE__ */ jsx("div", { className: "settings-field", children: [
3690
+ /* @__PURE__ */ jsx("label", { className: "settings-checkbox-label", children: [
3691
+ /* @__PURE__ */ jsx("input", { type: "checkbox", id: "settings-channel-enabled" }),
3692
+ "Enable Claude Channel integration"
3693
+ ] }),
3694
+ /* @__PURE__ */ jsx("span", { className: "settings-hint", id: "settings-channel-hint", children: "Push worklist events to a running Claude Code session via MCP channels." }),
3695
+ /* @__PURE__ */ jsx("div", { id: "settings-channel-instructions", style: "display:none", children: [
3696
+ /* @__PURE__ */ jsx("div", { className: "settings-hint", style: "margin-top:8px", children: "Launch Claude Code with channel support:" }),
3697
+ /* @__PURE__ */ jsx("div", { className: "settings-channel-command", children: [
3698
+ /* @__PURE__ */ jsx("code", { id: "settings-channel-cmd", children: "claude --dangerously-load-development-channels server:hotsheet-channel" }),
3699
+ /* @__PURE__ */ jsx("button", { className: "btn btn-sm", id: "settings-channel-copy-btn", title: "Copy command", children: "Copy" })
3700
+ ] })
3701
+ ] })
3702
+ ] })
3703
+ ] }) })
3412
3704
  ] }),
3413
3705
  /* @__PURE__ */ jsx("div", { className: "settings-tab-panel", "data-panel": "categories", children: [
3414
3706
  /* @__PURE__ */ jsx("div", { className: "settings-section-header", children: [
@@ -3444,11 +3736,11 @@ pageRoutes.get("/", (c) => {
3444
3736
  });
3445
3737
 
3446
3738
  // src/server.ts
3447
- function tryServe(fetch, port2) {
3448
- return new Promise((resolve2, reject) => {
3449
- const server = serve({ fetch, port: port2 });
3739
+ function tryServe(fetch2, port2) {
3740
+ return new Promise((resolve3, reject) => {
3741
+ const server = serve({ fetch: fetch2, port: port2 });
3450
3742
  server.on("listening", () => {
3451
- resolve2(port2);
3743
+ resolve3(port2);
3452
3744
  });
3453
3745
  server.on("error", (err) => {
3454
3746
  reject(err);
@@ -3462,20 +3754,20 @@ async function startServer(port2, dataDir2, options) {
3462
3754
  await next();
3463
3755
  });
3464
3756
  const selfDir = dirname(fileURLToPath(import.meta.url));
3465
- const distDir = existsSync7(join9(selfDir, "client", "styles.css")) ? join9(selfDir, "client") : join9(selfDir, "..", "dist", "client");
3757
+ const distDir = existsSync8(join10(selfDir, "client", "styles.css")) ? join10(selfDir, "client") : join10(selfDir, "..", "dist", "client");
3466
3758
  app.get("/static/styles.css", (c) => {
3467
- const css = readFileSync6(join9(distDir, "styles.css"), "utf-8");
3759
+ const css = readFileSync7(join10(distDir, "styles.css"), "utf-8");
3468
3760
  return c.text(css, 200, { "Content-Type": "text/css", "Cache-Control": "no-cache" });
3469
3761
  });
3470
3762
  app.get("/static/app.js", (c) => {
3471
- const js = readFileSync6(join9(distDir, "app.global.js"), "utf-8");
3763
+ const js = readFileSync7(join10(distDir, "app.global.js"), "utf-8");
3472
3764
  return c.text(js, 200, { "Content-Type": "application/javascript", "Cache-Control": "no-cache" });
3473
3765
  });
3474
3766
  app.get("/static/assets/:filename", (c) => {
3475
3767
  const filename = c.req.param("filename");
3476
- const filePath = join9(distDir, "assets", filename);
3477
- if (!existsSync7(filePath)) return c.notFound();
3478
- const content = readFileSync6(filePath);
3768
+ const filePath = join10(distDir, "assets", filename);
3769
+ if (!existsSync8(filePath)) return c.notFound();
3770
+ const content = readFileSync7(filePath);
3479
3771
  const ext = filename.split(".").pop();
3480
3772
  const mimeTypes = { png: "image/png", jpg: "image/jpeg", svg: "image/svg+xml" };
3481
3773
  return new Response(content, { headers: { "Content-Type": mimeTypes[ext || ""] || "application/octet-stream", "Cache-Control": "max-age=86400" } });
@@ -3519,18 +3811,18 @@ async function startServer(port2, dataDir2, options) {
3519
3811
  }
3520
3812
 
3521
3813
  // src/update-check.ts
3522
- import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
3814
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
3523
3815
  import { get } from "https";
3524
3816
  import { homedir } from "os";
3525
- import { dirname as dirname2, join as join10 } from "path";
3817
+ import { dirname as dirname2, join as join11 } from "path";
3526
3818
  import { fileURLToPath as fileURLToPath2 } from "url";
3527
- var DATA_DIR = join10(homedir(), ".hotsheet");
3528
- var CHECK_FILE = join10(DATA_DIR, "last-update-check");
3819
+ var DATA_DIR = join11(homedir(), ".hotsheet");
3820
+ var CHECK_FILE = join11(DATA_DIR, "last-update-check");
3529
3821
  var PACKAGE_NAME = "hotsheet";
3530
3822
  function getCurrentVersion() {
3531
3823
  try {
3532
3824
  const dir = dirname2(fileURLToPath2(import.meta.url));
3533
- const pkg = JSON.parse(readFileSync7(join10(dir, "..", "package.json"), "utf-8"));
3825
+ const pkg = JSON.parse(readFileSync8(join11(dir, "..", "package.json"), "utf-8"));
3534
3826
  return pkg.version;
3535
3827
  } catch {
3536
3828
  return "0.0.0";
@@ -3538,8 +3830,8 @@ function getCurrentVersion() {
3538
3830
  }
3539
3831
  function getLastCheckDate() {
3540
3832
  try {
3541
- if (existsSync8(CHECK_FILE)) {
3542
- return readFileSync7(CHECK_FILE, "utf-8").trim();
3833
+ if (existsSync9(CHECK_FILE)) {
3834
+ return readFileSync8(CHECK_FILE, "utf-8").trim();
3543
3835
  }
3544
3836
  } catch {
3545
3837
  }
@@ -3547,7 +3839,7 @@ function getLastCheckDate() {
3547
3839
  }
3548
3840
  function saveCheckDate() {
3549
3841
  mkdirSync5(DATA_DIR, { recursive: true });
3550
- writeFileSync6(CHECK_FILE, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), "utf-8");
3842
+ writeFileSync7(CHECK_FILE, (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), "utf-8");
3551
3843
  }
3552
3844
  function isFirstUseToday() {
3553
3845
  const last = getLastCheckDate();
@@ -3556,10 +3848,10 @@ function isFirstUseToday() {
3556
3848
  return last !== today;
3557
3849
  }
3558
3850
  function fetchLatestVersion() {
3559
- return new Promise((resolve2) => {
3851
+ return new Promise((resolve3) => {
3560
3852
  const req = get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { timeout: 5e3 }, (res) => {
3561
3853
  if (res.statusCode !== 200) {
3562
- resolve2(null);
3854
+ resolve3(null);
3563
3855
  return;
3564
3856
  }
3565
3857
  let data = "";
@@ -3568,18 +3860,18 @@ function fetchLatestVersion() {
3568
3860
  });
3569
3861
  res.on("end", () => {
3570
3862
  try {
3571
- resolve2(JSON.parse(data).version);
3863
+ resolve3(JSON.parse(data).version);
3572
3864
  } catch {
3573
- resolve2(null);
3865
+ resolve3(null);
3574
3866
  }
3575
3867
  });
3576
3868
  });
3577
3869
  req.on("error", () => {
3578
- resolve2(null);
3870
+ resolve3(null);
3579
3871
  });
3580
3872
  req.on("timeout", () => {
3581
3873
  req.destroy();
3582
- resolve2(null);
3874
+ resolve3(null);
3583
3875
  });
3584
3876
  });
3585
3877
  }
@@ -3652,7 +3944,7 @@ Examples:
3652
3944
  function parseArgs(argv) {
3653
3945
  const args = argv.slice(2);
3654
3946
  let port2 = 4174;
3655
- let dataDir2 = join11(process.cwd(), ".hotsheet");
3947
+ let dataDir2 = join12(process.cwd(), ".hotsheet");
3656
3948
  let demo = null;
3657
3949
  let forceUpdateCheck = false;
3658
3950
  let noOpen = false;
@@ -3681,7 +3973,7 @@ function parseArgs(argv) {
3681
3973
  }
3682
3974
  break;
3683
3975
  case "--data-dir":
3684
- dataDir2 = resolve(args[++i]);
3976
+ dataDir2 = resolve2(args[++i]);
3685
3977
  break;
3686
3978
  case "--check-for-updates":
3687
3979
  forceUpdateCheck = true;
@@ -3719,7 +4011,7 @@ async function main() {
3719
4011
  }
3720
4012
  process.exit(1);
3721
4013
  }
3722
- dataDir2 = join11(tmpdir(), `hotsheet-demo-${demo}-${Date.now()}`);
4014
+ dataDir2 = join12(tmpdir(), `hotsheet-demo-${demo}-${Date.now()}`);
3723
4015
  console.log(`
3724
4016
  DEMO MODE: ${scenario.label}
3725
4017
  `);