@workerdeck/ui 3.5.0 → 3.6.1

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/build/index.mjs CHANGED
@@ -6,7 +6,7 @@ import { Anchor, ArrowRight, Atom, Beaker, BedDouble, BellRing, Bike, Binary, Bl
6
6
  import { AlertDialog as AlertDialog$1 } from "@base-ui/react/alert-dialog";
7
7
  import { Popover as Popover$1 } from "@base-ui/react/popover";
8
8
  import { Toaster as Toaster$1, toast } from "sonner";
9
- import { STATE_LABELS, STATE_ORDER, adaptersOf, addCustomGroup, clearFilters, displayCustomized, displayedTasks, facetFilterCount, filterRows, groupRows, hasFacetFilter, isAgentRecord, isTeamCollapsed, moveCustomGroup, moveToCustomGroup, newCustomGroupId, projectLabel, projectName, projectSubpath, projectsOf, removeCustomGroup, renameCustomGroup, scopeActive, sessionKey, sessionLabel, subagentLabel, subsetSummary, taskSummary, teamSummary, toggleTeamCollapsed, visibleShells, visibleSubagents, visibleTasks } from "@workerdeck/protocol";
9
+ import { PROJECT_ACCENTS, STATE_LABELS, STATE_ORDER, adaptersOf, addCustomGroup, clearFilters, customGroupColor, displayCustomized, displayedTasks, facetFilterCount, filterRows, groupRows, hasFacetFilter, isAgentRecord, isTeamCollapsed, moveCustomGroup, moveToCustomGroup, newCustomGroupId, projectLabel, projectName, projectSubpath, projectsOf, removeCustomGroup, renameCustomGroup, scopeActive, sessionKey, sessionLabel, styleCustomGroup, subagentLabel, subsetSummary, taskSummary, teamSummary, toggleTeamCollapsed, visibleShells, visibleSubagents, visibleTasks } from "@workerdeck/protocol";
10
10
  //#region src/components/ui/Card.tsx
11
11
  function Card({ className, ...props }) {
12
12
  return /* @__PURE__ */ jsx("div", {
@@ -1156,11 +1156,18 @@ function SessionItem({ row, active = false, activeStepKey, showGateway, showProj
1156
1156
  cost === "-" ? void 0 : cost
1157
1157
  ].filter((part) => Boolean(part));
1158
1158
  const model = friendlyModel(info.model);
1159
+ const status = statusLine(info.statusLabel);
1160
+ const needsYou = row.state === "attention" || row.teamState === "attention" || badgeState === "attention";
1159
1161
  const parts = [];
1160
1162
  if (model) parts.push(/* @__PURE__ */ jsx("span", {
1161
1163
  className: vendorTextClass(engine, info.model),
1162
1164
  children: model
1163
1165
  }, "model"));
1166
+ if (status) parts.push(/* @__PURE__ */ jsx("span", {
1167
+ title: status,
1168
+ className: "text-fg-3",
1169
+ children: status
1170
+ }, "status"));
1164
1171
  if (project !== void 0) parts.push(/* @__PURE__ */ jsxs("span", {
1165
1172
  title: projectTitle,
1166
1173
  children: [/* @__PURE__ */ jsx(ProjectIcon, {
@@ -1235,7 +1242,7 @@ function SessionItem({ row, active = false, activeStepKey, showGateway, showProj
1235
1242
  });
1236
1243
  }
1237
1244
  },
1238
- className: cn("group flex w-full cursor-pointer flex-col p-1 text-left outline-none", "rounded-[4px] transition-colors focus-visible:ring-2 focus-visible:ring-ring", holdsOpenStep ? "bg-row-selected-weak" : active ? "bg-row-selected" : "hover:bg-row-hover", className),
1245
+ className: cn("group flex w-full cursor-pointer flex-col p-1 text-left outline-none", "rounded-[4px] transition-colors focus-visible:ring-2 focus-visible:ring-ring", needsYou ? cn("bg-row-attention", active && "ring-1 ring-accent ring-inset") : holdsOpenStep ? "bg-row-selected-weak" : active ? "bg-row-selected" : "hover:bg-row-hover", className),
1239
1246
  children: [agent && variant === "member" ? /* @__PURE__ */ jsxs("div", {
1240
1247
  className: cn("flex h-6 items-center gap-1.5 overflow-hidden py-0.5 pr-0.5 pl-1.5", info.engineAsleep && "opacity-60"),
1241
1248
  children: [
@@ -1245,9 +1252,10 @@ function SessionItem({ row, active = false, activeStepKey, showGateway, showProj
1245
1252
  size: 22
1246
1253
  }),
1247
1254
  nameLabel,
1248
- info.title ? /* @__PURE__ */ jsx("span", {
1255
+ status ? /* @__PURE__ */ jsx("span", {
1256
+ title: status,
1249
1257
  className: "min-w-0 flex-1 truncate text-body-sm text-fg-3",
1250
- children: info.title
1258
+ children: status
1251
1259
  }) : /* @__PURE__ */ jsx("span", { className: "flex-1" }),
1252
1260
  unread,
1253
1261
  /* @__PURE__ */ jsx("span", {
@@ -1269,6 +1277,10 @@ function SessionItem({ row, active = false, activeStepKey, showGateway, showProj
1269
1277
  children: [
1270
1278
  nameLabel,
1271
1279
  agent?.leads ? /* @__PURE__ */ jsx(LeadChip, {}) : null,
1280
+ agent ? /* @__PURE__ */ jsx("span", {
1281
+ className: "w-max max-w-max min-w-0 grow basis-0 truncate text-label text-fg-4/70",
1282
+ children: time
1283
+ }) : null,
1272
1284
  /* @__PURE__ */ jsx("span", { className: "flex-1" }),
1273
1285
  unread,
1274
1286
  teamUnseen > 0 ? /* @__PURE__ */ jsxs("span", {
@@ -1287,20 +1299,14 @@ function SessionItem({ row, active = false, activeStepKey, showGateway, showProj
1287
1299
  }), /* @__PURE__ */ jsxs("div", {
1288
1300
  className: "flex h-5 items-center gap-1.5 overflow-hidden text-body-sm tracking-[-0.005em]",
1289
1301
  children: [
1290
- agent ? /* @__PURE__ */ jsxs(Fragment$1, { children: [info.title ? /* @__PURE__ */ jsx("span", {
1291
- className: "min-w-0 truncate text-fg-4",
1292
- children: info.title
1293
- }) : null, /* @__PURE__ */ jsxs("span", {
1294
- className: "shrink-0 text-fg-4",
1295
- children: [model ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1296
- info.title ? "· " : "",
1297
- /* @__PURE__ */ jsx("span", {
1298
- className: vendorTextClass(engine, info.model),
1299
- children: model
1300
- }),
1301
- " · "
1302
- ] }) : info.title ? "· " : null, time]
1303
- })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1302
+ agent ? /* @__PURE__ */ jsxs(Fragment$1, { children: [model ? /* @__PURE__ */ jsx("span", {
1303
+ className: cn("shrink-0", vendorTextClass(engine, info.model)),
1304
+ children: model
1305
+ }) : null, status ? /* @__PURE__ */ jsxs("span", {
1306
+ title: status,
1307
+ className: "min-w-0 truncate text-fg-3",
1308
+ children: [model ? "· " : "", status]
1309
+ }) : null] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1304
1310
  className: "min-w-0 truncate text-fg-4",
1305
1311
  children: parts.map((part, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 ? " · " : "", part] }, i))
1306
1312
  }), /* @__PURE__ */ jsxs("span", {
@@ -1399,6 +1405,9 @@ function NameEditor({ initial, placeholder, onCommit, onCancel }) {
1399
1405
  className: cn("-my-0.5 min-w-0 flex-1 rounded-sm border border-ring bg-bg px-1 py-px", "text-body-sm leading-5 text-fg-1 outline-none")
1400
1406
  });
1401
1407
  }
1408
+ function statusLine(label) {
1409
+ return label ? label.emoji ? `${label.emoji} ${label.text}` : label.text : void 0;
1410
+ }
1402
1411
  //#endregion
1403
1412
  //#region src/lib/team-drop.ts
1404
1413
  function dropZone(offsetY, height) {
@@ -1610,10 +1619,11 @@ function useGroupDrag(groups, onChange, options) {
1610
1619
  return id;
1611
1620
  },
1612
1621
  rename: (id, name) => onChange(renameCustomGroup(groups, id, name)),
1622
+ style: (id, look) => onChange(styleCustomGroup(groups, id, look)),
1613
1623
  remove: (id) => onChange(removeCustomGroup(groups, id))
1614
1624
  };
1615
1625
  }
1616
- function CustomGroupHeader({ group, editing, onEditingChange, onRename, onRemove, dragProps }) {
1626
+ function CustomGroupHeader({ group, look, editing, onEditingChange, onRename, onRemove, onStyle, dragProps }) {
1617
1627
  if (editing) return /* @__PURE__ */ jsx(GroupNameInput, {
1618
1628
  initial: group.label ?? "",
1619
1629
  onRename,
@@ -1624,9 +1634,13 @@ function CustomGroupHeader({ group, editing, onEditingChange, onRename, onRemove
1624
1634
  ...dragProps,
1625
1635
  label: group.label ?? "",
1626
1636
  count: group.rows.length,
1637
+ caps: !custom,
1627
1638
  className: cn("group/heading", custom && "cursor-grab"),
1628
1639
  onDoubleClick: custom ? () => onEditingChange(true) : void 0,
1629
- leading: custom ? /* @__PURE__ */ jsx(GripVertical, { className: "-ml-1.5 size-3 opacity-0 group-hover/heading:opacity-60" }) : void 0,
1640
+ leading: custom ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(GripVertical, { className: "-mr-1.5 -ml-1.5 size-3 opacity-0 group-hover/heading:opacity-60" }), look ? /* @__PURE__ */ jsx(GroupBadge, {
1641
+ group: look,
1642
+ onStyle
1643
+ }) : null] }) : void 0,
1630
1644
  actions: custom ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(HeadingAction, {
1631
1645
  label: "Rename group",
1632
1646
  onClick: () => onEditingChange(true),
@@ -1666,14 +1680,14 @@ function GroupNameInput({ initial, onRename, onDone }) {
1666
1680
  })
1667
1681
  });
1668
1682
  }
1669
- function GroupHeading({ label, count, leading, actions, className, ...rest }) {
1683
+ function GroupHeading({ label, count, leading, actions, caps = true, className, ...rest }) {
1670
1684
  return /* @__PURE__ */ jsxs("div", {
1671
1685
  ...rest,
1672
1686
  className: cn("group/heading flex min-h-5 items-center gap-2 px-2 text-label font-medium text-fg-4", className),
1673
1687
  children: [
1674
1688
  leading,
1675
1689
  /* @__PURE__ */ jsx("span", {
1676
- className: "min-w-0 truncate uppercase tracking-wide",
1690
+ className: cn("min-w-0 truncate", caps ? "uppercase tracking-wide" : "text-fg-3"),
1677
1691
  children: label
1678
1692
  }),
1679
1693
  /* @__PURE__ */ jsx("span", {
@@ -1710,6 +1724,94 @@ function NewGroupButton({ onClick }) {
1710
1724
  children: [/* @__PURE__ */ jsx(Plus, {}), "New group"]
1711
1725
  });
1712
1726
  }
1727
+ const ICON_PX = 64;
1728
+ function GroupBadge({ group, onStyle }) {
1729
+ const [error, setError] = useState();
1730
+ const file = useRef(null);
1731
+ const mark = group.icon ? /* @__PURE__ */ jsx("img", {
1732
+ src: group.icon,
1733
+ alt: "",
1734
+ className: "size-3.5 rounded-[3px] object-cover",
1735
+ draggable: false
1736
+ }) : /* @__PURE__ */ jsx("span", {
1737
+ className: "size-2 rounded-[2px]",
1738
+ style: { background: customGroupColor(group) }
1739
+ });
1740
+ if (!onStyle) return /* @__PURE__ */ jsx("span", {
1741
+ className: "flex size-3.5 shrink-0 items-center justify-center",
1742
+ children: mark
1743
+ });
1744
+ return /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, { render: /* @__PURE__ */ jsx("button", {
1745
+ type: "button",
1746
+ "aria-label": `Colour or image for ${group.name}`,
1747
+ title: "Colour or image",
1748
+ onClick: (e) => e.stopPropagation(),
1749
+ onDoubleClick: (e) => e.stopPropagation(),
1750
+ className: "flex size-3.5 shrink-0 items-center justify-center rounded-[3px] hover:ring-1 hover:ring-border",
1751
+ children: mark
1752
+ }) }), /* @__PURE__ */ jsxs(PopoverContent, {
1753
+ align: "start",
1754
+ className: "flex w-44 flex-col gap-2",
1755
+ children: [
1756
+ /* @__PURE__ */ jsx("div", {
1757
+ className: "grid grid-cols-4 gap-1.5",
1758
+ children: PROJECT_ACCENTS.map((color) => /* @__PURE__ */ jsx("button", {
1759
+ type: "button",
1760
+ "aria-label": `Colour ${color}`,
1761
+ onClick: () => onStyle({
1762
+ color,
1763
+ icon: null
1764
+ }),
1765
+ className: cn("h-6 rounded-[4px] ring-offset-1 ring-offset-surface", !group.icon && customGroupColor(group) === color && "ring-2 ring-fg-3"),
1766
+ style: { background: color }
1767
+ }, color))
1768
+ }),
1769
+ /* @__PURE__ */ jsx("input", {
1770
+ ref: file,
1771
+ type: "file",
1772
+ accept: "image/*",
1773
+ className: "hidden",
1774
+ onChange: (e) => {
1775
+ const picked = e.target.files?.[0];
1776
+ e.target.value = "";
1777
+ if (!picked) return;
1778
+ setError(void 0);
1779
+ iconFromFile(picked).then((icon) => onStyle({ icon })).catch(() => setError("That image could not be read"));
1780
+ }
1781
+ }),
1782
+ /* @__PURE__ */ jsxs(Button, {
1783
+ variant: "outline",
1784
+ size: "xs",
1785
+ onClick: () => file.current?.click(),
1786
+ children: [/* @__PURE__ */ jsx(Image, {}), "Choose image…"]
1787
+ }),
1788
+ group.icon ? /* @__PURE__ */ jsx(Button, {
1789
+ variant: "ghost",
1790
+ size: "xs",
1791
+ onClick: () => onStyle({ icon: null }),
1792
+ children: "Remove image"
1793
+ }) : null,
1794
+ error ? /* @__PURE__ */ jsx("span", {
1795
+ className: "text-label text-danger",
1796
+ children: error
1797
+ }) : null
1798
+ ]
1799
+ })] });
1800
+ }
1801
+ async function iconFromFile(file) {
1802
+ const bitmap = await createImageBitmap(file);
1803
+ const canvas = document.createElement("canvas");
1804
+ canvas.width = ICON_PX;
1805
+ canvas.height = ICON_PX;
1806
+ const context = canvas.getContext("2d");
1807
+ if (!context) throw new Error("no canvas");
1808
+ const scale = Math.max(ICON_PX / bitmap.width, ICON_PX / bitmap.height);
1809
+ const width = bitmap.width * scale;
1810
+ const height = bitmap.height * scale;
1811
+ context.drawImage(bitmap, (ICON_PX - width) / 2, (ICON_PX - height) / 2, width, height);
1812
+ bitmap.close();
1813
+ return canvas.toDataURL("image/png");
1814
+ }
1713
1815
  //#endregion
1714
1816
  //#region src/components/agent/SessionBrowser.tsx
1715
1817
  function rowShapeClass(active) {
@@ -1752,6 +1854,8 @@ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, activeS
1752
1854
  const heading = (group) => {
1753
1855
  if (custom) return /* @__PURE__ */ jsx(CustomGroupHeader, {
1754
1856
  group,
1857
+ look: config.customGroups?.find((custom) => custom.id === group.custom),
1858
+ onStyle: (look) => group.custom && drag.style(group.custom, look),
1755
1859
  editing: group.custom !== void 0 && editingGroup === group.custom,
1756
1860
  onEditingChange: (editing) => setEditingGroup(editing ? group.custom : void 0),
1757
1861
  onRename: (name) => group.custom && drag.rename(group.custom, name),
@@ -1854,7 +1958,7 @@ function SessionBrowser({ rows, config, onConfigChange, scope, activeId, activeS
1854
1958
  title: "Nothing here",
1855
1959
  description: "No session to show."
1856
1960
  }) : /* @__PURE__ */ jsxs("div", {
1857
- className: "flex flex-col gap-4 px-1",
1961
+ className: "flex flex-col gap-2 px-1",
1858
1962
  children: [groups.map((group) => /* @__PURE__ */ jsxs("div", {
1859
1963
  ...draggable ? drag.container(group) : {},
1860
1964
  className: cn("flex flex-col gap-1 rounded-md", draggable && drag.isOver(`group:${group.key}`) && "bg-row-hover/50"),