bosun 0.28.3 → 0.28.4
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/.env.example +68 -0
- package/agent-prompts.mjs +12 -6
- package/agent-work-analyzer.mjs +39 -15
- package/cli.mjs +4 -1
- package/codex-config.mjs +7 -0
- package/monitor.mjs +40 -5
- package/package.json +1 -1
- package/primary-agent.mjs +5 -1
- package/setup.mjs +6 -0
- package/task-executor.mjs +125 -2
- package/ui/app.js +2 -16
- package/ui/components/workspace-switcher.js +25 -32
- package/ui/modules/settings-schema.js +7 -0
- package/ui/styles/base.css +3 -28
- package/ui/styles/components.css +309 -73
- package/ui/styles/kanban.css +10 -16
- package/ui/styles/layout.css +81 -101
- package/ui/styles/sessions.css +27 -32
- package/ui/styles/variables.css +8 -8
- package/ui/styles/workspace-switcher.css +2 -4
- package/ui/tabs/control.js +39 -115
- package/ui/tabs/settings.js +207 -0
- package/ui/tabs/tasks.js +116 -129
- package/ui-server.mjs +487 -0
package/ui/tabs/tasks.js
CHANGED
|
@@ -89,6 +89,8 @@ const SORT_OPTIONS = [
|
|
|
89
89
|
{ value: "title", label: "Title" },
|
|
90
90
|
];
|
|
91
91
|
|
|
92
|
+
const PRIORITY_ORDER = { critical: 0, high: 1, medium: 2, low: 3, "": 4 };
|
|
93
|
+
|
|
92
94
|
const SYSTEM_TAGS = new Set([
|
|
93
95
|
"draft",
|
|
94
96
|
"todo",
|
|
@@ -760,6 +762,8 @@ export function TasksTab() {
|
|
|
760
762
|
const [actionsOpen, setActionsOpen] = useState(false);
|
|
761
763
|
const [exporting, setExporting] = useState(false);
|
|
762
764
|
const [filtersOpen, setFiltersOpen] = useState(false);
|
|
765
|
+
const [listSortCol, setListSortCol] = useState(""); // active column sort in list mode
|
|
766
|
+
const [listSortDir, setListSortDir] = useState("desc"); // "asc" | "desc"
|
|
763
767
|
const [isCompact, setIsCompact] = useState(() => {
|
|
764
768
|
try { return globalThis.matchMedia?.("(max-width: 768px)")?.matches ?? false; }
|
|
765
769
|
catch { return false; }
|
|
@@ -978,6 +982,31 @@ export function TasksTab() {
|
|
|
978
982
|
];
|
|
979
983
|
}, [tasks]);
|
|
980
984
|
|
|
985
|
+
/* ── Client-side table sort (list mode) ── */
|
|
986
|
+
const sortedForTable = useMemo(() => {
|
|
987
|
+
if (!listSortCol) return visible;
|
|
988
|
+
return [...visible].sort((a, b) => {
|
|
989
|
+
let av, bv;
|
|
990
|
+
const dir = listSortDir === "asc" ? 1 : -1;
|
|
991
|
+
if (listSortCol === "priority") {
|
|
992
|
+
av = PRIORITY_ORDER[a.priority || ""] ?? 4;
|
|
993
|
+
bv = PRIORITY_ORDER[b.priority || ""] ?? 4;
|
|
994
|
+
return dir * (av - bv);
|
|
995
|
+
}
|
|
996
|
+
if (listSortCol === "updated") {
|
|
997
|
+
av = a.updated_at ? new Date(a.updated_at).getTime() : 0;
|
|
998
|
+
bv = b.updated_at ? new Date(b.updated_at).getTime() : 0;
|
|
999
|
+
return dir * (av - bv);
|
|
1000
|
+
}
|
|
1001
|
+
if (listSortCol === "status") { av = a.status || ""; bv = b.status || ""; }
|
|
1002
|
+
else if (listSortCol === "title") { av = (a.title || "").toLowerCase(); bv = (b.title || "").toLowerCase(); }
|
|
1003
|
+
else if (listSortCol === "repo") { av = a.repository || a.workspace || ""; bv = b.repository || b.workspace || ""; }
|
|
1004
|
+
else if (listSortCol === "branch") { av = getTaskBaseBranch(a); bv = getTaskBaseBranch(b); }
|
|
1005
|
+
else { return 0; }
|
|
1006
|
+
return dir * String(av).localeCompare(String(bv));
|
|
1007
|
+
});
|
|
1008
|
+
}, [visible, listSortCol, listSortDir]);
|
|
1009
|
+
|
|
981
1010
|
/* ── Handlers ── */
|
|
982
1011
|
const handleFilter = async (s) => {
|
|
983
1012
|
haptic();
|
|
@@ -1578,23 +1607,16 @@ export function TasksTab() {
|
|
|
1578
1607
|
`}
|
|
1579
1608
|
</div>
|
|
1580
1609
|
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
label=${metric.label}
|
|
1592
|
-
color=${metric.color}
|
|
1593
|
-
/>
|
|
1594
|
-
`,
|
|
1595
|
-
)}
|
|
1596
|
-
</div>
|
|
1597
|
-
<//>
|
|
1610
|
+
<div class="snapshot-bar">
|
|
1611
|
+
${summaryMetrics.map((m) => html`
|
|
1612
|
+
<span key=${m.label} class="snapshot-pill">
|
|
1613
|
+
<span class="snapshot-dot" style="background:${m.color};" />
|
|
1614
|
+
<strong class="snapshot-val">${m.value}</strong>
|
|
1615
|
+
<span class="snapshot-lbl">${m.label}</span>
|
|
1616
|
+
</span>
|
|
1617
|
+
`)}
|
|
1618
|
+
<span class="snapshot-view-tag">${isKanban ? "⬛ Board" : "☰ List"}</span>
|
|
1619
|
+
</div>
|
|
1598
1620
|
|
|
1599
1621
|
<style>
|
|
1600
1622
|
.actions-btn { display:inline-flex; align-items:center; gap:4px; }
|
|
@@ -1617,119 +1639,84 @@ export function TasksTab() {
|
|
|
1617
1639
|
|
|
1618
1640
|
${isKanban && html`<${KanbanBoard} onOpenTask=${openDetail} />`}
|
|
1619
1641
|
|
|
1620
|
-
${!isKanban &&
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
:
|
|
1662
|
-
${task.updated_at
|
|
1663
|
-
? html` · ${formatRelative(task.updated_at)}`
|
|
1664
|
-
: ""}
|
|
1665
|
-
</div>
|
|
1666
|
-
</div>
|
|
1667
|
-
<div class="task-card-badges">
|
|
1668
|
-
${isManual && html`<${Badge} status="warning" text="manual" />`}
|
|
1669
|
-
<${Badge} status=${task.status} text=${task.status} />
|
|
1670
|
-
</div>
|
|
1671
|
-
</div>
|
|
1672
|
-
<div class="meta-text">
|
|
1673
|
-
${task.description
|
|
1674
|
-
? truncate(task.description, descriptionLimit)
|
|
1675
|
-
: "No description."}
|
|
1676
|
-
</div>
|
|
1677
|
-
${getTaskBaseBranch(task) &&
|
|
1678
|
-
html`
|
|
1679
|
-
<div class="meta-text">
|
|
1680
|
-
Base: <code>${getTaskBaseBranch(task)}</code>
|
|
1681
|
-
</div>
|
|
1682
|
-
`}
|
|
1683
|
-
${(task.workspace || task.repository) &&
|
|
1684
|
-
html`
|
|
1685
|
-
<div class="meta-text" style="display:flex;gap:4px;align-items:center;flex-wrap:wrap">
|
|
1686
|
-
${task.workspace && html`<span class="pill" style="font-size:11px">📂 ${task.workspace}</span>`}
|
|
1687
|
-
${task.repository && html`<span class="pill" style="font-size:11px">📁 ${task.repository}</span>`}
|
|
1688
|
-
</div>
|
|
1689
|
-
`}
|
|
1690
|
-
${tags.length > 0 &&
|
|
1691
|
-
html`
|
|
1692
|
-
<div class="tag-row">
|
|
1693
|
-
${visibleTags.map(
|
|
1694
|
-
(tag) => html`<span class="tag-chip">#${tag}</span>`,
|
|
1695
|
-
)}
|
|
1696
|
-
${extraTagCount > 0 &&
|
|
1697
|
-
html`
|
|
1698
|
-
<span class="tag-chip tag-chip-muted">
|
|
1699
|
-
+${extraTagCount}
|
|
1700
|
-
</span>
|
|
1701
|
-
`}
|
|
1702
|
-
</div>
|
|
1703
|
-
`}
|
|
1704
|
-
${!batchMode &&
|
|
1705
|
-
html`
|
|
1706
|
-
<div class="btn-row mt-sm task-card-actions" onClick=${(e) => e.stopPropagation()}>
|
|
1707
|
-
${task.status === "todo" &&
|
|
1708
|
-
html`
|
|
1709
|
-
<button
|
|
1710
|
-
class="btn btn-primary btn-sm"
|
|
1711
|
-
onClick=${() => openStartModal(task)}
|
|
1642
|
+
${!isKanban && visible.length > 0 && html`
|
|
1643
|
+
<div class="task-table-wrap">
|
|
1644
|
+
<table class="task-table">
|
|
1645
|
+
<thead>
|
|
1646
|
+
<tr>
|
|
1647
|
+
${[
|
|
1648
|
+
{ col: "status", label: "Status" },
|
|
1649
|
+
{ col: "priority", label: "Pri" },
|
|
1650
|
+
{ col: "title", label: "Title", grow: true },
|
|
1651
|
+
{ col: "branch", label: "Branch" },
|
|
1652
|
+
{ col: "repo", label: "Repo" },
|
|
1653
|
+
{ col: "updated", label: "Updated" },
|
|
1654
|
+
].map(({ col, label, grow }) => {
|
|
1655
|
+
const active = listSortCol === col;
|
|
1656
|
+
const arrow = active ? (listSortDir === "asc" ? "▲" : "▼") : "⇅";
|
|
1657
|
+
return html`
|
|
1658
|
+
<th
|
|
1659
|
+
key=${col}
|
|
1660
|
+
class="task-th ${active ? "task-th-active" : ""} ${grow ? "task-th-grow" : ""}"
|
|
1661
|
+
onClick=${() => {
|
|
1662
|
+
if (listSortCol === col) {
|
|
1663
|
+
setListSortDir(listSortDir === "asc" ? "desc" : "asc");
|
|
1664
|
+
} else {
|
|
1665
|
+
setListSortCol(col);
|
|
1666
|
+
setListSortDir("desc");
|
|
1667
|
+
}
|
|
1668
|
+
}}
|
|
1669
|
+
>${label} <span class="task-th-arrow">${arrow}</span></th>
|
|
1670
|
+
`;
|
|
1671
|
+
})}
|
|
1672
|
+
</tr>
|
|
1673
|
+
</thead>
|
|
1674
|
+
<tbody>
|
|
1675
|
+
${sortedForTable.map((task) => {
|
|
1676
|
+
const isManual = isTaskManual(task);
|
|
1677
|
+
const branch = getTaskBaseBranch(task);
|
|
1678
|
+
return html`
|
|
1679
|
+
<tr
|
|
1680
|
+
key=${task.id}
|
|
1681
|
+
class="task-tr ${batchMode && selectedIds.has(task.id) ? "task-tr-selected" : ""}"
|
|
1682
|
+
data-status=${task.status || ""}
|
|
1683
|
+
onClick=${() => batchMode ? toggleSelect(task.id) : openDetail(task.id)}
|
|
1712
1684
|
>
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1685
|
+
<td class="task-td task-td-status">
|
|
1686
|
+
<${Badge} status=${task.status} text=${task.status} />
|
|
1687
|
+
${isManual && html`<${Badge} status="warning" text="⚑" />`}
|
|
1688
|
+
</td>
|
|
1689
|
+
<td class="task-td task-td-pri">
|
|
1690
|
+
${task.priority
|
|
1691
|
+
? html`<${Badge} status=${task.priority} text=${task.priority} />`
|
|
1692
|
+
: html`<span class="task-td-empty">—</span>`}
|
|
1693
|
+
</td>
|
|
1694
|
+
<td class="task-td task-td-title">
|
|
1695
|
+
<div class="task-td-title-text">${task.title || "(untitled)"}</div>
|
|
1696
|
+
${task.id && html`<div class="task-td-id">${task.id}</div>`}
|
|
1697
|
+
</td>
|
|
1698
|
+
<td class="task-td task-td-branch">
|
|
1699
|
+
${branch
|
|
1700
|
+
? html`<code class="task-td-code">${branch}</code>`
|
|
1701
|
+
: html`<span class="task-td-empty">—</span>`}
|
|
1702
|
+
</td>
|
|
1703
|
+
<td class="task-td task-td-repo">
|
|
1704
|
+
${(task.repository || task.workspace)
|
|
1705
|
+
? html`<span>${task.repository || task.workspace}</span>`
|
|
1706
|
+
: html`<span class="task-td-empty">—</span>`}
|
|
1707
|
+
</td>
|
|
1708
|
+
<td class="task-td task-td-updated">
|
|
1709
|
+
${task.updated_at
|
|
1710
|
+
? html`<span class="task-td-date">${formatRelative(task.updated_at)}</span>`
|
|
1711
|
+
: html`<span class="task-td-empty">—</span>`}
|
|
1712
|
+
</td>
|
|
1713
|
+
</tr>
|
|
1714
|
+
`;
|
|
1715
|
+
})}
|
|
1716
|
+
</tbody>
|
|
1717
|
+
</table>
|
|
1718
|
+
</div>
|
|
1719
|
+
`}
|
|
1733
1720
|
${!isKanban && !visible.length &&
|
|
1734
1721
|
html`
|
|
1735
1722
|
<${EmptyState}
|