@starlove/orbit 1.1.11 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/.dockerignore +0 -0
  2. package/Dockerfile +0 -0
  3. package/LICENSE +0 -0
  4. package/README.md +0 -0
  5. package/SKILL-ORBIT.md +0 -0
  6. package/build.mjs +11 -5
  7. package/dist/full/apple-touch-icon.png +0 -0
  8. package/dist/full/cursors/live_arrow.cur +0 -0
  9. package/dist/full/cursors/live_link.cur +0 -0
  10. package/dist/full/cursors/live_move.cur +0 -0
  11. package/dist/full/cursors/live_select.cur +0 -0
  12. package/dist/full/cursors/live_unavail.cur +0 -0
  13. package/dist/full/favicon-32x32.png +0 -0
  14. package/dist/full/index.html +11 -7
  15. package/dist/full/js/{app.55b38e12.js → app.ccc442f7.js} +85 -77
  16. package/dist/full/js/app.ccc442f7.js.map +7 -0
  17. package/dist/full/orbits-favicon.png +0 -0
  18. package/dist/full/orbits-logo-bg.png +0 -0
  19. package/dist/full/orbits-logo.png +0 -0
  20. package/dist/full/styles.baccc8e3.css +1 -0
  21. package/dist/full/version.json +2 -2
  22. package/docs/AGENT_PROTOCOL.md +0 -0
  23. package/docs/MCP_SETUP.md +0 -0
  24. package/docs/ORBIT_DISPATCH.md +0 -0
  25. package/docs/SS-Orbit-Dark.png +0 -0
  26. package/docs/SS-Orbit-Light.png +0 -0
  27. package/docs/mcp-client.example.json +0 -0
  28. package/docs/setup/orbit-init-wizard.md +0 -0
  29. package/package.json +19 -18
  30. package/public/apple-touch-icon.png +0 -0
  31. package/public/cursors/live_arrow.cur +0 -0
  32. package/public/cursors/live_link.cur +0 -0
  33. package/public/cursors/live_move.cur +0 -0
  34. package/public/cursors/live_select.cur +0 -0
  35. package/public/cursors/live_unavail.cur +0 -0
  36. package/public/favicon-32x32.png +0 -0
  37. package/public/index.html +9 -5
  38. package/public/js/actor-labels.js +0 -0
  39. package/public/js/api.js +0 -0
  40. package/public/js/app.js +0 -0
  41. package/public/js/board-menu.js +16 -2
  42. package/public/js/card-actions.js +0 -0
  43. package/public/js/config.js +0 -0
  44. package/public/js/create-card.js +1 -1
  45. package/public/js/dom.js +0 -0
  46. package/public/js/drawer.js +1 -1
  47. package/public/js/format.js +2 -3
  48. package/public/js/kanban.js +10 -9
  49. package/public/js/local-backend.js +0 -0
  50. package/public/js/main.js +0 -0
  51. package/public/js/modal.js +0 -0
  52. package/public/js/motion-preference.js +0 -0
  53. package/public/js/router.js +0 -0
  54. package/public/js/search.js +1 -1
  55. package/public/js/settings-tabs.js +0 -0
  56. package/public/js/settings.js +39 -27
  57. package/public/js/sse.js +0 -0
  58. package/public/js/starscape.js +0 -0
  59. package/public/js/state.js +0 -0
  60. package/public/js/theme-preference.js +0 -0
  61. package/public/js/ticket-detail.js +4 -4
  62. package/public/js/toast.js +0 -0
  63. package/public/js/transport.js +0 -0
  64. package/public/js/unread.js +0 -0
  65. package/public/js/url-routes.js +0 -0
  66. package/public/orbits-favicon.png +0 -0
  67. package/public/orbits-logo-bg.png +0 -0
  68. package/public/orbits-logo.png +0 -0
  69. package/public/styles.css +498 -445
  70. package/src/core/agent.js +0 -0
  71. package/src/core/agents-md.js +0 -0
  72. package/src/core/attachments.js +0 -0
  73. package/src/core/auth.js +0 -0
  74. package/src/core/backups.js +0 -0
  75. package/src/core/board.js +0 -0
  76. package/src/core/boards.js +0 -0
  77. package/src/core/bootstrap.js +0 -0
  78. package/src/core/css-bundle.js +29 -0
  79. package/src/core/db.js +0 -0
  80. package/src/core/delete-board.js +0 -0
  81. package/src/core/dispatch.js +0 -0
  82. package/src/core/events.js +0 -0
  83. package/src/core/http.js +10 -3
  84. package/src/core/migrate.js +0 -0
  85. package/src/core/paths.js +0 -0
  86. package/src/core/provision-repo-board.js +0 -0
  87. package/src/core/queries.js +0 -0
  88. package/src/core/registry.js +0 -0
  89. package/src/core/relations.js +0 -0
  90. package/src/core/review-verdicts.js +0 -0
  91. package/src/core/router.js +0 -0
  92. package/src/core/search.js +0 -0
  93. package/src/core/seed.js +0 -0
  94. package/src/core/snapshots.js +0 -0
  95. package/src/core/states.js +0 -0
  96. package/src/core/system-picker.js +0 -0
  97. package/src/core/tickets.js +0 -0
  98. package/src/core/util.js +0 -0
  99. package/src/mcp/orbit-client.js +0 -0
  100. package/src/mcp-server.js +0 -0
  101. package/src/server.js +0 -0
  102. package/dist/full/js/app.55b38e12.js.map +0 -7
  103. package/dist/full/styles.273fc37a.css +0 -6002
@@ -23,11 +23,25 @@ export function closeMenuFlyouts() {
23
23
  closeBoardFlyout();
24
24
  }
25
25
 
26
+ const boardChipIcon = `
27
+ <svg
28
+ class="topbar-icon"
29
+ width="16"
30
+ height="16"
31
+ viewBox="0 0 24 24"
32
+ fill="none"
33
+ aria-hidden="true"
34
+ >
35
+ <rect x="4" y="4" width="6" height="16" rx="0.75" stroke="currentColor" stroke-width="2" />
36
+ <rect x="14" y="4" width="6" height="16" rx="0.75" stroke="currentColor" stroke-width="2" />
37
+ </svg>`;
38
+
26
39
  export function updateTopbarChips() {
27
40
  if (!state.data) return;
28
41
  const board = (state.data.boards || []).find((b) => b.id === state.boardId);
29
42
  if (boardMenuBtn) {
30
- boardMenuBtn.textContent = board?.name || "Board";
43
+ const label = board?.name || "Board";
44
+ boardMenuBtn.innerHTML = `${boardChipIcon}<span class="topbar-chip-label">${escapeHtml(label)}</span>`;
31
45
  boardMenuBtn.title = board ? `${board.name} — slug: ${board.slug}` : "Choose board";
32
46
  }
33
47
  }
@@ -37,7 +51,7 @@ function renderBoardFlyoutInner() {
37
51
  const rows = boards
38
52
  .map(
39
53
  (b) => `
40
- <button type="button" class="menu-flyout-item ${b.id === state.boardId ? "is-current" : ""}" data-pick-board="${escapeHtml(b.id)}" role="menuitem">
54
+ <button type="button" class="menu-flyout-item ${b.id === state.boardId ? "is-current" : ""}" data-variant="menu-item" data-pick-board="${escapeHtml(b.id)}" role="menuitem">
41
55
  <span class="menu-flyout-item-title">${escapeHtml(b.name)}</span>
42
56
  <span class="menu-flyout-item-meta">${escapeHtml(b.slug)}</span>
43
57
  </button>`
File without changes
File without changes
@@ -86,7 +86,7 @@ function renderCreateForm(preselectedLaneId) {
86
86
  <label>
87
87
  Type
88
88
  <select id="createTicketType" name="type" class="select-chevron-field" aria-label="Ticket type">
89
- <option value="epic">Epic / Story</option>
89
+ <option value="epic">Epic</option>
90
90
  <option value="feature">Feature</option>
91
91
  <option value="task" selected>Task</option>
92
92
  <option value="bug">Bug</option>
package/public/js/dom.js CHANGED
File without changes
@@ -75,7 +75,7 @@ export function renderDrawerShell({ eyebrow, title, titleAttrs, subtitleHtml, ta
75
75
  // last render (e.g. the inline title editor's click handler) die with the
76
76
  // old nodes, instead of stacking on persistent shell elements.
77
77
  const extraTitleClass = titleAttrs?.class ? ` ${titleAttrs.class}` : "";
78
- let titleAttrHtml = ` class="drawer-title-text${extraTitleClass}"`;
78
+ let titleAttrHtml = ` class="drawer-title drawer-title-text${extraTitleClass}"`;
79
79
  for (const [key, value] of Object.entries(titleAttrs || {})) {
80
80
  if (key === "class") continue;
81
81
  titleAttrHtml += ` ${key}="${escapeHtml(String(value))}"`;
@@ -389,11 +389,10 @@ export function typeLabel(type) {
389
389
  return labels[canonicalTicketType(type)] || "Task";
390
390
  }
391
391
 
392
- // Long, hierarchy-aware label for dropdowns where we have the room. Epic/Story
393
- // pairs older vocabulary so existing terminology still resonates.
392
+ // Long, hierarchy-aware label for dropdowns where we have the room.
394
393
  export function typeLabelLong(type) {
395
394
  const labels = {
396
- epic: "Epic / Story",
395
+ epic: "Epic",
397
396
  feature: "Feature",
398
397
  task: "Task",
399
398
  bug: "Bug"
@@ -269,17 +269,18 @@ function renderColumn(column, tickets) {
269
269
  // are the queue the claim-next MCP tool pulls from. Mark it so humans can
270
270
  // instantly see where agent-facing staging lives.
271
271
  const isAiReady = column.name === "AI Ready";
272
- const columnClasses = ["column"];
272
+ const columnClasses = ["column", "lane"];
273
273
  if (isAiReady) columnClasses.push("column--ai-ready");
274
+ const accentAttr = isAiReady ? ' data-accent="ai-ready"' : "";
274
275
 
275
276
  return `
276
- <section class="${columnClasses.join(" ")}" data-state-id="${column.id}" data-state-name="${escapeHtml(column.name)}">
277
- <div class="column-head">
278
- <h2${isAiReady ? ' title="Agent staging — cards here are picked up by the claim-next tool when unblocked"' : ""}>
277
+ <section class="${columnClasses.join(" ")}" data-flat${accentAttr} data-state-id="${column.id}" data-state-name="${escapeHtml(column.name)}">
278
+ <div class="column-head lane-head">
279
+ <h2 class="lane-title"${isAiReady ? ' title="Agent staging — cards here are picked up by the claim-next tool when unblocked"' : ""}>
279
280
  ${isAiReady ? '<span class="agent-dot" aria-hidden="true"></span>' : ""}${escapeHtml(column.name)}
280
281
  </h2>
281
- <div class="column-head-actions">
282
- <button type="button" class="column-add-btn ghost" data-add-to-state="${escapeHtml(column.id)}" title="New card">+</button>
282
+ <div class="column-head-actions lane-actions">
283
+ <button type="button" class="column-add-btn" data-variant="plus" data-add-to-state="${escapeHtml(column.id)}" title="New card">+</button>
283
284
  <span class="count">${columnTickets.length}</span>
284
285
  </div>
285
286
  </div>
@@ -375,7 +376,7 @@ function renderEpicMiniHeader(epic) {
375
376
  export function renderPriorityPill(priority) {
376
377
  const key = priorityKeyFor(priority);
377
378
  const label = priorityLabel(priority);
378
- return `<div class="card-priority-id priority-pill priority-pill-${escapeHtml(key)}"><span class="card-priority-label">${escapeHtml(label)}</span></div>`;
379
+ return `<div class="card-priority-id priority-pill" data-variant="${escapeHtml(key)}"><span class="card-priority-label">${escapeHtml(label)}</span></div>`;
379
380
  }
380
381
 
381
382
  export function renderTypeIcon(type) {
@@ -434,13 +435,13 @@ function renderCard(ticket, { asEpicChild = false } = {}) {
434
435
  : `<span class="card-unread-dot has-count" role="status" aria-label="${unreadN} unread updates">${unreadN > 99 ? "99+" : unreadN}</span>`;
435
436
 
436
437
  return `
437
- <article class="card type-${escapeHtml(canonicalType)} priority-${escapeHtml(priorityKey)}${isExpanded ? " is-expanded" : ""}${asEpicChild ? " is-epic-child" : ""} ${ticket.id === state.selectedTicketId ? "selected" : ""}" draggable="true" data-ticket-id="${ticket.id}">
438
+ <article class="card type-${escapeHtml(canonicalType)} priority-${escapeHtml(priorityKey)}${isExpanded ? " is-expanded" : ""}${asEpicChild ? " is-epic-child" : ""} ${ticket.id === state.selectedTicketId ? "selected" : ""}" data-variant="${escapeHtml(canonicalType)}" draggable="true" data-ticket-id="${ticket.id}">
438
439
  ${unreadDot}
439
440
  <h3>${escapeHtml(ticket.title)}</h3>
440
441
 
441
442
  <div class="card-meta">
442
443
  ${hasExpandable
443
- ? `<button type="button" class="card-expand-trigger" data-card-toggle aria-expanded="${isExpanded ? "true" : "false"}">${triggerInner}</button>`
444
+ ? `<button type="button" class="card-expand-trigger" data-variant="card-accordion" data-card-toggle aria-expanded="${isExpanded ? "true" : "false"}">${triggerInner}</button>`
444
445
  : `<div class="card-expand-trigger card-expand-trigger--static">${triggerInner}</div>`}
445
446
  </div>
446
447
 
File without changes
package/public/js/main.js CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -32,7 +32,7 @@ export async function runSearch() {
32
32
  <strong>${escapeHtml(ticketLabel(ticket))}</strong>
33
33
  <span class="search-hit-title">${escapeHtml(ticket.title)}</span>
34
34
  </span>
35
- <span class="search-hit-state state-pill-${escapeHtml(stateClassFor(ticket))}">${escapeHtml(ticket.state_name || "State")}</span>
35
+ <span class="search-hit-state" data-variant="${escapeHtml(stateClassFor(ticket))}">${escapeHtml(ticket.state_name || "State")}</span>
36
36
  </div>
37
37
  `
38
38
  )
File without changes
@@ -242,7 +242,7 @@ function renderMcpSetupSection(context) {
242
242
  <strong data-mcp-output-title>${escapeHtml(client.label)} setup</strong>
243
243
  <p class="description" data-mcp-helper>${escapeHtml(client.helper)}</p>
244
244
  </div>
245
- <button type="button" class="ghost" data-copy-mcp="config">Copy setup</button>
245
+ <button type="button" data-variant="secondary" data-copy-mcp="config">Copy setup</button>
246
246
  </div>
247
247
  <div class="mcp-terminal">
248
248
  <div class="mcp-terminal-chrome" aria-hidden="true"><span></span><span></span><span></span></div>
@@ -258,7 +258,7 @@ function renderMcpSetupSection(context) {
258
258
  <strong>Manual run command</strong>
259
259
  <p class="description">Use this only when an app asks you to start the MCP server yourself.</p>
260
260
  </div>
261
- <button type="button" class="ghost" data-copy-mcp="command">Copy command</button>
261
+ <button type="button" data-variant="secondary" data-copy-mcp="command">Copy command</button>
262
262
  </div>
263
263
  <div class="mcp-terminal">
264
264
  <div class="mcp-terminal-chrome" aria-hidden="true"><span></span><span></span><span></span></div>
@@ -403,11 +403,9 @@ function renderProjectRepositoryTab(context) {
403
403
  <p class="description">Image exports are embedded in the .orbit.json snapshot and may be much larger.</p>
404
404
  ` : ""}
405
405
  <div class="deployment-actions">
406
- <button type="button" class="ghost" id="exportProject">Export</button>
407
- <label class="import-button">
408
- <input type="file" id="importSnapshotAsNewFile" accept=".orbit.json,.json,application/json" />
409
- <span>Import as New Board</span>
410
- </label>
406
+ <button type="button" data-variant="secondary" id="exportProject">Export</button>
407
+ <input type="file" id="importSnapshotAsNewFile" class="import-file-input" accept=".orbit.json,.json,application/json" />
408
+ <button type="button" data-variant="secondary" id="importSnapshotAsNewButton">Import as New Board</button>
411
409
  </div>
412
410
  </div>
413
411
 
@@ -419,10 +417,8 @@ function renderProjectRepositoryTab(context) {
419
417
  <input id="replaceImportConfirmInput" autocomplete="off" spellcheck="false" placeholder="${escapeHtml(project.slug || "")}" />
420
418
  </label>
421
419
  <div class="deployment-actions">
422
- <label class="import-button danger-button">
423
- <input type="file" id="replaceCurrentBoardImportFile" accept=".orbit.json,.json,application/json" disabled />
424
- <span>Replace Current Board</span>
425
- </label>
420
+ <input type="file" id="replaceCurrentBoardImportFile" class="import-file-input" accept=".orbit.json,.json,application/json" disabled />
421
+ <button type="button" data-variant="secondary" id="replaceCurrentBoardImportButton" disabled>Replace Current Board</button>
426
422
  </div>
427
423
  </div>
428
424
 
@@ -440,7 +436,7 @@ function renderBoardRenameSection(project) {
440
436
  <span>Display name</span>
441
437
  <input name="name" value="${escapeHtml(project.name || "")}" maxlength="120" required />
442
438
  </label>
443
- <button type="submit">Rename Board</button>
439
+ <button type="submit" data-variant="secondary">Rename Board</button>
444
440
  </form>
445
441
  </div>
446
442
  `;
@@ -454,9 +450,9 @@ function renderDeleteBoardSection(project) {
454
450
  <h3>Delete Board</h3>
455
451
  <p class="description">Delete this board from Orbit and remove its repo-local Orbit files. Export a snapshot first if you may need this board again.</p>
456
452
  <div class="repository-delete-actions">
457
- <button type="button" class="ghost" id="deleteBoardExportFirst">Export first</button>
458
- <button type="button" class="ghost danger-button" id="deleteBoardSkipExport">Delete without exporting</button>
459
- <button type="button" class="ghost" id="deleteBoardCancel">Cancel</button>
453
+ <button type="button" data-variant="secondary" id="deleteBoardExportFirst">Export first</button>
454
+ <button type="button" data-variant="ghost" class="danger-button" id="deleteBoardSkipExport">Delete without exporting</button>
455
+ <button type="button" data-variant="ghost" id="deleteBoardCancel">Cancel</button>
460
456
  </div>
461
457
  </div>
462
458
  `;
@@ -476,8 +472,8 @@ function renderDeleteBoardSection(project) {
476
472
  <input id="deleteBoardConfirmInput" autocomplete="off" spellcheck="false" placeholder="${slug}" autofocus />
477
473
  </label>
478
474
  <div class="repository-delete-actions">
479
- <button type="button" class="danger-button" id="deleteBoardConfirm" disabled>Delete board permanently</button>
480
- <button type="button" class="ghost" id="deleteBoardCancel">Cancel</button>
475
+ <button type="button" data-variant="primary" class="danger-button" id="deleteBoardConfirm" disabled>Delete board permanently</button>
476
+ <button type="button" data-variant="ghost" id="deleteBoardCancel">Cancel</button>
481
477
  </div>
482
478
  </div>
483
479
  `;
@@ -487,7 +483,7 @@ function renderDeleteBoardSection(project) {
487
483
  <div class="section repository-danger-zone">
488
484
  <h3>Delete Board</h3>
489
485
  <p class="description">Remove this board from Orbit and delete its repo-local Orbit files. This cannot be undone without an exported snapshot.</p>
490
- <button type="button" class="ghost danger-button" id="deleteBoardStart">Delete Board</button>
486
+ <button type="button" data-variant="ghost" class="danger-button" id="deleteBoardStart">Delete Board</button>
491
487
  </div>
492
488
  `;
493
489
  }
@@ -536,7 +532,7 @@ function renderProjectLanesTab() {
536
532
  ${renderLaneManager()}
537
533
  <form id="laneCreateForm" class="lane-create-form">
538
534
  <input name="name" placeholder="New lane name..." required />
539
- <button type="submit">Add Lane</button>
535
+ <button type="submit" data-variant="primary">Add Lane</button>
540
536
  </form>
541
537
  </div>
542
538
  `;
@@ -619,7 +615,7 @@ function renderProjectJournalTab(context) {
619
615
  </select>
620
616
  <input name="title" placeholder="Entry title..." required />
621
617
  <textarea name="body" placeholder="Decision rationale or lesson: do X instead of Y when working with Z." required></textarea>
622
- <button type="submit">Add Entry</button>
618
+ <button type="submit" data-variant="primary">Add Entry</button>
623
619
  </form>
624
620
  </div>
625
621
  `;
@@ -675,7 +671,12 @@ function bindProjectTabHandlers(context, tab) {
675
671
  });
676
672
  };
677
673
 
678
- $("#importSnapshotAsNewFile")?.addEventListener("change", async (event) => {
674
+ const importSnapshotAsNewFile = $("#importSnapshotAsNewFile");
675
+ $("#importSnapshotAsNewButton")?.addEventListener("click", () => {
676
+ importSnapshotAsNewFile?.click();
677
+ });
678
+
679
+ importSnapshotAsNewFile?.addEventListener("change", async (event) => {
679
680
  const input = event.currentTarget;
680
681
  const file = input.files?.[0];
681
682
  if (!file) return;
@@ -698,8 +699,16 @@ function bindProjectTabHandlers(context, tab) {
698
699
 
699
700
  const replaceImportConfirmInput = $("#replaceImportConfirmInput");
700
701
  const replaceCurrentBoardImportFile = $("#replaceCurrentBoardImportFile");
702
+ const replaceCurrentBoardImportButton = $("#replaceCurrentBoardImportButton");
701
703
  replaceImportConfirmInput?.addEventListener("input", () => {
702
- if (replaceCurrentBoardImportFile) replaceCurrentBoardImportFile.disabled = replaceImportConfirmInput.value.trim() !== project.slug;
704
+ const disabled = replaceImportConfirmInput.value.trim() !== project.slug;
705
+ if (replaceCurrentBoardImportFile) replaceCurrentBoardImportFile.disabled = disabled;
706
+ if (replaceCurrentBoardImportButton) replaceCurrentBoardImportButton.disabled = disabled;
707
+ });
708
+
709
+ replaceCurrentBoardImportButton?.addEventListener("click", () => {
710
+ if (replaceCurrentBoardImportButton.disabled || replaceCurrentBoardImportFile?.disabled) return;
711
+ replaceCurrentBoardImportFile?.click();
703
712
  });
704
713
 
705
714
  replaceCurrentBoardImportFile?.addEventListener("change", async (event) => {
@@ -1054,7 +1063,8 @@ function renderProjectEntries(entries = []) {
1054
1063
  <span>${escapeHtml(entry.created_by)} - ${formatDate(entry.created_at)}</span>
1055
1064
  <button
1056
1065
  type="button"
1057
- class="ghost project-entry-strike"
1066
+ data-variant="ghost"
1067
+ class="project-entry-strike"
1058
1068
  data-entry-action="toggle-struck"
1059
1069
  data-entry-id="${escapeHtml(entry.id)}"
1060
1070
  data-struck="${isStruck ? "true" : "false"}"
@@ -1122,14 +1132,14 @@ function renderLaneManager() {
1122
1132
  : "";
1123
1133
  return `
1124
1134
  <div class="lane-row${isAiReady ? " lane-row--locked" : ""}" data-lane-id="${lane.id}">
1125
- <button type="button" class="lane-icon-btn" data-lane-action="up" ${index === 0 ? "disabled" : ""} title="Move lane left" aria-label="Move lane left">↑</button>
1126
- <button type="button" class="lane-icon-btn" data-lane-action="down" ${index === lanes.length - 1 ? "disabled" : ""} title="Move lane right" aria-label="Move lane right">↓</button>
1135
+ <button type="button" data-variant="ghost" class="settings-lane-icon-btn" data-lane-action="up" ${index === 0 ? "disabled" : ""} title="Move lane left" aria-label="Move lane left">↑</button>
1136
+ <button type="button" data-variant="ghost" class="settings-lane-icon-btn" data-lane-action="down" ${index === lanes.length - 1 ? "disabled" : ""} title="Move lane right" aria-label="Move lane right">↓</button>
1127
1137
  <input name="name" value="${escapeHtml(lane.name)}" aria-label="Lane name" ${isAiReady ? `readonly title="${escapeHtml(nameTitle)}"` : ""} />
1128
- <button type="button" class="lane-new-toggle ${lane.is_default ? "is-on" : ""}" data-lane-action="toggle-default" aria-pressed="${lane.is_default ? "true" : "false"}" title="${lane.is_default ? "Default lane for new cards — click another lane to move it" : "Make this the default lane for new cards"}" aria-label="Default lane for new cards">
1138
+ <button type="button" data-variant="${lane.is_default ? "secondary" : "ghost"}" class="settings-lane-new-toggle ${lane.is_default ? "is-on" : ""}" data-lane-action="toggle-default" aria-pressed="${lane.is_default ? "true" : "false"}" title="${lane.is_default ? "Default lane for new cards — click another lane to move it" : "Make this the default lane for new cards"}" aria-label="Default lane for new cards">
1129
1139
  <span class="lane-new-sun" aria-hidden="true"></span>
1130
1140
  <span class="lane-new-label">New</span>
1131
1141
  </button>
1132
- <button type="button" class="lane-delete-btn" data-lane-action="delete" ${deleteDisabled(lane, counts, aiEnabled) ? "disabled" : ""} title="${deleteTitle(lane, counts, aiEnabled)}" aria-label="Delete lane">×</button>
1142
+ <button type="button" data-variant="ghost" class="settings-lane-delete-btn danger-button" data-lane-action="delete" ${deleteDisabled(lane, counts, aiEnabled) ? "disabled" : ""} title="${deleteTitle(lane, counts, aiEnabled)}" aria-label="Delete lane">×</button>
1133
1143
  </div>
1134
1144
  `;
1135
1145
  })
@@ -1158,9 +1168,11 @@ async function handleLaneAction(button) {
1158
1168
  if (wasOn) return;
1159
1169
  drawerInner.querySelectorAll('[data-lane-action="toggle-default"]').forEach((btn) => {
1160
1170
  btn.setAttribute("aria-pressed", "false");
1171
+ btn.setAttribute("data-variant", "ghost");
1161
1172
  btn.classList.remove("is-on");
1162
1173
  });
1163
1174
  button.setAttribute("aria-pressed", "true");
1175
+ button.setAttribute("data-variant", "secondary");
1164
1176
  button.classList.add("is-on");
1165
1177
  await updateLane(row, { silent: true });
1166
1178
  toast.success("Default lane updated");
package/public/js/sse.js CHANGED
File without changes
File without changes
File without changes
File without changes
@@ -480,9 +480,9 @@ export async function renderDetail(options = {}) {
480
480
  const detailSubtitleHtml = `
481
481
  <span class="ticket-number">${escapeHtml(ticketLabel(ticket))}</span>
482
482
  <div class="detail-meta-badge-row" aria-label="Ticket metadata controls">
483
- <select class="detail-meta-badge detail-state-badge state-pill-${escapeHtml(stateClass)} meta-select" data-meta-field="state_id" aria-label="Ticket state">${stateOptions}</select>
483
+ <select class="detail-meta-badge detail-state-badge meta-select" data-variant="${escapeHtml(stateClass)}" data-meta-field="state_id" aria-label="Ticket state">${stateOptions}</select>
484
484
  <select class="detail-meta-badge detail-type-badge type-pill-${escapeHtml(detailCanonicalType)} meta-select" data-meta-field="type" aria-label="Ticket type">${typeOptions}</select>
485
- <select class="detail-meta-badge detail-priority-badge priority-pill priority-pill-${escapeHtml(priorityKey)} meta-select" data-meta-field="priority" aria-label="Ticket priority">${priorityOptions}</select>
485
+ <select class="detail-meta-badge detail-priority-badge priority-pill meta-select" data-variant="${escapeHtml(priorityKey)}" data-meta-field="priority" aria-label="Ticket priority">${priorityOptions}</select>
486
486
  </div>
487
487
  `;
488
488
 
@@ -1110,7 +1110,7 @@ export function renderDetailCard(ticket, options = {}) {
1110
1110
  ? `<span class="card-unread-dot" role="status" aria-label="1 unread update"></span>`
1111
1111
  : `<span class="card-unread-dot has-count" role="status" aria-label="${unreadN} unread updates">${unreadN > 99 ? "99+" : unreadN}</span>`;
1112
1112
  return `
1113
- <article class="card card--detail type-${escapeHtml(canonicalType)} priority-${escapeHtml(priorityKey)}"${options.disableOpen ? "" : ` data-open-ticket="${escapeHtml(ticket.id)}"`}>
1113
+ <article class="card card--detail type-${escapeHtml(canonicalType)} priority-${escapeHtml(priorityKey)}" data-variant="${escapeHtml(canonicalType)}"${options.disableOpen ? "" : ` data-open-ticket="${escapeHtml(ticket.id)}"`}>
1114
1114
  ${unreadDot}
1115
1115
  ${detachBtn}
1116
1116
  ${eyebrow}
@@ -1122,7 +1122,7 @@ export function renderDetailCard(ticket, options = {}) {
1122
1122
  ${renderTypeIcon(ticket.type)}
1123
1123
  <span class="card-type-label">${escapeHtml(typeText)}</span>
1124
1124
  </div>
1125
- <span class="detail-card-state state-pill-${escapeHtml(stateClass)}">${escapeHtml(ticket.state_name || "State")}</span>
1125
+ <span class="detail-card-state" data-variant="${escapeHtml(stateClass)}">${escapeHtml(ticket.state_name || "State")}</span>
1126
1126
  ${state.showPriority ? renderPriorityPill(ticket.priority) : ""}
1127
1127
  </div>
1128
1128
  </div>
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes