hotsheet 0.2.13 → 0.3.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
@@ -420,16 +420,85 @@ function initBackupScheduler(dataDir2) {
420
420
  // src/cleanup.ts
421
421
  import { rmSync as rmSync3 } from "fs";
422
422
 
423
+ // src/types.ts
424
+ var DEFAULT_CATEGORIES = [
425
+ { id: "issue", label: "Issue", shortLabel: "ISS", color: "#6b7280", shortcutKey: "i", description: "General issues that need attention" },
426
+ { id: "bug", label: "Bug", shortLabel: "BUG", color: "#ef4444", shortcutKey: "b", description: "Bugs that should be fixed in the codebase" },
427
+ { id: "feature", label: "Feature", shortLabel: "FEA", color: "#22c55e", shortcutKey: "f", description: "New features to be implemented" },
428
+ { id: "requirement_change", label: "Req Change", shortLabel: "REQ", color: "#f97316", shortcutKey: "r", description: "Changes to existing requirements" },
429
+ { id: "task", label: "Task", shortLabel: "TSK", color: "#3b82f6", shortcutKey: "k", description: "General tasks to complete" },
430
+ { id: "investigation", label: "Investigation", shortLabel: "INV", color: "#8b5cf6", shortcutKey: "g", description: "Items requiring research or analysis" }
431
+ ];
432
+ var CATEGORY_PRESETS = [
433
+ {
434
+ id: "software",
435
+ name: "Software Development",
436
+ categories: DEFAULT_CATEGORIES
437
+ },
438
+ {
439
+ id: "design",
440
+ name: "Design / Creative",
441
+ categories: [
442
+ { id: "concept", label: "Concept", shortLabel: "CON", color: "#8b5cf6", shortcutKey: "c", description: "Design concepts and explorations" },
443
+ { id: "revision", label: "Revision", shortLabel: "REV", color: "#f97316", shortcutKey: "r", description: "Revisions to existing designs" },
444
+ { id: "feedback", label: "Feedback", shortLabel: "FDB", color: "#3b82f6", shortcutKey: "f", description: "Client or stakeholder feedback to address" },
445
+ { id: "asset", label: "Asset", shortLabel: "AST", color: "#22c55e", shortcutKey: "a", description: "Assets to produce or deliver" },
446
+ { id: "research", label: "Research", shortLabel: "RSC", color: "#6b7280", shortcutKey: "s", description: "User research or competitive analysis" },
447
+ { id: "bug", label: "Bug", shortLabel: "BUG", color: "#ef4444", shortcutKey: "b", description: "Visual or UI bugs" }
448
+ ]
449
+ },
450
+ {
451
+ id: "product",
452
+ name: "Product Management",
453
+ categories: [
454
+ { id: "epic", label: "Epic", shortLabel: "EPC", color: "#8b5cf6", shortcutKey: "e", description: "Large initiatives spanning multiple stories" },
455
+ { id: "story", label: "Story", shortLabel: "STY", color: "#3b82f6", shortcutKey: "s", description: "User stories describing desired functionality" },
456
+ { id: "bug", label: "Bug", shortLabel: "BUG", color: "#ef4444", shortcutKey: "b", description: "Bugs that need to be fixed" },
457
+ { id: "task", label: "Task", shortLabel: "TSK", color: "#22c55e", shortcutKey: "t", description: "Tasks to complete" },
458
+ { id: "spike", label: "Spike", shortLabel: "SPK", color: "#f97316", shortcutKey: "k", description: "Research or investigation spikes" },
459
+ { id: "debt", label: "Tech Debt", shortLabel: "DBT", color: "#6b7280", shortcutKey: "d", description: "Technical debt to address" }
460
+ ]
461
+ },
462
+ {
463
+ id: "marketing",
464
+ name: "Marketing",
465
+ categories: [
466
+ { id: "campaign", label: "Campaign", shortLabel: "CMP", color: "#8b5cf6", shortcutKey: "c", description: "Marketing campaigns" },
467
+ { id: "content", label: "Content", shortLabel: "CNT", color: "#3b82f6", shortcutKey: "n", description: "Content to create or publish" },
468
+ { id: "design", label: "Design", shortLabel: "DES", color: "#22c55e", shortcutKey: "d", description: "Design requests and assets" },
469
+ { id: "analytics", label: "Analytics", shortLabel: "ANL", color: "#f97316", shortcutKey: "a", description: "Analytics and reporting tasks" },
470
+ { id: "outreach", label: "Outreach", shortLabel: "OUT", color: "#6b7280", shortcutKey: "o", description: "Outreach and partnership activities" },
471
+ { id: "event", label: "Event", shortLabel: "EVT", color: "#ef4444", shortcutKey: "e", description: "Events to plan or manage" }
472
+ ]
473
+ },
474
+ {
475
+ id: "personal",
476
+ name: "Personal",
477
+ categories: [
478
+ { id: "task", label: "Task", shortLabel: "TSK", color: "#3b82f6", shortcutKey: "t", description: "Things to do" },
479
+ { id: "idea", label: "Idea", shortLabel: "IDA", color: "#22c55e", shortcutKey: "i", description: "Ideas to explore" },
480
+ { id: "note", label: "Note", shortLabel: "NTE", color: "#6b7280", shortcutKey: "n", description: "Notes and references" },
481
+ { id: "errand", label: "Errand", shortLabel: "ERR", color: "#f97316", shortcutKey: "e", description: "Errands and appointments" },
482
+ { id: "project", label: "Project", shortLabel: "PRJ", color: "#8b5cf6", shortcutKey: "p", description: "Larger projects" },
483
+ { id: "urgent", label: "Urgent", shortLabel: "URG", color: "#ef4444", shortcutKey: "u", description: "Urgent items" }
484
+ ]
485
+ }
486
+ ];
487
+ var CATEGORIES = DEFAULT_CATEGORIES.map((c) => ({ value: c.id, label: c.label, color: c.color }));
488
+ var CATEGORY_DESCRIPTIONS = Object.fromEntries(
489
+ DEFAULT_CATEGORIES.map((c) => [c.id, c.description])
490
+ );
491
+
423
492
  // src/db/queries.ts
424
493
  init_connection();
425
- function parseNotes(raw2) {
426
- if (!raw2 || raw2 === "") return [];
494
+ function parseNotes(raw) {
495
+ if (!raw || raw === "") return [];
427
496
  try {
428
- const parsed = JSON.parse(raw2);
497
+ const parsed = JSON.parse(raw);
429
498
  if (Array.isArray(parsed)) return parsed;
430
499
  } catch {
431
500
  }
432
- return [{ text: raw2, created_at: (/* @__PURE__ */ new Date()).toISOString() }];
501
+ return [{ text: raw, created_at: (/* @__PURE__ */ new Date()).toISOString() }];
433
502
  }
434
503
  async function nextTicketNumber() {
435
504
  const db2 = await getDb();
@@ -681,6 +750,20 @@ async function getTicketsForCleanup(verifiedDays = 30, trashDays = 3) {
681
750
  `, [verifiedDays, trashDays]);
682
751
  return result.rows;
683
752
  }
753
+ async function getCategories() {
754
+ const settings = await getSettings();
755
+ if (settings.categories) {
756
+ try {
757
+ const parsed = JSON.parse(settings.categories);
758
+ if (Array.isArray(parsed) && parsed.length > 0) return parsed;
759
+ } catch {
760
+ }
761
+ }
762
+ return DEFAULT_CATEGORIES;
763
+ }
764
+ async function saveCategories(categories) {
765
+ await updateSetting("categories", JSON.stringify(categories));
766
+ }
684
767
  async function getSettings() {
685
768
  const db2 = await getDb();
686
769
  const result = await db2.query("SELECT key, value FROM settings");
@@ -1611,33 +1694,25 @@ import { basename, extname, join as join8, relative as relative2 } from "path";
1611
1694
  // src/skills.ts
1612
1695
  import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
1613
1696
  import { join as join6, relative } from "path";
1614
-
1615
- // src/types.ts
1616
- var CATEGORY_DESCRIPTIONS = {
1617
- issue: "General issues that need attention",
1618
- bug: "Bugs that should be fixed in the codebase",
1619
- feature: "New features to be implemented",
1620
- requirement_change: "Changes to existing requirements",
1621
- task: "General tasks to complete",
1622
- investigation: "Items requiring research or analysis"
1623
- };
1624
-
1625
- // src/skills.ts
1626
- var SKILL_VERSION = 2;
1697
+ var SKILL_VERSION = 3;
1627
1698
  var skillPort;
1628
1699
  var skillDataDir;
1700
+ var skillCategories = DEFAULT_CATEGORIES;
1629
1701
  function initSkills(port2, dataDir2) {
1630
1702
  skillPort = port2;
1631
1703
  skillDataDir = dataDir2;
1632
1704
  }
1633
- var TICKET_SKILLS = [
1634
- { name: "hs-bug", category: "bug", label: "bug" },
1635
- { name: "hs-feature", category: "feature", label: "feature" },
1636
- { name: "hs-task", category: "task", label: "task" },
1637
- { name: "hs-issue", category: "issue", label: "issue" },
1638
- { name: "hs-investigation", category: "investigation", label: "investigation" },
1639
- { name: "hs-req-change", category: "requirement_change", label: "requirement change" }
1640
- ];
1705
+ function setSkillCategories(categories) {
1706
+ skillCategories = categories;
1707
+ }
1708
+ function buildTicketSkills() {
1709
+ return skillCategories.map((cat) => ({
1710
+ name: `hs-${cat.id.replace(/_/g, "-")}`,
1711
+ category: cat.id,
1712
+ label: cat.label.toLowerCase(),
1713
+ description: cat.description
1714
+ }));
1715
+ }
1641
1716
  function versionHeader() {
1642
1717
  return `<!-- hotsheet-skill-version: ${SKILL_VERSION} -->`;
1643
1718
  }
@@ -1658,9 +1733,8 @@ function updateFile(path, content) {
1658
1733
  return true;
1659
1734
  }
1660
1735
  function ticketSkillBody(skill) {
1661
- const desc = CATEGORY_DESCRIPTIONS[skill.category];
1662
1736
  return [
1663
- `Create a new Hot Sheet **${skill.label}** ticket. ${desc}.`,
1737
+ `Create a new Hot Sheet **${skill.label}** ticket. ${skill.description}.`,
1664
1738
  "",
1665
1739
  "**Parsing the input:**",
1666
1740
  '- If the input starts with "next", "up next", or "do next" (case-insensitive), set `up_next` to `true` and use the remaining text as the title',
@@ -1731,7 +1805,7 @@ function ensureClaudeSkills(cwd) {
1731
1805
  ""
1732
1806
  ].join("\n");
1733
1807
  if (updateFile(join6(mainDir, "SKILL.md"), mainContent)) updated = true;
1734
- for (const skill of TICKET_SKILLS) {
1808
+ for (const skill of buildTicketSkills()) {
1735
1809
  const dir = join6(skillsDir, skill.name);
1736
1810
  mkdirSync3(dir, { recursive: true });
1737
1811
  const content = [
@@ -1764,7 +1838,7 @@ function ensureCursorRules(cwd) {
1764
1838
  ""
1765
1839
  ].join("\n");
1766
1840
  if (updateFile(join6(rulesDir, "hotsheet.mdc"), mainContent)) updated = true;
1767
- for (const skill of TICKET_SKILLS) {
1841
+ for (const skill of buildTicketSkills()) {
1768
1842
  const content = [
1769
1843
  "---",
1770
1844
  `description: Create a new ${skill.label} ticket in Hot Sheet`,
@@ -1793,7 +1867,7 @@ function ensureCopilotPrompts(cwd) {
1793
1867
  ""
1794
1868
  ].join("\n");
1795
1869
  if (updateFile(join6(promptsDir, "hotsheet.prompt.md"), mainContent)) updated = true;
1796
- for (const skill of TICKET_SKILLS) {
1870
+ for (const skill of buildTicketSkills()) {
1797
1871
  const content = [
1798
1872
  "---",
1799
1873
  `description: Create a new ${skill.label} ticket in Hot Sheet`,
@@ -1822,7 +1896,7 @@ function ensureWindsurfRules(cwd) {
1822
1896
  ""
1823
1897
  ].join("\n");
1824
1898
  if (updateFile(join6(rulesDir, "hotsheet.md"), mainContent)) updated = true;
1825
- for (const skill of TICKET_SKILLS) {
1899
+ for (const skill of buildTicketSkills()) {
1826
1900
  const content = [
1827
1901
  "---",
1828
1902
  "trigger: manual",
@@ -1893,14 +1967,14 @@ function scheduleAllSync() {
1893
1967
  scheduleWorklistSync();
1894
1968
  scheduleOpenTicketsSync();
1895
1969
  }
1896
- function parseTicketNotes(raw2) {
1897
- if (!raw2 || raw2 === "") return [];
1970
+ function parseTicketNotes(raw) {
1971
+ if (!raw || raw === "") return [];
1898
1972
  try {
1899
- const parsed = JSON.parse(raw2);
1973
+ const parsed = JSON.parse(raw);
1900
1974
  if (Array.isArray(parsed)) return parsed;
1901
1975
  } catch {
1902
1976
  }
1903
- if (raw2.trim()) return [{ text: raw2, created_at: "" }];
1977
+ if (raw.trim()) return [{ text: raw, created_at: "" }];
1904
1978
  return [];
1905
1979
  }
1906
1980
  async function formatTicket(ticket) {
@@ -1935,10 +2009,12 @@ async function formatTicket(ticket) {
1935
2009
  }
1936
2010
  return lines.join("\n");
1937
2011
  }
1938
- function formatCategoryDescriptions(categories) {
2012
+ async function formatCategoryDescriptions(usedCategories) {
2013
+ const allCategories = await getCategories();
2014
+ const descMap = Object.fromEntries(allCategories.map((c) => [c.id, c.description]));
1939
2015
  const lines = ["Ticket Types:"];
1940
- for (const cat of categories) {
1941
- lines.push(`- ${cat} - ${CATEGORY_DESCRIPTIONS[cat]}`);
2016
+ for (const cat of usedCategories) {
2017
+ lines.push(`- ${cat} - ${descMap[cat] || cat}`);
1942
2018
  }
1943
2019
  return lines.join("\n");
1944
2020
  }
@@ -1976,7 +2052,9 @@ async function syncWorklist() {
1976
2052
  sections.push("- Create follow-up tasks for items outside the current scope");
1977
2053
  sections.push("");
1978
2054
  sections.push("To create a ticket:");
1979
- sections.push(` \`curl -s -X POST http://localhost:${port}/api/tickets -H "Content-Type: application/json" -d '{"title": "Title", "defaults": {"category": "bug|feature|task|issue|investigation|requirement_change", "up_next": false}}'\``);
2055
+ const allCats = await getCategories();
2056
+ const catIds = allCats.map((c) => c.id).join("|");
2057
+ sections.push(` \`curl -s -X POST http://localhost:${port}/api/tickets -H "Content-Type: application/json" -d '{"title": "Title", "defaults": {"category": "${catIds}", "up_next": false}}'\``);
1980
2058
  sections.push("");
1981
2059
  sections.push('You can also include `"details"` in the defaults object for longer descriptions.');
1982
2060
  sections.push("Set `up_next: true` only for items that should be prioritized immediately.");
@@ -1994,7 +2072,7 @@ async function syncWorklist() {
1994
2072
  }
1995
2073
  sections.push("---");
1996
2074
  sections.push("");
1997
- sections.push(formatCategoryDescriptions(categories));
2075
+ sections.push(await formatCategoryDescriptions(categories));
1998
2076
  }
1999
2077
  sections.push("");
2000
2078
  writeFileSync5(join7(dataDir, "worklist.md"), sections.join("\n"), "utf-8");
@@ -2038,7 +2116,7 @@ async function syncOpenTickets() {
2038
2116
  } else {
2039
2117
  sections.push("---");
2040
2118
  sections.push("");
2041
- sections.push(formatCategoryDescriptions(categories));
2119
+ sections.push(await formatCategoryDescriptions(categories));
2042
2120
  }
2043
2121
  sections.push("");
2044
2122
  writeFileSync5(join7(dataDir, "open-tickets.md"), sections.join("\n"), "utf-8");
@@ -2282,6 +2360,20 @@ apiRoutes.get("/attachments/file/*", async (c) => {
2282
2360
  headers: { "Content-Type": contentType }
2283
2361
  });
2284
2362
  });
2363
+ apiRoutes.get("/categories", async (c) => {
2364
+ const categories = await getCategories();
2365
+ return c.json(categories);
2366
+ });
2367
+ apiRoutes.put("/categories", async (c) => {
2368
+ const categories = await c.req.json();
2369
+ await saveCategories(categories);
2370
+ scheduleAllSync();
2371
+ notifyChange();
2372
+ return c.json(categories);
2373
+ });
2374
+ apiRoutes.get("/category-presets", (c) => {
2375
+ return c.json(CATEGORY_PRESETS);
2376
+ });
2285
2377
  apiRoutes.get("/stats", async (c) => {
2286
2378
  const stats = await getTicketStats();
2287
2379
  return c.json(stats);
@@ -2424,24 +2516,7 @@ var SafeHtml = class {
2424
2516
  return this.__html;
2425
2517
  }
2426
2518
  };
2427
- function raw(html) {
2428
- return new SafeHtml(html);
2429
- }
2430
- var VOID_TAGS = /* @__PURE__ */ new Set([
2431
- "area",
2432
- "base",
2433
- "br",
2434
- "col",
2435
- "embed",
2436
- "hr",
2437
- "img",
2438
- "input",
2439
- "link",
2440
- "meta",
2441
- "source",
2442
- "track",
2443
- "wbr"
2444
- ]);
2519
+ var VOID_TAGS = /* @__PURE__ */ new Set(["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "source", "track", "wbr"]);
2445
2520
  function renderChildren(children) {
2446
2521
  if (children == null || typeof children === "boolean") return "";
2447
2522
  if (children instanceof SafeHtml) return children.__html;
@@ -2450,10 +2525,134 @@ function renderChildren(children) {
2450
2525
  if (Array.isArray(children)) return children.map(renderChildren).join("");
2451
2526
  return "";
2452
2527
  }
2528
+ var ATTR_ALIASES = {
2529
+ // HTML attributes
2530
+ className: "class",
2531
+ htmlFor: "for",
2532
+ httpEquiv: "http-equiv",
2533
+ acceptCharset: "accept-charset",
2534
+ accessKey: "accesskey",
2535
+ autoCapitalize: "autocapitalize",
2536
+ autoComplete: "autocomplete",
2537
+ autoFocus: "autofocus",
2538
+ autoPlay: "autoplay",
2539
+ colSpan: "colspan",
2540
+ contentEditable: "contenteditable",
2541
+ crossOrigin: "crossorigin",
2542
+ dateTime: "datetime",
2543
+ defaultChecked: "checked",
2544
+ defaultValue: "value",
2545
+ encType: "enctype",
2546
+ formAction: "formaction",
2547
+ formEncType: "formenctype",
2548
+ formMethod: "formmethod",
2549
+ formNoValidate: "formnovalidate",
2550
+ formTarget: "formtarget",
2551
+ hrefLang: "hreflang",
2552
+ inputMode: "inputmode",
2553
+ maxLength: "maxlength",
2554
+ minLength: "minlength",
2555
+ noModule: "nomodule",
2556
+ noValidate: "novalidate",
2557
+ readOnly: "readonly",
2558
+ referrerPolicy: "referrerpolicy",
2559
+ rowSpan: "rowspan",
2560
+ spellCheck: "spellcheck",
2561
+ srcDoc: "srcdoc",
2562
+ srcLang: "srclang",
2563
+ srcSet: "srcset",
2564
+ tabIndex: "tabindex",
2565
+ useMap: "usemap",
2566
+ // SVG presentation attributes (camelCase → kebab-case)
2567
+ strokeWidth: "stroke-width",
2568
+ strokeLinecap: "stroke-linecap",
2569
+ strokeLinejoin: "stroke-linejoin",
2570
+ strokeDasharray: "stroke-dasharray",
2571
+ strokeDashoffset: "stroke-dashoffset",
2572
+ strokeMiterlimit: "stroke-miterlimit",
2573
+ strokeOpacity: "stroke-opacity",
2574
+ fillOpacity: "fill-opacity",
2575
+ fillRule: "fill-rule",
2576
+ clipPath: "clip-path",
2577
+ clipRule: "clip-rule",
2578
+ colorInterpolation: "color-interpolation",
2579
+ colorInterpolationFilters: "color-interpolation-filters",
2580
+ floodColor: "flood-color",
2581
+ floodOpacity: "flood-opacity",
2582
+ lightingColor: "lighting-color",
2583
+ stopColor: "stop-color",
2584
+ stopOpacity: "stop-opacity",
2585
+ shapeRendering: "shape-rendering",
2586
+ imageRendering: "image-rendering",
2587
+ textRendering: "text-rendering",
2588
+ pointerEvents: "pointer-events",
2589
+ vectorEffect: "vector-effect",
2590
+ paintOrder: "paint-order",
2591
+ // SVG text/font attributes
2592
+ fontFamily: "font-family",
2593
+ fontSize: "font-size",
2594
+ fontStyle: "font-style",
2595
+ fontVariant: "font-variant",
2596
+ fontWeight: "font-weight",
2597
+ fontStretch: "font-stretch",
2598
+ textAnchor: "text-anchor",
2599
+ textDecoration: "text-decoration",
2600
+ dominantBaseline: "dominant-baseline",
2601
+ alignmentBaseline: "alignment-baseline",
2602
+ baselineShift: "baseline-shift",
2603
+ letterSpacing: "letter-spacing",
2604
+ wordSpacing: "word-spacing",
2605
+ writingMode: "writing-mode",
2606
+ glyphOrientationHorizontal: "glyph-orientation-horizontal",
2607
+ glyphOrientationVertical: "glyph-orientation-vertical",
2608
+ // SVG marker/gradient/filter attributes
2609
+ markerStart: "marker-start",
2610
+ markerMid: "marker-mid",
2611
+ markerEnd: "marker-end",
2612
+ gradientUnits: "gradientUnits",
2613
+ gradientTransform: "gradientTransform",
2614
+ spreadMethod: "spreadMethod",
2615
+ patternUnits: "patternUnits",
2616
+ patternContentUnits: "patternContentUnits",
2617
+ patternTransform: "patternTransform",
2618
+ maskUnits: "maskUnits",
2619
+ maskContentUnits: "maskContentUnits",
2620
+ filterUnits: "filterUnits",
2621
+ primitiveUnits: "primitiveUnits",
2622
+ clipPathUnits: "clipPathUnits",
2623
+ // SVG xlink (legacy but still used)
2624
+ xlinkHref: "xlink:href",
2625
+ xlinkShow: "xlink:show",
2626
+ xlinkActuate: "xlink:actuate",
2627
+ xlinkType: "xlink:type",
2628
+ xlinkRole: "xlink:role",
2629
+ xlinkTitle: "xlink:title",
2630
+ xlinkArcrole: "xlink:arcrole",
2631
+ xmlBase: "xml:base",
2632
+ xmlLang: "xml:lang",
2633
+ xmlSpace: "xml:space",
2634
+ xmlns: "xmlns",
2635
+ xmlnsXlink: "xmlns:xlink",
2636
+ // SVG filter primitive attributes
2637
+ stdDeviation: "stdDeviation",
2638
+ baseFrequency: "baseFrequency",
2639
+ numOctaves: "numOctaves",
2640
+ kernelMatrix: "kernelMatrix",
2641
+ surfaceScale: "surfaceScale",
2642
+ specularConstant: "specularConstant",
2643
+ specularExponent: "specularExponent",
2644
+ diffuseConstant: "diffuseConstant",
2645
+ pointsAtX: "pointsAtX",
2646
+ pointsAtY: "pointsAtY",
2647
+ pointsAtZ: "pointsAtZ",
2648
+ limitingConeAngle: "limitingConeAngle",
2649
+ tableValues: "tableValues"
2650
+ // viewBox, preserveAspectRatio stay as-is (already correct casing)
2651
+ };
2453
2652
  function renderAttr(key, value) {
2653
+ const name = ATTR_ALIASES[key] ?? key;
2454
2654
  if (value == null || value === false) return "";
2455
- if (value === true) return ` ${key}`;
2456
- const name = key === "className" ? "class" : key === "htmlFor" ? "for" : key;
2655
+ if (value === true) return ` ${name}`;
2457
2656
  let strValue;
2458
2657
  if (value instanceof SafeHtml) {
2459
2658
  strValue = value.__html;
@@ -2501,8 +2700,19 @@ pageRoutes.get("/", (c) => {
2501
2700
  /* @__PURE__ */ jsx("div", { className: "header-controls", children: [
2502
2701
  /* @__PURE__ */ jsx("div", { className: "search-box", children: /* @__PURE__ */ jsx("input", { type: "text", id: "search-input", placeholder: "Search tickets..." }) }),
2503
2702
  /* @__PURE__ */ jsx("div", { className: "layout-toggle", id: "layout-toggle", children: [
2504
- /* @__PURE__ */ jsx("button", { className: "layout-btn active", "data-layout": "list", title: "List view", children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>') }),
2505
- /* @__PURE__ */ jsx("button", { className: "layout-btn", "data-layout": "columns", title: "Column view", children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="M15 3v18"/></svg>') })
2703
+ /* @__PURE__ */ jsx("button", { className: "layout-btn active", "data-layout": "list", title: "List view", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2704
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "6", x2: "21", y2: "6" }),
2705
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "12", x2: "21", y2: "12" }),
2706
+ /* @__PURE__ */ jsx("line", { x1: "8", y1: "18", x2: "21", y2: "18" }),
2707
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "6", x2: "3.01", y2: "6" }),
2708
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "12", x2: "3.01", y2: "12" }),
2709
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "18", x2: "3.01", y2: "18" })
2710
+ ] }) }),
2711
+ /* @__PURE__ */ jsx("button", { className: "layout-btn", "data-layout": "columns", title: "Column view", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2712
+ /* @__PURE__ */ jsx("rect", { width: "18", height: "18", x: "3", y: "3", rx: "2" }),
2713
+ /* @__PURE__ */ jsx("path", { d: "M9 3v18" }),
2714
+ /* @__PURE__ */ jsx("path", { d: "M15 3v18" })
2715
+ ] }) })
2506
2716
  ] }),
2507
2717
  /* @__PURE__ */ jsx("div", { className: "sort-controls", children: /* @__PURE__ */ jsx("select", { id: "sort-select", children: [
2508
2718
  /* @__PURE__ */ jsx("option", { value: "created:desc", children: "Newest First" }),
@@ -2512,11 +2722,20 @@ pageRoutes.get("/", (c) => {
2512
2722
  /* @__PURE__ */ jsx("option", { value: "status:asc", children: "Status" })
2513
2723
  ] }) }),
2514
2724
  /* @__PURE__ */ jsx("div", { className: "layout-toggle", id: "detail-position-toggle", children: [
2515
- /* @__PURE__ */ jsx("button", { className: "layout-btn active", "data-position": "side", title: "Detail panel on side", children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="15" y1="3" x2="15" y2="21"/></svg>') }),
2516
- /* @__PURE__ */ jsx("button", { className: "layout-btn", "data-position": "bottom", title: "Detail panel on bottom", children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="15" x2="21" y2="15"/></svg>') })
2725
+ /* @__PURE__ */ jsx("button", { className: "layout-btn active", "data-position": "side", title: "Detail panel on side", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2726
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
2727
+ /* @__PURE__ */ jsx("line", { x1: "15", y1: "3", x2: "15", y2: "21" })
2728
+ ] }) }),
2729
+ /* @__PURE__ */ jsx("button", { className: "layout-btn", "data-position": "bottom", title: "Detail panel on bottom", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", children: [
2730
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
2731
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "15", x2: "21", y2: "15" })
2732
+ ] }) })
2517
2733
  ] }),
2518
- /* @__PURE__ */ jsx("button", { className: "glassbox-btn", id: "glassbox-btn", title: "Open Glassbox", style: "display:none", children: raw('<img id="glassbox-icon" alt="Glassbox" />') }),
2519
- /* @__PURE__ */ jsx("button", { className: "settings-btn", id: "settings-btn", title: "Settings", children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>') })
2734
+ /* @__PURE__ */ jsx("button", { className: "glassbox-btn", id: "glassbox-btn", title: "Open Glassbox", style: "display:none", children: /* @__PURE__ */ jsx("img", { id: "glassbox-icon", alt: "Glassbox" }) }),
2735
+ /* @__PURE__ */ jsx("button", { className: "settings-btn", id: "settings-btn", title: "Settings", children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2736
+ /* @__PURE__ */ jsx("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }),
2737
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3" })
2738
+ ] }) })
2520
2739
  ] })
2521
2740
  ] }),
2522
2741
  /* @__PURE__ */ jsx("div", { id: "backup-preview-banner", className: "backup-preview-banner", style: "display:none", children: [
@@ -2540,7 +2759,10 @@ pageRoutes.get("/", (c) => {
2540
2759
  /* @__PURE__ */ jsx("div", { className: "app-body", children: [
2541
2760
  /* @__PURE__ */ jsx("nav", { className: "sidebar", children: [
2542
2761
  /* @__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: [
2543
- /* @__PURE__ */ jsx("span", { className: "copy-prompt-icon", id: "copy-prompt-icon", children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>') }),
2762
+ /* @__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: [
2763
+ /* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2" }),
2764
+ /* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
2765
+ ] }) }),
2544
2766
  /* @__PURE__ */ jsx("span", { id: "copy-prompt-label", children: "Copy AI prompt" })
2545
2767
  ] }) }),
2546
2768
  /* @__PURE__ */ jsx("div", { className: "sidebar-section", children: [
@@ -2597,82 +2819,49 @@ pageRoutes.get("/", (c) => {
2597
2819
  /* @__PURE__ */ jsx("main", { className: "main-content", children: [
2598
2820
  /* @__PURE__ */ jsx("div", { className: "batch-toolbar", id: "batch-toolbar", children: [
2599
2821
  /* @__PURE__ */ jsx("input", { type: "checkbox", id: "batch-select-all", className: "batch-select-all", title: "Select all / none" }),
2600
- /* @__PURE__ */ jsx("select", { id: "batch-category", title: "Set category", disabled: true, children: [
2601
- /* @__PURE__ */ jsx("option", { value: "", children: "Category..." }),
2602
- /* @__PURE__ */ jsx("option", { value: "issue", children: "Issue" }),
2603
- /* @__PURE__ */ jsx("option", { value: "bug", children: "Bug" }),
2604
- /* @__PURE__ */ jsx("option", { value: "feature", children: "Feature" }),
2605
- /* @__PURE__ */ jsx("option", { value: "requirement_change", children: "Req Change" }),
2606
- /* @__PURE__ */ jsx("option", { value: "task", children: "Task" }),
2607
- /* @__PURE__ */ jsx("option", { value: "investigation", children: "Investigation" })
2608
- ] }),
2609
- /* @__PURE__ */ jsx("select", { id: "batch-priority", title: "Set priority", disabled: true, children: [
2610
- /* @__PURE__ */ jsx("option", { value: "", children: "Priority..." }),
2611
- /* @__PURE__ */ jsx("option", { value: "highest", children: "Highest" }),
2612
- /* @__PURE__ */ jsx("option", { value: "high", children: "High" }),
2613
- /* @__PURE__ */ jsx("option", { value: "default", children: "Default" }),
2614
- /* @__PURE__ */ jsx("option", { value: "low", children: "Low" }),
2615
- /* @__PURE__ */ jsx("option", { value: "lowest", children: "Lowest" })
2616
- ] }),
2617
- /* @__PURE__ */ jsx("select", { id: "batch-status", title: "Set status", disabled: true, children: [
2618
- /* @__PURE__ */ jsx("option", { value: "", children: "Status..." }),
2619
- /* @__PURE__ */ jsx("option", { value: "not_started", children: "Not Started" }),
2620
- /* @__PURE__ */ jsx("option", { value: "started", children: "Started" }),
2621
- /* @__PURE__ */ jsx("option", { value: "completed", children: "Completed" }),
2622
- /* @__PURE__ */ jsx("option", { value: "verified", children: "Verified" }),
2623
- /* @__PURE__ */ jsx("option", { value: "backlog", children: "Backlog" }),
2624
- /* @__PURE__ */ jsx("option", { value: "archive", children: "Archive" })
2625
- ] }),
2626
- /* @__PURE__ */ jsx("button", { id: "batch-upnext", className: "batch-star-btn", title: "Toggle Up Next", disabled: true, children: raw('<span class="batch-star-icon">&#9734;</span>') }),
2627
- /* @__PURE__ */ jsx("button", { id: "batch-delete", className: "btn btn-sm btn-danger batch-delete-btn", title: "Delete selected", disabled: true, children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>') }),
2628
- /* @__PURE__ */ jsx("button", { id: "batch-more", className: "btn btn-sm batch-more-btn", title: "More actions", disabled: true, children: raw('<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>') }),
2822
+ /* @__PURE__ */ jsx("button", { id: "batch-category", className: "btn btn-sm batch-dropdown-btn", title: "Set category", disabled: true, children: "Category" }),
2823
+ /* @__PURE__ */ jsx("button", { id: "batch-priority", className: "btn btn-sm batch-dropdown-btn", title: "Set priority", disabled: true, children: "Priority" }),
2824
+ /* @__PURE__ */ jsx("button", { id: "batch-status", className: "btn btn-sm batch-dropdown-btn", title: "Set status", disabled: true, children: "Status" }),
2825
+ /* @__PURE__ */ jsx("button", { id: "batch-upnext", className: "batch-star-btn", title: "Toggle Up Next", disabled: true, children: /* @__PURE__ */ jsx("span", { className: "batch-star-icon", children: "\u2606" }) }),
2826
+ /* @__PURE__ */ jsx("button", { id: "batch-delete", className: "btn btn-sm btn-danger batch-delete-btn", title: "Delete selected", disabled: true, children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2827
+ /* @__PURE__ */ jsx("path", { d: "M3 6h18" }),
2828
+ /* @__PURE__ */ jsx("path", { d: "M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" }),
2829
+ /* @__PURE__ */ jsx("path", { d: "M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" }),
2830
+ /* @__PURE__ */ jsx("line", { x1: "10", y1: "11", x2: "10", y2: "17" }),
2831
+ /* @__PURE__ */ jsx("line", { x1: "14", y1: "11", x2: "14", y2: "17" })
2832
+ ] }) }),
2833
+ /* @__PURE__ */ jsx("button", { id: "batch-more", className: "btn btn-sm batch-more-btn", title: "More actions", disabled: true, children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2834
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "1" }),
2835
+ /* @__PURE__ */ jsx("circle", { cx: "19", cy: "12", r: "1" }),
2836
+ /* @__PURE__ */ jsx("circle", { cx: "5", cy: "12", r: "1" })
2837
+ ] }) }),
2629
2838
  /* @__PURE__ */ jsx("span", { className: "batch-count", id: "batch-count" })
2630
2839
  ] }),
2631
- /* @__PURE__ */ jsx("div", { className: "ticket-list", id: "ticket-list", children: raw('<div class="ticket-list-loading">Loading...</div>') })
2840
+ /* @__PURE__ */ jsx("div", { className: "ticket-list", id: "ticket-list", children: /* @__PURE__ */ jsx("div", { className: "ticket-list-loading", children: "Loading..." }) })
2632
2841
  ] }),
2633
2842
  /* @__PURE__ */ jsx("div", { className: "detail-resize-handle", id: "detail-resize-handle" }),
2634
2843
  /* @__PURE__ */ jsx("aside", { className: "detail-panel detail-disabled", id: "detail-panel", children: [
2635
2844
  /* @__PURE__ */ jsx("div", { className: "detail-placeholder", id: "detail-placeholder", children: /* @__PURE__ */ jsx("span", { className: "detail-placeholder-text", id: "detail-placeholder-text", children: "Nothing selected" }) }),
2636
2845
  /* @__PURE__ */ jsx("div", { className: "detail-header", id: "detail-header", style: "display:none", children: [
2637
2846
  /* @__PURE__ */ jsx("span", { className: "detail-ticket-number", id: "detail-ticket-number" }),
2638
- /* @__PURE__ */ jsx("button", { className: "detail-close", id: "detail-close", title: "Close", children: raw("&times;") })
2847
+ /* @__PURE__ */ jsx("button", { className: "detail-close", id: "detail-close", title: "Close", children: "\xD7" })
2639
2848
  ] }),
2640
2849
  /* @__PURE__ */ jsx("div", { className: "detail-body", id: "detail-body", style: "display:none", children: [
2641
2850
  /* @__PURE__ */ jsx("div", { className: "detail-fields-row", children: [
2642
2851
  /* @__PURE__ */ jsx("div", { className: "detail-field", children: [
2643
2852
  /* @__PURE__ */ jsx("label", { children: "Category" }),
2644
- /* @__PURE__ */ jsx("select", { id: "detail-category", children: [
2645
- /* @__PURE__ */ jsx("option", { value: "issue", children: "Issue" }),
2646
- /* @__PURE__ */ jsx("option", { value: "bug", children: "Bug" }),
2647
- /* @__PURE__ */ jsx("option", { value: "feature", children: "Feature" }),
2648
- /* @__PURE__ */ jsx("option", { value: "requirement_change", children: "Req Change" }),
2649
- /* @__PURE__ */ jsx("option", { value: "task", children: "Task" }),
2650
- /* @__PURE__ */ jsx("option", { value: "investigation", children: "Investigation" })
2651
- ] })
2853
+ /* @__PURE__ */ jsx("button", { id: "detail-category", className: "detail-dropdown-btn", "data-value": "issue", children: "Issue" })
2652
2854
  ] }),
2653
2855
  /* @__PURE__ */ jsx("div", { className: "detail-field", children: [
2654
2856
  /* @__PURE__ */ jsx("label", { children: "Priority" }),
2655
- /* @__PURE__ */ jsx("select", { id: "detail-priority", children: [
2656
- /* @__PURE__ */ jsx("option", { value: "highest", children: "Highest" }),
2657
- /* @__PURE__ */ jsx("option", { value: "high", children: "High" }),
2658
- /* @__PURE__ */ jsx("option", { value: "default", children: "Default" }),
2659
- /* @__PURE__ */ jsx("option", { value: "low", children: "Low" }),
2660
- /* @__PURE__ */ jsx("option", { value: "lowest", children: "Lowest" })
2661
- ] })
2857
+ /* @__PURE__ */ jsx("button", { id: "detail-priority", className: "detail-dropdown-btn", "data-value": "default", children: "Default" })
2662
2858
  ] }),
2663
2859
  /* @__PURE__ */ jsx("div", { className: "detail-field", children: [
2664
2860
  /* @__PURE__ */ jsx("label", { children: "Status" }),
2665
- /* @__PURE__ */ jsx("select", { id: "detail-status", children: [
2666
- /* @__PURE__ */ jsx("option", { value: "not_started", children: "Not Started" }),
2667
- /* @__PURE__ */ jsx("option", { value: "started", children: "Started" }),
2668
- /* @__PURE__ */ jsx("option", { value: "completed", children: "Completed" }),
2669
- /* @__PURE__ */ jsx("option", { value: "verified", children: "Verified" }),
2670
- /* @__PURE__ */ jsx("option", { value: "backlog", children: "Backlog" }),
2671
- /* @__PURE__ */ jsx("option", { value: "archive", children: "Archive" })
2672
- ] })
2861
+ /* @__PURE__ */ jsx("button", { id: "detail-status", className: "detail-dropdown-btn", "data-value": "not_started", children: "Not Started" })
2673
2862
  ] }),
2674
2863
  /* @__PURE__ */ jsx("div", { className: "detail-field", children: /* @__PURE__ */ jsx("label", { className: "detail-upnext-label", children: [
2675
- /* @__PURE__ */ jsx("input", { type: "checkbox", id: "detail-upnext" }),
2864
+ /* @__PURE__ */ jsx("button", { className: "ticket-star detail-upnext-star", id: "detail-upnext", type: "button", children: "\u2606" }),
2676
2865
  "Up Next"
2677
2866
  ] }) })
2678
2867
  ] }),
@@ -2709,7 +2898,7 @@ pageRoutes.get("/", (c) => {
2709
2898
  ] }),
2710
2899
  /* @__PURE__ */ jsx("span", { children: [
2711
2900
  /* @__PURE__ */ jsx("kbd", { children: [
2712
- raw("&#8984;"),
2901
+ "\u2318",
2713
2902
  "I/B/F/R/K/G"
2714
2903
  ] }),
2715
2904
  " category"
@@ -2720,7 +2909,7 @@ pageRoutes.get("/", (c) => {
2720
2909
  ] }),
2721
2910
  /* @__PURE__ */ jsx("span", { children: [
2722
2911
  /* @__PURE__ */ jsx("kbd", { children: [
2723
- raw("&#8984;"),
2912
+ "\u2318",
2724
2913
  "D"
2725
2914
  ] }),
2726
2915
  " up next"
@@ -2736,23 +2925,73 @@ pageRoutes.get("/", (c) => {
2736
2925
  /* @__PURE__ */ jsx("div", { className: "settings-overlay", id: "settings-overlay", style: "display:none", children: /* @__PURE__ */ jsx("div", { className: "settings-dialog", children: [
2737
2926
  /* @__PURE__ */ jsx("div", { className: "settings-header", children: [
2738
2927
  /* @__PURE__ */ jsx("h2", { children: "Settings" }),
2739
- /* @__PURE__ */ jsx("button", { className: "detail-close", id: "settings-close", children: raw("&times;") })
2928
+ /* @__PURE__ */ jsx("button", { className: "detail-close", id: "settings-close", children: "\xD7" })
2740
2929
  ] }),
2741
- /* @__PURE__ */ jsx("div", { className: "settings-body", children: [
2742
- /* @__PURE__ */ jsx("div", { className: "settings-field", children: [
2743
- /* @__PURE__ */ jsx("label", { children: "App name" }),
2744
- /* @__PURE__ */ jsx("input", { type: "text", id: "settings-app-name", placeholder: "Hot Sheet" }),
2745
- /* @__PURE__ */ jsx("span", { className: "settings-hint", id: "settings-app-name-hint", children: "Custom name shown in the title bar. Leave empty for default." })
2930
+ /* @__PURE__ */ jsx("div", { className: "settings-tabs", id: "settings-tabs", children: [
2931
+ /* @__PURE__ */ jsx("button", { className: "settings-tab active", "data-tab": "general", children: [
2932
+ /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2933
+ /* @__PURE__ */ jsx("line", { x1: "21", x2: "14", y1: "4", y2: "4" }),
2934
+ /* @__PURE__ */ jsx("line", { x1: "10", x2: "3", y1: "4", y2: "4" }),
2935
+ /* @__PURE__ */ jsx("line", { x1: "21", x2: "12", y1: "12", y2: "12" }),
2936
+ /* @__PURE__ */ jsx("line", { x1: "8", x2: "3", y1: "12", y2: "12" }),
2937
+ /* @__PURE__ */ jsx("line", { x1: "21", x2: "16", y1: "20", y2: "20" }),
2938
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "3", y1: "20", y2: "20" }),
2939
+ /* @__PURE__ */ jsx("line", { x1: "14", x2: "14", y1: "2", y2: "6" }),
2940
+ /* @__PURE__ */ jsx("line", { x1: "8", x2: "8", y1: "10", y2: "14" }),
2941
+ /* @__PURE__ */ jsx("line", { x1: "16", x2: "16", y1: "18", y2: "22" })
2942
+ ] }),
2943
+ /* @__PURE__ */ jsx("span", { children: "General" })
2944
+ ] }),
2945
+ /* @__PURE__ */ jsx("button", { className: "settings-tab", "data-tab": "categories", children: [
2946
+ /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2947
+ /* @__PURE__ */ jsx("path", { d: "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z" }),
2948
+ /* @__PURE__ */ jsx("circle", { cx: "7.5", cy: "7.5", r: ".5", fill: "currentColor" })
2949
+ ] }),
2950
+ /* @__PURE__ */ jsx("span", { children: "Categories" })
2746
2951
  ] }),
2747
- /* @__PURE__ */ jsx("div", { className: "settings-field", children: [
2748
- /* @__PURE__ */ jsx("label", { children: "Auto-clear trash after (days)" }),
2749
- /* @__PURE__ */ jsx("input", { type: "number", id: "settings-trash-days", min: "1", value: "3" })
2952
+ /* @__PURE__ */ jsx("button", { className: "settings-tab", "data-tab": "backups", children: [
2953
+ /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2954
+ /* @__PURE__ */ jsx("line", { x1: "22", x2: "2", y1: "12", y2: "12" }),
2955
+ /* @__PURE__ */ jsx("path", { d: "M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z" }),
2956
+ /* @__PURE__ */ jsx("line", { x1: "6", x2: "6.01", y1: "16", y2: "16" }),
2957
+ /* @__PURE__ */ jsx("line", { x1: "10", x2: "10.01", y1: "16", y2: "16" })
2958
+ ] }),
2959
+ /* @__PURE__ */ jsx("span", { children: "Backups" })
2960
+ ] }),
2961
+ /* @__PURE__ */ jsx("button", { className: "settings-tab", "data-tab": "updates", id: "settings-tab-updates", style: "display:none", children: [
2962
+ /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
2963
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
2964
+ /* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
2965
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
2966
+ ] }),
2967
+ /* @__PURE__ */ jsx("span", { children: "Updates" })
2968
+ ] })
2969
+ ] }),
2970
+ /* @__PURE__ */ jsx("div", { className: "settings-body", children: [
2971
+ /* @__PURE__ */ jsx("div", { className: "settings-tab-panel active", "data-panel": "general", children: [
2972
+ /* @__PURE__ */ jsx("div", { className: "settings-field", children: [
2973
+ /* @__PURE__ */ jsx("label", { children: "App name" }),
2974
+ /* @__PURE__ */ jsx("input", { type: "text", id: "settings-app-name", placeholder: "Hot Sheet" }),
2975
+ /* @__PURE__ */ jsx("span", { className: "settings-hint", id: "settings-app-name-hint", children: "Custom name shown in the title bar. Leave empty for default." })
2976
+ ] }),
2977
+ /* @__PURE__ */ jsx("div", { className: "settings-field", children: [
2978
+ /* @__PURE__ */ jsx("label", { children: "Auto-clear trash after (days)" }),
2979
+ /* @__PURE__ */ jsx("input", { type: "number", id: "settings-trash-days", min: "1", value: "3" })
2980
+ ] }),
2981
+ /* @__PURE__ */ jsx("div", { className: "settings-field", children: [
2982
+ /* @__PURE__ */ jsx("label", { children: "Auto-clear verified after (days)" }),
2983
+ /* @__PURE__ */ jsx("input", { type: "number", id: "settings-verified-days", min: "1", value: "30" })
2984
+ ] })
2750
2985
  ] }),
2751
- /* @__PURE__ */ jsx("div", { className: "settings-field", children: [
2752
- /* @__PURE__ */ jsx("label", { children: "Auto-clear verified after (days)" }),
2753
- /* @__PURE__ */ jsx("input", { type: "number", id: "settings-verified-days", min: "1", value: "30" })
2986
+ /* @__PURE__ */ jsx("div", { className: "settings-tab-panel", "data-panel": "categories", children: [
2987
+ /* @__PURE__ */ jsx("div", { className: "settings-section-header", children: [
2988
+ /* @__PURE__ */ jsx("h3", { children: "Categories" }),
2989
+ /* @__PURE__ */ jsx("div", { className: "category-preset-controls", children: /* @__PURE__ */ jsx("select", { id: "category-preset-select", className: "btn btn-sm", children: /* @__PURE__ */ jsx("option", { value: "", children: "Load preset..." }) }) })
2990
+ ] }),
2991
+ /* @__PURE__ */ jsx("div", { id: "category-list", className: "category-list" }),
2992
+ /* @__PURE__ */ jsx("button", { id: "category-add-btn", className: "btn btn-sm", style: "margin-top:8px", children: "Add Category" })
2754
2993
  ] }),
2755
- /* @__PURE__ */ jsx("div", { className: "settings-section", children: [
2994
+ /* @__PURE__ */ jsx("div", { className: "settings-tab-panel", "data-panel": "backups", children: [
2756
2995
  /* @__PURE__ */ jsx("div", { className: "settings-section-header", children: [
2757
2996
  /* @__PURE__ */ jsx("h3", { children: "Database Backups" }),
2758
2997
  /* @__PURE__ */ jsx("button", { className: "btn btn-sm", id: "backup-now-btn", children: "Backup Now" })
@@ -2764,7 +3003,7 @@ pageRoutes.get("/", (c) => {
2764
3003
  ] }),
2765
3004
  /* @__PURE__ */ jsx("div", { id: "backup-list", className: "backup-list", children: "Loading backups..." })
2766
3005
  ] }),
2767
- /* @__PURE__ */ jsx("div", { className: "settings-section", id: "settings-updates-section", style: "display:none", children: [
3006
+ /* @__PURE__ */ jsx("div", { className: "settings-tab-panel", "data-panel": "updates", id: "settings-updates-section", style: "display:none", children: [
2768
3007
  /* @__PURE__ */ jsx("div", { className: "settings-section-header", children: [
2769
3008
  /* @__PURE__ */ jsx("h3", { children: "Software Updates" }),
2770
3009
  /* @__PURE__ */ jsx("button", { className: "btn btn-sm", id: "check-updates-btn", children: "Check for Updates" })
@@ -3076,6 +3315,7 @@ async function main() {
3076
3315
  initMarkdownSync(dataDir2, actualPort);
3077
3316
  scheduleAllSync();
3078
3317
  initSkills(actualPort, dataDir2);
3318
+ setSkillCategories(await getCategories());
3079
3319
  const updatedPlatforms = ensureSkills();
3080
3320
  if (updatedPlatforms.length > 0) {
3081
3321
  console.log(`