epiq 1.4.1 → 1.4.2

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/gui/main.js CHANGED
@@ -22849,6 +22849,19 @@ function useLocation() {
22849
22849
  );
22850
22850
  return React2.useContext(LocationContext).location;
22851
22851
  }
22852
+ function useMatch(pattern) {
22853
+ invariant(
22854
+ useInRouterContext(),
22855
+ // TODO: This error is probably because they somehow have 2 versions of the
22856
+ // router loaded. We can help them understand how to avoid that.
22857
+ `useMatch() may be used only in the context of a <Router> component.`
22858
+ );
22859
+ let { pathname } = useLocation();
22860
+ return React2.useMemo(
22861
+ () => matchPath(pattern, decodePath(pathname)),
22862
+ [pathname, pattern]
22863
+ );
22864
+ }
22852
22865
  var navigateEffectWarning = `You should call navigate() in a React.useEffect(), not when your component is first rendered.`;
22853
22866
  function useIsomorphicLayoutEffect(cb) {
22854
22867
  let isStatic = React2.useContext(NavigationContext).static;
@@ -24825,7 +24838,7 @@ function useViewTransitionState(to, { relative } = {}) {
24825
24838
  }
24826
24839
 
24827
24840
  // source/gui/client/App.tsx
24828
- var import_react15 = __toESM(require_react(), 1);
24841
+ var import_react16 = __toESM(require_react(), 1);
24829
24842
 
24830
24843
  // source/gui/client/components/Aside.tsx
24831
24844
  var import_react = __toESM(require_react(), 1);
@@ -25305,7 +25318,7 @@ var User = ({
25305
25318
  );
25306
25319
 
25307
25320
  // source/version.ts
25308
- var EPIQ_VERSION = "1.4.1";
25321
+ var EPIQ_VERSION = "1.4.2";
25309
25322
 
25310
25323
  // source/gui/client/components/Header.tsx
25311
25324
  var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
@@ -27702,11 +27715,11 @@ function addChildren(props, children) {
27702
27715
  }
27703
27716
  }
27704
27717
  }
27705
- function productionCreate(_, jsx34, jsxs22) {
27718
+ function productionCreate(_, jsx35, jsxs23) {
27706
27719
  return create2;
27707
27720
  function create2(_2, type, props, key) {
27708
27721
  const isStaticChildren = Array.isArray(props.children);
27709
- const fn = isStaticChildren ? jsxs22 : jsx34;
27722
+ const fn = isStaticChildren ? jsxs23 : jsx35;
27710
27723
  return key ? fn(type, props, key) : fn(type, props);
27711
27724
  }
27712
27725
  }
@@ -38842,6 +38855,9 @@ var MarkdownContent = ({
38842
38855
  }
38843
38856
  );
38844
38857
 
38858
+ // source/lib/utils/comment.limits.ts
38859
+ var MAX_COMMENT_LENGTH = 4e3;
38860
+
38845
38861
  // source/gui/client/components/IssueComments.tsx
38846
38862
  var import_jsx_runtime18 = __toESM(require_jsx_runtime(), 1);
38847
38863
  var IssueComments = ({
@@ -38853,9 +38869,11 @@ var IssueComments = ({
38853
38869
  onDeleteComment
38854
38870
  }) => {
38855
38871
  const [body, setBody] = (0, import_react9.useState)("");
38872
+ const length = body.trim().length;
38873
+ const tooLong = length > MAX_COMMENT_LENGTH;
38856
38874
  const addComment = () => {
38857
38875
  const nextBody = body.trim();
38858
- if (!nextBody) return;
38876
+ if (!nextBody || tooLong) return;
38859
38877
  onAddComment?.(issueId, nextBody);
38860
38878
  setBody("");
38861
38879
  };
@@ -38912,7 +38930,7 @@ var IssueComments = ({
38912
38930
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
38913
38931
  Textarea,
38914
38932
  {
38915
- maxLength: 120,
38933
+ maxLength: Number.MAX_SAFE_INTEGER,
38916
38934
  value: body,
38917
38935
  placeholder: "write a comment",
38918
38936
  onChange: (event) => setBody(event.target.value),
@@ -38929,7 +38947,20 @@ var IssueComments = ({
38929
38947
  }
38930
38948
  }
38931
38949
  ),
38932
- /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(ActionRow, { children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Button, { onClick: addComment, children: "comment" }) })
38950
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(ActionRow, { children: [
38951
+ length > MAX_COMMENT_LENGTH / 2 && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
38952
+ "span",
38953
+ {
38954
+ style: {
38955
+ alignSelf: "center",
38956
+ fontSize: 11,
38957
+ color: tooLong ? GUI_THEME.red : GUI_THEME.dim
38958
+ },
38959
+ children: tooLong ? `${length - MAX_COMMENT_LENGTH} over the limit` : `${MAX_COMMENT_LENGTH - length} left`
38960
+ }
38961
+ ),
38962
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(Button, { disabled: tooLong, onClick: addComment, children: "comment" })
38963
+ ] })
38933
38964
  ] })
38934
38965
  ] });
38935
38966
  };
@@ -39446,12 +39477,177 @@ var IssueDetails = ({
39446
39477
  ] });
39447
39478
  };
39448
39479
 
39480
+ // source/gui/client/components/BulkDetails.tsx
39481
+ var import_jsx_runtime21 = __toESM(require_jsx_runtime(), 1);
39482
+ var countBy = (issues, pick) => {
39483
+ const byId = /* @__PURE__ */ new Map();
39484
+ for (const issue of issues) {
39485
+ for (const item of pick(issue)) {
39486
+ const seen = byId.get(item.id);
39487
+ byId.set(item.id, { item, count: (seen?.count ?? 0) + 1 });
39488
+ }
39489
+ }
39490
+ return [...byId.values()];
39491
+ };
39492
+ var BulkDetails = ({
39493
+ issues,
39494
+ knownTags,
39495
+ knownAssignees,
39496
+ tagName,
39497
+ assigneeName,
39498
+ onChangeTagName,
39499
+ onChangeAssigneeName,
39500
+ onAddTag,
39501
+ onRemoveTag,
39502
+ onAddAssignee,
39503
+ onRemoveAssignee,
39504
+ onCloseIssues,
39505
+ onReopenIssues,
39506
+ onClear
39507
+ }) => {
39508
+ const total = issues.length;
39509
+ const tags = countBy(issues, (issue) => issue.tags);
39510
+ const assignees = countBy(issues, (issue) => issue.assignees);
39511
+ const availableTags = knownTags.filter(
39512
+ (tag) => !tags.some((entry) => entry.item.id === tag.id && entry.count === total)
39513
+ );
39514
+ const availableAssignees = knownAssignees.filter(
39515
+ (person) => !assignees.some(
39516
+ (entry) => entry.item.id === person.id && entry.count === total
39517
+ )
39518
+ );
39519
+ const partial = (count) => count === total ? "" : ` ${count}/${total}`;
39520
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Aside, { children: [
39521
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
39522
+ "div",
39523
+ {
39524
+ style: {
39525
+ display: "flex",
39526
+ alignItems: "center",
39527
+ justifyContent: "space-between",
39528
+ gap: 8,
39529
+ marginBottom: 12
39530
+ },
39531
+ children: [
39532
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { fontSize: 13, fontWeight: 600, color: GUI_THEME.primary }, children: [
39533
+ total,
39534
+ " tickets selected"
39535
+ ] }),
39536
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { variant: "ghost", onClick: onClear, children: "clear" })
39537
+ ]
39538
+ }
39539
+ ),
39540
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Section, { title: "Tags", children: [
39541
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChipRow, { children: tags.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Empty, { children: "No tags" }) : tags.map(({ item, count }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
39542
+ Button,
39543
+ {
39544
+ variant: "chip",
39545
+ title: "Remove from every selected ticket",
39546
+ onClick: () => onRemoveTag(item.id),
39547
+ style: { color: item.color },
39548
+ children: [
39549
+ item.name,
39550
+ partial(count),
39551
+ " \xD7"
39552
+ ]
39553
+ },
39554
+ item.id
39555
+ )) }),
39556
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChipRow, { children: availableTags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
39557
+ Button,
39558
+ {
39559
+ variant: "chip",
39560
+ title: "Add to every selected ticket",
39561
+ onClick: () => onAddTag(tag.name),
39562
+ style: { color: tag.color, opacity: 0.6 },
39563
+ children: [
39564
+ "+ ",
39565
+ tag.name
39566
+ ]
39567
+ },
39568
+ tag.id
39569
+ )) }),
39570
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(AddRow, { children: [
39571
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
39572
+ Input,
39573
+ {
39574
+ value: tagName,
39575
+ placeholder: "new tag",
39576
+ onChange: (event) => onChangeTagName(event.target.value),
39577
+ onKeyDown: (event) => {
39578
+ if (event.key === "Enter" && tagName.trim()) {
39579
+ onAddTag(tagName.trim());
39580
+ }
39581
+ }
39582
+ }
39583
+ ),
39584
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Button, { onClick: () => tagName.trim() && onAddTag(tagName.trim()), children: "add" })
39585
+ ] })
39586
+ ] }),
39587
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Section, { title: "Assignees", children: [
39588
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChipRow, { children: assignees.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Empty, { children: "No assignees" }) : assignees.map(({ item, count }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
39589
+ Button,
39590
+ {
39591
+ variant: "chip",
39592
+ title: "Remove from every selected ticket",
39593
+ onClick: () => onRemoveAssignee(item.id),
39594
+ style: { color: item.color },
39595
+ children: [
39596
+ "@",
39597
+ item.name,
39598
+ partial(count),
39599
+ " \xD7"
39600
+ ]
39601
+ },
39602
+ item.id
39603
+ )) }),
39604
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ChipRow, { children: availableAssignees.map((person) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
39605
+ Button,
39606
+ {
39607
+ variant: "chip",
39608
+ title: "Add to every selected ticket",
39609
+ onClick: () => onAddAssignee(person.id),
39610
+ style: { color: person.color, opacity: 0.6 },
39611
+ children: [
39612
+ "+ @",
39613
+ person.isSelf ? "me" : person.name
39614
+ ]
39615
+ },
39616
+ person.id
39617
+ )) }),
39618
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(AddRow, { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
39619
+ Input,
39620
+ {
39621
+ value: assigneeName,
39622
+ placeholder: "name of unknown contributor",
39623
+ onChange: (event) => onChangeAssigneeName(event.target.value)
39624
+ }
39625
+ ) })
39626
+ ] }),
39627
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(Section, { title: "Actions", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(ChipRow, { children: [
39628
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Button, { onClick: onCloseIssues, children: [
39629
+ "close ",
39630
+ total,
39631
+ " tickets"
39632
+ ] }),
39633
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(Button, { onClick: onReopenIssues, children: [
39634
+ "reopen ",
39635
+ total,
39636
+ " tickets"
39637
+ ] })
39638
+ ] }) })
39639
+ ] });
39640
+ };
39641
+
39449
39642
  // source/gui/client/components/SwimlaneColumn.tsx
39643
+ var import_react12 = __toESM(require_react(), 1);
39644
+
39645
+ // source/gui/client/components/TicketCard.tsx
39450
39646
  var import_react11 = __toESM(require_react(), 1);
39451
39647
 
39452
39648
  // source/gui/client/components/IconComment.tsx
39453
- var import_jsx_runtime21 = __toESM(require_jsx_runtime(), 1);
39454
- var IconComment = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
39649
+ var import_jsx_runtime22 = __toESM(require_jsx_runtime(), 1);
39650
+ var IconComment = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39455
39651
  "svg",
39456
39652
  {
39457
39653
  width: size,
@@ -39459,7 +39655,7 @@ var IconComment = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime21.js
39459
39655
  viewBox: "0 0 16 16",
39460
39656
  fill: "none",
39461
39657
  "aria-hidden": "true",
39462
- children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
39658
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39463
39659
  "path",
39464
39660
  {
39465
39661
  d: "M3.9 2.6h5.2a2.4 2.4 0 0 1 2.4 2.4v2.5a2.4 2.4 0 0 1-2.4 2.4H6.6l-1.8 2.1.2-2.1H3.9a2.4 2.4 0 0 1-2.4-2.4V5a2.4 2.4 0 0 1 2.4-2.4Z",
@@ -39474,28 +39670,40 @@ var IconComment = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime21.js
39474
39670
  );
39475
39671
 
39476
39672
  // source/gui/client/components/TicketCard.tsx
39477
- var import_jsx_runtime22 = __toESM(require_jsx_runtime(), 1);
39673
+ var import_jsx_runtime23 = __toESM(require_jsx_runtime(), 1);
39478
39674
  var TicketCard = ({
39479
39675
  ticket,
39480
39676
  index: index2,
39481
39677
  isSelected,
39678
+ isPicked,
39482
39679
  commentCount,
39483
39680
  onOpenComments,
39484
39681
  onSelect,
39485
- onDragStart,
39486
39682
  onDragOverIssue,
39487
39683
  onDropIssueAt
39488
39684
  }) => {
39685
+ const cardRef = (0, import_react11.useRef)(null);
39686
+ (0, import_react11.useEffect)(() => {
39687
+ if (!isSelected) return;
39688
+ cardRef.current?.scrollIntoView({
39689
+ behavior: "smooth",
39690
+ block: "nearest",
39691
+ inline: "nearest"
39692
+ });
39693
+ }, [isSelected]);
39489
39694
  const getVisualTargetIndex = (isAfterMiddle) => index2 + (isAfterMiddle ? 1 : 0);
39490
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
39695
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
39491
39696
  "div",
39492
39697
  {
39698
+ ref: cardRef,
39493
39699
  draggable: !ticket.readonly,
39494
- onClick: onSelect,
39700
+ onClick: (event) => {
39701
+ event.stopPropagation();
39702
+ onSelect({ toggle: event.metaKey || event.ctrlKey || event.shiftKey });
39703
+ },
39495
39704
  onDragStart: (event) => {
39496
39705
  event.dataTransfer.effectAllowed = "move";
39497
39706
  event.dataTransfer.setData("text/plain", ticket.id);
39498
- onDragStart(ticket.id);
39499
39707
  },
39500
39708
  onDragOver: (event) => {
39501
39709
  event.preventDefault();
@@ -39517,18 +39725,22 @@ var TicketCard = ({
39517
39725
  display: "flex",
39518
39726
  alignItems: "flex-start",
39519
39727
  gap: 10,
39520
- color: isSelected ? GUI_THEME.accent : GUI_THEME.primary,
39728
+ color: isSelected || isPicked ? GUI_THEME.accent : GUI_THEME.primary,
39521
39729
  fontSize: 11,
39522
39730
  cursor: ticket.readonly ? "default" : "grab",
39523
- background: isSelected ? "rgba(118,228,255,0.08)" : "rgba(185, 192, 255, 0.06)",
39731
+ background: isSelected || isPicked ? "rgba(118,228,255,0.08)" : "rgba(185, 192, 255, 0.06)",
39524
39732
  padding: "10px 8px",
39525
39733
  minHeight: "58px",
39526
39734
  borderRadius: "8px",
39527
39735
  marginBottom: 4,
39528
- border: `1px solid ${isSelected ? GUI_THEME.accent : "transparent"}`
39736
+ border: `1px solid ${isSelected || isPicked ? GUI_THEME.accent : "transparent"}`,
39737
+ // Only the multi-selection is outlined, so it stays legible when the
39738
+ // details panel is closed.
39739
+ outline: isPicked ? `1px solid ${GUI_THEME.accent}` : void 0,
39740
+ outlineOffset: 1
39529
39741
  },
39530
39742
  children: [
39531
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39743
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
39532
39744
  "div",
39533
39745
  {
39534
39746
  style: {
@@ -39542,7 +39754,7 @@ var TicketCard = ({
39542
39754
  children: isSelected ? "\u276F" : index2 + 1
39543
39755
  }
39544
39756
  ),
39545
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
39757
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
39546
39758
  "div",
39547
39759
  {
39548
39760
  style: {
@@ -39554,7 +39766,7 @@ var TicketCard = ({
39554
39766
  paddingLeft: 8
39555
39767
  },
39556
39768
  children: [
39557
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
39769
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
39558
39770
  "div",
39559
39771
  {
39560
39772
  style: {
@@ -39565,7 +39777,7 @@ var TicketCard = ({
39565
39777
  flex: 1
39566
39778
  },
39567
39779
  children: [
39568
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39780
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
39569
39781
  "div",
39570
39782
  {
39571
39783
  style: {
@@ -39581,17 +39793,17 @@ var TicketCard = ({
39581
39793
  children: ticket.title
39582
39794
  }
39583
39795
  ),
39584
- ticket.ref && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39796
+ ticket.ref && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
39585
39797
  "div",
39586
39798
  {
39587
39799
  style: {
39588
39800
  color: GUI_THEME.dim2,
39589
39801
  fontSize: 10
39590
39802
  },
39591
- children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(CopyRef, { refValue: ticket.ref })
39803
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(CopyRef, { refValue: ticket.ref })
39592
39804
  }
39593
39805
  ),
39594
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39806
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
39595
39807
  "div",
39596
39808
  {
39597
39809
  style: {
@@ -39600,7 +39812,7 @@ var TicketCard = ({
39600
39812
  gap: 6,
39601
39813
  alignItems: "center"
39602
39814
  },
39603
- children: ticket.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39815
+ children: ticket.tags.map((tag) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
39604
39816
  "span",
39605
39817
  {
39606
39818
  style: {
@@ -39620,7 +39832,7 @@ var TicketCard = ({
39620
39832
  ]
39621
39833
  }
39622
39834
  ),
39623
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39835
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
39624
39836
  "div",
39625
39837
  {
39626
39838
  style: {
@@ -39630,7 +39842,7 @@ var TicketCard = ({
39630
39842
  flexShrink: 0,
39631
39843
  paddingTop: 2
39632
39844
  },
39633
- children: commentCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
39845
+ children: commentCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
39634
39846
  "button",
39635
39847
  {
39636
39848
  type: "button",
@@ -39668,14 +39880,14 @@ var TicketCard = ({
39668
39880
  event.currentTarget.style.transform = "translateY(0)";
39669
39881
  },
39670
39882
  children: [
39671
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(IconComment, {}),
39672
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { children: commentCount })
39883
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(IconComment, {}),
39884
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { children: commentCount })
39673
39885
  ]
39674
39886
  }
39675
39887
  )
39676
39888
  }
39677
39889
  ),
39678
- ticket.assignees.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
39890
+ ticket.assignees.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
39679
39891
  "div",
39680
39892
  {
39681
39893
  style: {
@@ -39685,7 +39897,7 @@ var TicketCard = ({
39685
39897
  paddingTop: 2,
39686
39898
  marginTop: "-4px"
39687
39899
  },
39688
- children: ticket.assignees.map((assignee, idx) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(User, { user: assignee, index: idx, isFocus: isSelected }))
39900
+ children: ticket.assignees.map((assignee, idx) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(User, { user: assignee, index: idx, isFocus: isSelected }))
39689
39901
  }
39690
39902
  )
39691
39903
  ]
@@ -39697,7 +39909,7 @@ var TicketCard = ({
39697
39909
  };
39698
39910
 
39699
39911
  // source/gui/client/components/SwimlaneColumn.tsx
39700
- var import_jsx_runtime23 = __toESM(require_jsx_runtime(), 1);
39912
+ var import_jsx_runtime24 = __toESM(require_jsx_runtime(), 1);
39701
39913
  var COLUMN_GLOW_COLOR = "rgb(140, 176, 232)";
39702
39914
  var COLUMN_PADDING = 14;
39703
39915
  var SCROLLBAR_GUTTER_INSET = COLUMN_PADDING / 2;
@@ -39710,13 +39922,14 @@ var SwimlaneColumn = ({
39710
39922
  dragOver,
39711
39923
  dropIndex,
39712
39924
  onSelectIssue,
39925
+ pickedIssueIds,
39713
39926
  onCreateIssue,
39714
39927
  onDropIssue,
39715
39928
  onDragOver,
39716
39929
  onDragOverIssue,
39717
39930
  onDragLeave
39718
39931
  }) => {
39719
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
39932
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
39720
39933
  Panel,
39721
39934
  {
39722
39935
  as: "section",
@@ -39763,7 +39976,7 @@ var SwimlaneColumn = ({
39763
39976
  onDragLeave();
39764
39977
  },
39765
39978
  children: [
39766
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
39979
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
39767
39980
  "header",
39768
39981
  {
39769
39982
  style: {
@@ -39775,7 +39988,7 @@ var SwimlaneColumn = ({
39775
39988
  alignItems: "center"
39776
39989
  },
39777
39990
  children: [
39778
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
39991
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
39779
39992
  "div",
39780
39993
  {
39781
39994
  style: {
@@ -39784,37 +39997,37 @@ var SwimlaneColumn = ({
39784
39997
  gap: 8
39785
39998
  },
39786
39999
  children: [
39787
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
40000
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
39788
40001
  "span",
39789
40002
  {
39790
40003
  style: { color: selected ? GUI_THEME.accent : GUI_THEME.secondary },
39791
40004
  children: selected ? "\u276F" : " "
39792
40005
  }
39793
40006
  ),
39794
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
40007
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
39795
40008
  "strong",
39796
40009
  {
39797
40010
  style: { color: selected ? GUI_THEME.accent : GUI_THEME.primary },
39798
40011
  children: swimlane.title
39799
40012
  }
39800
40013
  ),
39801
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("span", { style: { color: GUI_THEME.dim }, children: [
40014
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { style: { color: GUI_THEME.dim }, children: [
39802
40015
  "(",
39803
40016
  swimlane.issues.length,
39804
40017
  ")"
39805
40018
  ] }),
39806
- swimlane.readonly && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
40019
+ swimlane.readonly && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
39807
40020
  "span",
39808
40021
  {
39809
40022
  title: "Read-only",
39810
40023
  style: { display: "flex", color: GUI_THEME.dim },
39811
- children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(IconLock, {})
40024
+ children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(IconLock, {})
39812
40025
  }
39813
40026
  )
39814
40027
  ]
39815
40028
  }
39816
40029
  ),
39817
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
40030
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
39818
40031
  Button,
39819
40032
  {
39820
40033
  variant: "ghost",
@@ -39827,7 +40040,7 @@ var SwimlaneColumn = ({
39827
40040
  ]
39828
40041
  }
39829
40042
  ),
39830
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
40043
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
39831
40044
  "div",
39832
40045
  {
39833
40046
  style: {
@@ -39838,9 +40051,9 @@ var SwimlaneColumn = ({
39838
40051
  flex: 1,
39839
40052
  minHeight: 0
39840
40053
  },
39841
- children: swimlane.issues.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
39842
- dropIndex === 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DropIndicator, {}),
39843
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
40054
+ children: swimlane.issues.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
40055
+ dropIndex === 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DropIndicator, {}),
40056
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
39844
40057
  "div",
39845
40058
  {
39846
40059
  style: {
@@ -39852,17 +40065,17 @@ var SwimlaneColumn = ({
39852
40065
  children: "Drop issue here"
39853
40066
  }
39854
40067
  )
39855
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
39856
- swimlane.issues.map((ticket, index2) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_react11.default.Fragment, { children: [
39857
- dropIndex === index2 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DropIndicator, {}),
39858
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
40068
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
40069
+ swimlane.issues.map((ticket, index2) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_react12.default.Fragment, { children: [
40070
+ dropIndex === index2 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DropIndicator, {}),
40071
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
39859
40072
  TicketCard,
39860
40073
  {
39861
40074
  ticket,
39862
40075
  index: index2,
39863
40076
  isSelected: ticket.id === selectedIssueId,
39864
- onSelect: () => onSelectIssue(ticket.id),
39865
- onDragStart: (issueId) => onSelectIssue(issueId),
40077
+ isPicked: pickedIssueIds.includes(ticket.id),
40078
+ onSelect: (options) => onSelectIssue(ticket.id, options),
39866
40079
  onOpenComments: onSelectIssueComments,
39867
40080
  commentCount: commentsByIssueId[ticket.id]?.length ?? 0,
39868
40081
  onDragOverIssue: (targetIndex) => onDragOverIssue(swimlane.id, targetIndex),
@@ -39873,7 +40086,7 @@ var SwimlaneColumn = ({
39873
40086
  }
39874
40087
  )
39875
40088
  ] }, ticket.id)),
39876
- dropIndex === swimlane.issues.length && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(DropIndicator, {})
40089
+ dropIndex === swimlane.issues.length && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(DropIndicator, {})
39877
40090
  ] })
39878
40091
  }
39879
40092
  )
@@ -39883,9 +40096,9 @@ var SwimlaneColumn = ({
39883
40096
  };
39884
40097
 
39885
40098
  // source/gui/client/components/GlobalScrollbarStyles.tsx
39886
- var import_jsx_runtime24 = __toESM(require_jsx_runtime(), 1);
40099
+ var import_jsx_runtime25 = __toESM(require_jsx_runtime(), 1);
39887
40100
  var SCROLLBAR_SIZE = 3;
39888
- var GlobalScrollbarStyles = () => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("style", { children: `
40101
+ var GlobalScrollbarStyles = () => /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("style", { children: `
39889
40102
  /* Gated deliberately: Chrome implements scrollbar-width/-color too, and
39890
40103
  setting either makes it ignore the ::-webkit-scrollbar rules below. */
39891
40104
  @supports not selector(::-webkit-scrollbar) {
@@ -39926,8 +40139,8 @@ var GlobalScrollbarStyles = () => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
39926
40139
  ` });
39927
40140
 
39928
40141
  // source/gui/client/components/ErrorToast.tsx
39929
- var import_jsx_runtime25 = __toESM(require_jsx_runtime(), 1);
39930
- var ErrorToast = ({ message, onDismiss }) => /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)(
40142
+ var import_jsx_runtime26 = __toESM(require_jsx_runtime(), 1);
40143
+ var ErrorToast = ({ message, onDismiss }) => /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
39931
40144
  "div",
39932
40145
  {
39933
40146
  style: {
@@ -39948,8 +40161,8 @@ var ErrorToast = ({ message, onDismiss }) => /* @__PURE__ */ (0, import_jsx_runt
39948
40161
  boxShadow: "0 4px 16px rgba(0, 0, 0, 0.4)"
39949
40162
  },
39950
40163
  children: [
39951
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("span", { style: { flex: 1, minWidth: 0, overflowWrap: "anywhere" }, children: message }),
39952
- /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(
40164
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)("span", { style: { flex: 1, minWidth: 0, overflowWrap: "anywhere" }, children: message }),
40165
+ /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
39953
40166
  "button",
39954
40167
  {
39955
40168
  onClick: onDismiss,
@@ -39970,7 +40183,7 @@ var ErrorToast = ({ message, onDismiss }) => /* @__PURE__ */ (0, import_jsx_runt
39970
40183
  );
39971
40184
 
39972
40185
  // source/gui/client/components/TimeScrubber.tsx
39973
- var import_react14 = __toESM(require_react(), 1);
40186
+ var import_react15 = __toESM(require_react(), 1);
39974
40187
 
39975
40188
  // source/lib/utils/date.utils.ts
39976
40189
  var pad = (n) => String(n).padStart(2, "0");
@@ -39983,7 +40196,7 @@ var minOf = (values, seed) => values.reduce((min, value) => value < min ? value
39983
40196
  var maxOf = (values, seed) => values.reduce((max, value) => value > max ? value : max, seed);
39984
40197
 
39985
40198
  // source/gui/client/lib/scrubber.ts
39986
- var import_react12 = __toESM(require_react(), 1);
40199
+ var import_react13 = __toESM(require_react(), 1);
39987
40200
  var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
39988
40201
  var TRACK_HEIGHT = 24;
39989
40202
  var EVENTS_MODE_TOTAL_HEIGHT = 8 + TRACK_HEIGHT * 2;
@@ -40155,6 +40368,7 @@ var barGrowAnimation = (index2, firstIndex, lastIndex) => {
40155
40368
  0
40156
40369
  )}ms backwards`;
40157
40370
  };
40371
+ var BAR_ENTRANCE_TOTAL_MS = BAR_GROW_MS + BAR_GROW_SWEEP_MS;
40158
40372
  var FADE_IN_ANIMATION = "epiqScrubberFadeIn 320ms ease-out";
40159
40373
  var SCRUBBER_KEYFRAMES = `
40160
40374
  /* Must animate the standalone 'scale' property, not 'transform': the dots
@@ -40185,10 +40399,10 @@ var SCRUBBER_KEYFRAMES = `
40185
40399
  `;
40186
40400
  var REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
40187
40401
  var usePrefersReducedMotion = () => {
40188
- const [reduced, setReduced] = (0, import_react12.useState)(
40402
+ const [reduced, setReduced] = (0, import_react13.useState)(
40189
40403
  () => window.matchMedia(REDUCED_MOTION_QUERY).matches
40190
40404
  );
40191
- (0, import_react12.useEffect)(() => {
40405
+ (0, import_react13.useEffect)(() => {
40192
40406
  const query = window.matchMedia(REDUCED_MOTION_QUERY);
40193
40407
  const onChange = () => setReduced(query.matches);
40194
40408
  query.addEventListener("change", onChange);
@@ -40197,12 +40411,12 @@ var usePrefersReducedMotion = () => {
40197
40411
  return reduced;
40198
40412
  };
40199
40413
  var useExitTransition = (visible, durationMs) => {
40200
- const [mounted, setMounted] = (0, import_react12.useState)(visible);
40201
- const [leaving, setLeaving] = (0, import_react12.useState)(false);
40202
- const wasVisible = (0, import_react12.useRef)(visible);
40203
- const duration = (0, import_react12.useRef)(durationMs);
40414
+ const [mounted, setMounted] = (0, import_react13.useState)(visible);
40415
+ const [leaving, setLeaving] = (0, import_react13.useState)(false);
40416
+ const wasVisible = (0, import_react13.useRef)(visible);
40417
+ const duration = (0, import_react13.useRef)(durationMs);
40204
40418
  duration.current = durationMs;
40205
- (0, import_react12.useEffect)(() => {
40419
+ (0, import_react13.useEffect)(() => {
40206
40420
  if (visible === wasVisible.current) return;
40207
40421
  wasVisible.current = visible;
40208
40422
  if (visible) {
@@ -40224,7 +40438,7 @@ var useExitTransition = (visible, durationMs) => {
40224
40438
  return { mounted, leaving };
40225
40439
  };
40226
40440
  var usePersistedFlag = (key, fallback) => {
40227
- const [value, setValue] = (0, import_react12.useState)(() => {
40441
+ const [value, setValue] = (0, import_react13.useState)(() => {
40228
40442
  const stored = localStorage.getItem(key);
40229
40443
  return stored === null ? fallback : stored === "true";
40230
40444
  });
@@ -40238,10 +40452,10 @@ var usePersistedFlag = (key, fallback) => {
40238
40452
  };
40239
40453
 
40240
40454
  // source/gui/client/components/ScrubberParts.tsx
40241
- var import_react13 = __toESM(require_react(), 1);
40455
+ var import_react14 = __toESM(require_react(), 1);
40242
40456
 
40243
40457
  // source/gui/client/components/Checkbox.tsx
40244
- var import_jsx_runtime26 = __toESM(require_jsx_runtime(), 1);
40458
+ var import_jsx_runtime27 = __toESM(require_jsx_runtime(), 1);
40245
40459
  var Checkbox = ({
40246
40460
  label,
40247
40461
  checked,
@@ -40251,7 +40465,7 @@ var Checkbox = ({
40251
40465
  title
40252
40466
  }) => {
40253
40467
  const color2 = checked ? activeColor : GUI_THEME.dim;
40254
- return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
40468
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
40255
40469
  "label",
40256
40470
  {
40257
40471
  title,
@@ -40265,7 +40479,7 @@ var Checkbox = ({
40265
40479
  opacity: disabled ? 0.4 : 1
40266
40480
  },
40267
40481
  children: [
40268
- /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
40482
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsxs)(
40269
40483
  "span",
40270
40484
  {
40271
40485
  style: {
@@ -40282,7 +40496,7 @@ var Checkbox = ({
40282
40496
  flexShrink: 0
40283
40497
  },
40284
40498
  children: [
40285
- /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
40499
+ /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
40286
40500
  "input",
40287
40501
  {
40288
40502
  type: "checkbox",
@@ -40300,7 +40514,7 @@ var Checkbox = ({
40300
40514
  }
40301
40515
  }
40302
40516
  ),
40303
- checked && /* @__PURE__ */ (0, import_jsx_runtime26.jsx)(
40517
+ checked && /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
40304
40518
  "span",
40305
40519
  {
40306
40520
  style: {
@@ -40321,8 +40535,8 @@ var Checkbox = ({
40321
40535
  };
40322
40536
 
40323
40537
  // source/gui/client/components/IconChevronDown.tsx
40324
- var import_jsx_runtime27 = __toESM(require_jsx_runtime(), 1);
40325
- var IconChevronDown = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(
40538
+ var import_jsx_runtime28 = __toESM(require_jsx_runtime(), 1);
40539
+ var IconChevronDown = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
40326
40540
  "svg",
40327
40541
  {
40328
40542
  width: size,
@@ -40334,13 +40548,13 @@ var IconChevronDown = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime2
40334
40548
  strokeLinecap: "round",
40335
40549
  strokeLinejoin: "round",
40336
40550
  "aria-hidden": "true",
40337
- children: /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("polyline", { points: "6 9 12 15 18 9" })
40551
+ children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("polyline", { points: "6 9 12 15 18 9" })
40338
40552
  }
40339
40553
  );
40340
40554
 
40341
40555
  // source/gui/client/components/IconChevronRight.tsx
40342
- var import_jsx_runtime28 = __toESM(require_jsx_runtime(), 1);
40343
- var IconChevronRight = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
40556
+ var import_jsx_runtime29 = __toESM(require_jsx_runtime(), 1);
40557
+ var IconChevronRight = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40344
40558
  "svg",
40345
40559
  {
40346
40560
  width: size,
@@ -40352,12 +40566,12 @@ var IconChevronRight = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime
40352
40566
  strokeLinecap: "round",
40353
40567
  strokeLinejoin: "round",
40354
40568
  "aria-hidden": "true",
40355
- children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("polyline", { points: "9 18 15 12 9 6" })
40569
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("polyline", { points: "9 18 15 12 9 6" })
40356
40570
  }
40357
40571
  );
40358
40572
 
40359
40573
  // source/gui/client/components/ScrubberParts.tsx
40360
- var import_jsx_runtime29 = __toESM(require_jsx_runtime(), 1);
40574
+ var import_jsx_runtime30 = __toESM(require_jsx_runtime(), 1);
40361
40575
  var toggleButtonStyle = (active) => ({
40362
40576
  background: "transparent",
40363
40577
  border: `1px solid ${active ? GUI_THEME.accent : GUI_THEME.dim}`,
@@ -40394,10 +40608,10 @@ var ScrubberControls = ({
40394
40608
  onChangeShowCommits,
40395
40609
  onChangeAllBoards,
40396
40610
  onReturnToLive
40397
- }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
40398
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
40399
- scope !== "all" && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 2 }, children: [
40400
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40611
+ }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
40612
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
40613
+ scope !== "all" && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 2 }, children: [
40614
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40401
40615
  "button",
40402
40616
  {
40403
40617
  title: "Earlier",
@@ -40406,7 +40620,7 @@ var ScrubberControls = ({
40406
40620
  children: "\u25C0"
40407
40621
  }
40408
40622
  ),
40409
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40623
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40410
40624
  "span",
40411
40625
  {
40412
40626
  style: {
@@ -40423,7 +40637,7 @@ var ScrubberControls = ({
40423
40637
  children: formatPeriodLabel(scope, offset, periodRange)
40424
40638
  }
40425
40639
  ),
40426
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40640
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40427
40641
  "button",
40428
40642
  {
40429
40643
  title: "Later",
@@ -40438,7 +40652,7 @@ var ScrubberControls = ({
40438
40652
  }
40439
40653
  )
40440
40654
  ] }),
40441
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { style: { display: "flex", gap: 2 }, children: SCOPES.map((option) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40655
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { style: { display: "flex", gap: 2 }, children: SCOPES.map((option) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40442
40656
  "button",
40443
40657
  {
40444
40658
  onClick: () => onChangeScope(option),
@@ -40448,8 +40662,8 @@ var ScrubberControls = ({
40448
40662
  option
40449
40663
  )) })
40450
40664
  ] }),
40451
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { style: { display: "flex", gap: 2 }, children: [
40452
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40665
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { style: { display: "flex", gap: 2 }, children: [
40666
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40453
40667
  "button",
40454
40668
  {
40455
40669
  title: "How much happened, per equal-width period \u2014 no empty gaps for quiet stretches",
@@ -40458,7 +40672,7 @@ var ScrubberControls = ({
40458
40672
  children: "Volume"
40459
40673
  }
40460
40674
  ),
40461
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40675
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40462
40676
  "button",
40463
40677
  {
40464
40678
  title: "Individual events by exact moment \u2014 x is elapsed time, y is time of day",
@@ -40468,8 +40682,8 @@ var ScrubberControls = ({
40468
40682
  }
40469
40683
  )
40470
40684
  ] }),
40471
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { style: { display: "flex", gap: 10 }, children: [
40472
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40685
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { style: { display: "flex", gap: 10 }, children: [
40686
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40473
40687
  Checkbox,
40474
40688
  {
40475
40689
  label: "Board",
@@ -40478,7 +40692,7 @@ var ScrubberControls = ({
40478
40692
  onChange: onChangeShowIssues
40479
40693
  }
40480
40694
  ),
40481
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40695
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40482
40696
  Checkbox,
40483
40697
  {
40484
40698
  label: "Code",
@@ -40487,7 +40701,7 @@ var ScrubberControls = ({
40487
40701
  onChange: onChangeShowCommits
40488
40702
  }
40489
40703
  ),
40490
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40704
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40491
40705
  Checkbox,
40492
40706
  {
40493
40707
  label: "All boards",
@@ -40497,7 +40711,7 @@ var ScrubberControls = ({
40497
40711
  }
40498
40712
  )
40499
40713
  ] }),
40500
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40714
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40501
40715
  "div",
40502
40716
  {
40503
40717
  style: {
@@ -40509,7 +40723,7 @@ var ScrubberControls = ({
40509
40723
  width: 100,
40510
40724
  flexShrink: 0
40511
40725
  },
40512
- children: isScrubbing ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40726
+ children: isScrubbing ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40513
40727
  "button",
40514
40728
  {
40515
40729
  onClick: onReturnToLive,
@@ -40525,7 +40739,7 @@ var ScrubberControls = ({
40525
40739
  },
40526
40740
  children: "Return to live"
40527
40741
  }
40528
- ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40742
+ ) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40529
40743
  "span",
40530
40744
  {
40531
40745
  style: {
@@ -40545,7 +40759,7 @@ var ScrubberHeader = ({
40545
40759
  collapsed,
40546
40760
  onToggleCollapsed,
40547
40761
  scrubbingAsOf
40548
- }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
40762
+ }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
40549
40763
  "div",
40550
40764
  {
40551
40765
  style: {
@@ -40556,7 +40770,7 @@ var ScrubberHeader = ({
40556
40770
  whiteSpace: "nowrap"
40557
40771
  },
40558
40772
  children: [
40559
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
40773
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
40560
40774
  "button",
40561
40775
  {
40562
40776
  onClick: onToggleCollapsed,
@@ -40574,13 +40788,13 @@ var ScrubberHeader = ({
40574
40788
  },
40575
40789
  children: [
40576
40790
  "Time travel",
40577
- collapsed ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(IconChevronRight, { size: 12 }) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(IconChevronDown, { size: 12 })
40791
+ collapsed ? /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(IconChevronRight, { size: 12 }) : /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(IconChevronDown, { size: 12 })
40578
40792
  ]
40579
40793
  }
40580
40794
  ),
40581
- scrubbingAsOf !== null && /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
40582
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { style: { color: GUI_THEME.accent, fontWeight: 700 }, children: "Read-only" }),
40583
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { style: { color: GUI_THEME.primary }, children: scrubbingAsOf })
40795
+ scrubbingAsOf !== null && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
40796
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { style: { color: GUI_THEME.accent, fontWeight: 700 }, children: "Read-only" }),
40797
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("span", { style: { color: GUI_THEME.primary }, children: scrubbingAsOf })
40584
40798
  ] })
40585
40799
  ]
40586
40800
  }
@@ -40590,7 +40804,7 @@ var SegmentHighlight = ({
40590
40804
  fractionForTime
40591
40805
  }) => {
40592
40806
  const left = fractionForTime(segment.start);
40593
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40807
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40594
40808
  "div",
40595
40809
  {
40596
40810
  style: {
@@ -40622,7 +40836,7 @@ var SegmentHighlight = ({
40622
40836
  var TrackBaseline = ({
40623
40837
  color: color2,
40624
40838
  anchor
40625
- }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40839
+ }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40626
40840
  "div",
40627
40841
  {
40628
40842
  style: {
@@ -40638,11 +40852,11 @@ var TrackBaseline = ({
40638
40852
  }
40639
40853
  }
40640
40854
  );
40641
- var HourAxisLabels = () => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_jsx_runtime29.Fragment, { children: [
40855
+ var HourAxisLabels = () => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(import_jsx_runtime30.Fragment, { children: [
40642
40856
  ["00:00", { top: EVENTS_MODE_VERTICAL_PADDING }],
40643
40857
  ["12:00", { top: "50%", transform: "translateY(-50%)" }],
40644
40858
  ["24:00", { bottom: EVENTS_MODE_VERTICAL_PADDING }]
40645
- ].map(([label, position3]) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40859
+ ].map(([label, position3]) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40646
40860
  "span",
40647
40861
  {
40648
40862
  style: {
@@ -40660,7 +40874,7 @@ var HourAxisLabels = () => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_
40660
40874
  var SeriesLayer = ({
40661
40875
  animate,
40662
40876
  children
40663
- }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40877
+ }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40664
40878
  "div",
40665
40879
  {
40666
40880
  style: {
@@ -40685,8 +40899,13 @@ var VolumeBars = ({
40685
40899
  const widthPercent = 100 / bucketCount;
40686
40900
  const barWidth = barWidthCss(bucketCount);
40687
40901
  const growsUp = direction === "up";
40688
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
40689
- highlightedIndex !== null && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40902
+ const [fresh, setFresh] = (0, import_react14.useState)(true);
40903
+ (0, import_react14.useEffect)(() => {
40904
+ const timeout = setTimeout(() => setFresh(false), BAR_ENTRANCE_TOTAL_MS);
40905
+ return () => clearTimeout(timeout);
40906
+ }, []);
40907
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
40908
+ highlightedIndex !== null && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40690
40909
  "div",
40691
40910
  {
40692
40911
  style: {
@@ -40702,7 +40921,7 @@ var VolumeBars = ({
40702
40921
  }
40703
40922
  }
40704
40923
  ),
40705
- bars.map(({ index: index2, intensity }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40924
+ bars.map(({ index: index2, intensity }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40706
40925
  "div",
40707
40926
  {
40708
40927
  style: {
@@ -40715,7 +40934,7 @@ var VolumeBars = ({
40715
40934
  background: color2,
40716
40935
  opacity: 0.35 + intensity * 0.65,
40717
40936
  transformOrigin: growsUp ? "bottom" : "top",
40718
- animation: animate ? barGrowAnimation(index2, firstBar, lastBar) : void 0,
40937
+ animation: animate && fresh ? barGrowAnimation(index2, firstBar, lastBar) : void 0,
40719
40938
  pointerEvents: "none"
40720
40939
  }
40721
40940
  },
@@ -40736,7 +40955,7 @@ var ScatterDot = ({
40736
40955
  onMouseEnter,
40737
40956
  onMouseLeave,
40738
40957
  onClick
40739
- }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40958
+ }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40740
40959
  "div",
40741
40960
  {
40742
40961
  title,
@@ -40765,13 +40984,13 @@ var ScatterDot = ({
40765
40984
  }
40766
40985
  );
40767
40986
  var ScrubberNeedle = ({ fraction }) => {
40768
- const [hovered, setHovered] = (0, import_react13.useState)(false);
40987
+ const [hovered, setHovered] = (0, import_react14.useState)(false);
40769
40988
  const hover = {
40770
40989
  onMouseEnter: () => setHovered(true),
40771
40990
  onMouseLeave: () => setHovered(false)
40772
40991
  };
40773
- return /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(import_jsx_runtime29.Fragment, { children: [
40774
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
40992
+ return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(import_jsx_runtime30.Fragment, { children: [
40993
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40775
40994
  "div",
40776
40995
  {
40777
40996
  ...hover,
@@ -40791,7 +41010,7 @@ var ScrubberNeedle = ({ fraction }) => {
40791
41010
  }
40792
41011
  }
40793
41012
  ),
40794
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
41013
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40795
41014
  "div",
40796
41015
  {
40797
41016
  ...hover,
@@ -40821,7 +41040,7 @@ var ScrubberHoverHint = ({
40821
41040
  fraction,
40822
41041
  trackWidthPx,
40823
41042
  empty: empty3 = false
40824
- }) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)(
41043
+ }) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
40825
41044
  "div",
40826
41045
  {
40827
41046
  style: {
@@ -40853,8 +41072,8 @@ var ScrubberHoverHint = ({
40853
41072
  zIndex: 5
40854
41073
  },
40855
41074
  children: [
40856
- segmentLabel && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { style: { fontSize: 10, color: GUI_THEME.dim }, children: segmentLabel }),
40857
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
41075
+ segmentLabel && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { style: { fontSize: 10, color: GUI_THEME.dim }, children: segmentLabel }),
41076
+ /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40858
41077
  "div",
40859
41078
  {
40860
41079
  style: {
@@ -40867,7 +41086,7 @@ var ScrubberHoverHint = ({
40867
41086
  children: label
40868
41087
  }
40869
41088
  ),
40870
- rows.map((row, index2) => /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
41089
+ rows.map((row, index2) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
40871
41090
  "div",
40872
41091
  {
40873
41092
  style: {
@@ -40885,7 +41104,7 @@ var ScrubberHoverHint = ({
40885
41104
  );
40886
41105
 
40887
41106
  // source/gui/client/components/ScrubberLayout.tsx
40888
- var import_jsx_runtime30 = __toESM(require_jsx_runtime(), 1);
41107
+ var import_jsx_runtime31 = __toESM(require_jsx_runtime(), 1);
40889
41108
  var dotAnimation = (key, animate, leaving) => !animate ? void 0 : leaving ? dotExitAnimation(key) : dotAppearAnimation(key);
40890
41109
  var ScrubberLayout = ({
40891
41110
  collapsed,
@@ -40895,7 +41114,7 @@ var ScrubberLayout = ({
40895
41114
  chart
40896
41115
  }) => {
40897
41116
  const { axis, layoutMode, animate, windowKey, on } = chart;
40898
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
41117
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
40899
41118
  Panel,
40900
41119
  {
40901
41120
  as: "div",
@@ -40912,8 +41131,8 @@ var ScrubberLayout = ({
40912
41131
  overflow: "visible"
40913
41132
  },
40914
41133
  children: [
40915
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("style", { children: SCRUBBER_KEYFRAMES }),
40916
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
41134
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("style", { children: SCRUBBER_KEYFRAMES }),
41135
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
40917
41136
  "div",
40918
41137
  {
40919
41138
  style: {
@@ -40922,7 +41141,7 @@ var ScrubberLayout = ({
40922
41141
  gap: collapsed ? 0 : 8
40923
41142
  },
40924
41143
  children: [
40925
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
41144
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
40926
41145
  "div",
40927
41146
  {
40928
41147
  style: {
@@ -40934,7 +41153,7 @@ var ScrubberLayout = ({
40934
41153
  minHeight: 22
40935
41154
  },
40936
41155
  children: [
40937
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41156
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
40938
41157
  ScrubberHeader,
40939
41158
  {
40940
41159
  collapsed,
@@ -40942,7 +41161,7 @@ var ScrubberLayout = ({
40942
41161
  scrubbingAsOf
40943
41162
  }
40944
41163
  ),
40945
- !collapsed && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ScrubberControls, { ...controls })
41164
+ !collapsed && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ScrubberControls, { ...controls })
40946
41165
  ]
40947
41166
  }
40948
41167
  ),
@@ -40950,7 +41169,7 @@ var ScrubberLayout = ({
40950
41169
  // spanning them and the gap between. Pointer handlers belong here
40951
41170
  // rather than on either chart, so a drag or hover anywhere across
40952
41171
  // the pair — the gap included — counts as one timeline.
40953
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
41172
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
40954
41173
  "div",
40955
41174
  {
40956
41175
  onPointerDown: on.onPointerDown,
@@ -40967,14 +41186,14 @@ var ScrubberLayout = ({
40967
41186
  cursor: "pointer"
40968
41187
  },
40969
41188
  children: [
40970
- chart.hoveredSegment && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41189
+ chart.hoveredSegment && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
40971
41190
  SegmentHighlight,
40972
41191
  {
40973
41192
  segment: chart.hoveredSegment,
40974
41193
  fractionForTime: axis.fractionForTime
40975
41194
  }
40976
41195
  ),
40977
- /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
41196
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
40978
41197
  "div",
40979
41198
  {
40980
41199
  ref: chart.trackRef,
@@ -40989,15 +41208,15 @@ var ScrubberLayout = ({
40989
41208
  alignItems: "center"
40990
41209
  },
40991
41210
  children: [
40992
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41211
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
40993
41212
  TrackBaseline,
40994
41213
  {
40995
41214
  color: GUI_THEME.accent,
40996
41215
  anchor: layoutMode === "even" ? "bottom" : "centre"
40997
41216
  }
40998
41217
  ),
40999
- layoutMode === "real" && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(HourAxisLabels, {}),
41000
- chart.showIssues && layoutMode === "even" && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(SeriesLayer, { animate, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41218
+ layoutMode === "real" && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(HourAxisLabels, {}),
41219
+ chart.showIssues && layoutMode === "even" && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(SeriesLayer, { animate, children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41001
41220
  VolumeBars,
41002
41221
  {
41003
41222
  bars: chart.issueBars,
@@ -41010,13 +41229,13 @@ var ScrubberLayout = ({
41010
41229
  animate
41011
41230
  }
41012
41231
  ) }, `issues-${windowKey}`),
41013
- chart.issueScatter.mounted && layoutMode === "real" && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41232
+ chart.issueScatter.mounted && layoutMode === "real" && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41014
41233
  SeriesLayer,
41015
41234
  {
41016
41235
  animate: animate && !chart.issueScatter.leaving,
41017
41236
  children: chart.eventBuckets.map((bucket) => {
41018
41237
  const intensity = bucket.count / chart.maxEventCount;
41019
- return /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41238
+ return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41020
41239
  ScatterDot,
41021
41240
  {
41022
41241
  fraction: axis.fractionForTime(bucket.t),
@@ -41041,11 +41260,11 @@ var ScrubberLayout = ({
41041
41260
  },
41042
41261
  `issues-${windowKey}`
41043
41262
  ),
41044
- chart.commitScatter.mounted && layoutMode === "real" && chart.commits.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41263
+ chart.commitScatter.mounted && layoutMode === "real" && chart.commits.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41045
41264
  SeriesLayer,
41046
41265
  {
41047
41266
  animate: animate && !chart.commitScatter.leaving,
41048
- children: chart.commits.map((commit) => /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41267
+ children: chart.commits.map((commit) => /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41049
41268
  ScatterDot,
41050
41269
  {
41051
41270
  fraction: axis.fractionForTime(commit.time),
@@ -41073,7 +41292,7 @@ var ScrubberLayout = ({
41073
41292
  ]
41074
41293
  }
41075
41294
  ),
41076
- chart.showCommits && layoutMode === "even" && chart.commits.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)(
41295
+ chart.showCommits && layoutMode === "even" && chart.commits.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
41077
41296
  "div",
41078
41297
  {
41079
41298
  onMouseEnter: on.onCommitTrackMouseEnter,
@@ -41086,8 +41305,8 @@ var ScrubberLayout = ({
41086
41305
  animation: animate ? FADE_IN_ANIMATION : void 0
41087
41306
  },
41088
41307
  children: [
41089
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(TrackBaseline, { color: GUI_THEME.green, anchor: "top" }),
41090
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41308
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(TrackBaseline, { color: GUI_THEME.green, anchor: "top" }),
41309
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41091
41310
  VolumeBars,
41092
41311
  {
41093
41312
  bars: chart.commitBars,
@@ -41104,8 +41323,8 @@ var ScrubberLayout = ({
41104
41323
  },
41105
41324
  `commits-${windowKey}`
41106
41325
  ),
41107
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(ScrubberNeedle, { fraction: chart.thumbFraction }),
41108
- chart.boardHint && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41326
+ /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(ScrubberNeedle, { fraction: chart.thumbFraction }),
41327
+ chart.boardHint && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41109
41328
  ScrubberHoverHint,
41110
41329
  {
41111
41330
  ...chart.boardHint,
@@ -41114,7 +41333,7 @@ var ScrubberLayout = ({
41114
41333
  trackWidthPx: chart.trackWidthPx
41115
41334
  }
41116
41335
  ),
41117
- chart.commitHint && /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
41336
+ chart.commitHint && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41118
41337
  ScrubberHoverHint,
41119
41338
  {
41120
41339
  ...chart.commitHint,
@@ -41135,7 +41354,7 @@ var ScrubberLayout = ({
41135
41354
  };
41136
41355
 
41137
41356
  // source/gui/client/components/TimeScrubber.tsx
41138
- var import_jsx_runtime31 = __toESM(require_jsx_runtime(), 1);
41357
+ var import_jsx_runtime32 = __toESM(require_jsx_runtime(), 1);
41139
41358
  var SCRUB_THROTTLE_MS = 120;
41140
41359
  var COLLAPSED_STORAGE_KEY = "epiq.timeScrubber.collapsed";
41141
41360
  var SCOPE_STORAGE_KEY = "epiq.timeScrubber.scope";
@@ -41149,6 +41368,7 @@ var readStoredScope = () => {
41149
41368
  var TimeScrubber = ({
41150
41369
  timeline,
41151
41370
  commits,
41371
+ historyId,
41152
41372
  timeTravel,
41153
41373
  onScrub,
41154
41374
  onReturnToLive,
@@ -41158,12 +41378,12 @@ var TimeScrubber = ({
41158
41378
  onInspectCommit
41159
41379
  }) => {
41160
41380
  const animate = !usePrefersReducedMotion();
41161
- const trackRef = (0, import_react14.useRef)(null);
41162
- const lastDispatchRef = (0, import_react14.useRef)(0);
41163
- const [layoutMode, setLayoutMode] = (0, import_react14.useState)("even");
41164
- const [scope, setScope] = (0, import_react14.useState)(readStoredScope);
41165
- const [offset, setOffset] = (0, import_react14.useState)(0);
41166
- const [dragFraction, setDragFraction] = (0, import_react14.useState)(null);
41381
+ const trackRef = (0, import_react15.useRef)(null);
41382
+ const lastDispatchRef = (0, import_react15.useRef)(0);
41383
+ const [layoutMode, setLayoutMode] = (0, import_react15.useState)("even");
41384
+ const [scope, setScope] = (0, import_react15.useState)(readStoredScope);
41385
+ const [offset, setOffset] = (0, import_react15.useState)(0);
41386
+ const [dragFraction, setDragFraction] = (0, import_react15.useState)(null);
41167
41387
  const [collapsed, setCollapsed] = usePersistedFlag(
41168
41388
  COLLAPSED_STORAGE_KEY,
41169
41389
  false
@@ -41183,23 +41403,32 @@ var TimeScrubber = ({
41183
41403
  const exitMs = layoutMode === "real" && animate ? DOT_EXIT_TOTAL_MS : 0;
41184
41404
  const issueScatter = useExitTransition(showIssues, exitMs);
41185
41405
  const commitScatter = useExitTransition(showCommits, exitMs);
41186
- const [hoveredEvent, setHoveredEvent] = (0, import_react14.useState)(null);
41187
- const [pointerFraction, setPointerFraction] = (0, import_react14.useState)(null);
41188
- const [hoveredCommit, setHoveredCommit] = (0, import_react14.useState)(null);
41189
- const [hoveredBucketIndex, setHoveredBucketIndex] = (0, import_react14.useState)(
41406
+ const [hoveredEvent, setHoveredEvent] = (0, import_react15.useState)(null);
41407
+ const [pointerFraction, setPointerFraction] = (0, import_react15.useState)(null);
41408
+ const [hoveredCommit, setHoveredCommit] = (0, import_react15.useState)(null);
41409
+ const [hoveredBucketIndex, setHoveredBucketIndex] = (0, import_react15.useState)(
41190
41410
  null
41191
41411
  );
41192
- const [hoveredCommitBucketIndex, setHoveredCommitBucketIndex] = (0, import_react14.useState)(null);
41412
+ const [hoveredCommitBucketIndex, setHoveredCommitBucketIndex] = (0, import_react15.useState)(null);
41193
41413
  const periodRange = getPeriodRange(scope, offset);
41194
- (0, import_react14.useEffect)(() => {
41414
+ (0, import_react15.useEffect)(() => {
41195
41415
  if (!connected) return;
41196
41416
  onRequestHistory(periodRange?.start, periodRange?.end, allBoards);
41197
41417
  }, [scope, offset, boardId, allBoards, connected]);
41198
41418
  const changeScope = (nextScope) => {
41419
+ armEntrance();
41199
41420
  setScope(nextScope);
41200
41421
  setOffset(0);
41201
41422
  localStorage.setItem(SCOPE_STORAGE_KEY, nextScope);
41202
41423
  };
41424
+ const changeOffset = (nextOffset) => {
41425
+ armEntrance();
41426
+ setOffset(nextOffset);
41427
+ };
41428
+ const changeAllBoards = (next) => {
41429
+ armEntrance();
41430
+ setAllBoards(next);
41431
+ };
41203
41432
  const axis = buildAxis(timeline, commits);
41204
41433
  const issueCounts = bucketIssueCounts(axis, timeline);
41205
41434
  const commitStats = bucketCommitStats(axis, commits);
@@ -41220,11 +41449,21 @@ var TimeScrubber = ({
41220
41449
  index: index2,
41221
41450
  intensity: stats.count / maxCommitCount
41222
41451
  }));
41223
- const [animationGeneration, setAnimationGeneration] = (0, import_react14.useState)(0);
41224
- const hasData = timeline !== null;
41225
- (0, import_react14.useEffect)(() => {
41226
- setAnimationGeneration((generation) => generation + 1);
41227
- }, [scope, offset, boardId, allBoards, layoutMode, hasData]);
41452
+ const [entrance, setEntrance] = (0, import_react15.useState)(0);
41453
+ const armed = (0, import_react15.useRef)(true);
41454
+ const seenHistoryId = (0, import_react15.useRef)(historyId);
41455
+ const armEntrance = () => {
41456
+ armed.current = true;
41457
+ };
41458
+ (0, import_react15.useEffect)(armEntrance, [boardId]);
41459
+ (0, import_react15.useEffect)(() => {
41460
+ if (seenHistoryId.current === historyId) return;
41461
+ seenHistoryId.current = historyId;
41462
+ if (!armed.current) return;
41463
+ armed.current = false;
41464
+ setEntrance((generation) => generation + 1);
41465
+ }, [historyId]);
41466
+ const windowKey = `${layoutMode}-${entrance}`;
41228
41467
  const confirmedFraction = timeTravel.mode === "scrub" && timeTravel.asOfTime !== null ? axis.fractionForTime(timeTravel.asOfTime) : 1;
41229
41468
  const fractionFromClientX = (clientX) => {
41230
41469
  const track = trackRef.current;
@@ -41296,7 +41535,7 @@ var TimeScrubber = ({
41296
41535
  hoveredEvent?.t ?? hoveredCommit?.commit.time ?? (pointerFraction !== null ? axis.fractionToTime(pointerFraction) : void 0)
41297
41536
  )) ?? null;
41298
41537
  const segmentUnit = chooseSegmentUnit(axis.span);
41299
- return /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
41538
+ return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
41300
41539
  ScrubberLayout,
41301
41540
  {
41302
41541
  collapsed,
@@ -41313,11 +41552,11 @@ var TimeScrubber = ({
41313
41552
  isScrubbing: timeTravel.mode === "scrub",
41314
41553
  nowLabel: scope === "all" || offset === 0 ? "Now" : formatDateTime(new Date(axis.latest)),
41315
41554
  onChangeScope: changeScope,
41316
- onChangeOffset: setOffset,
41555
+ onChangeOffset: changeOffset,
41317
41556
  onChangeLayoutMode: setLayoutMode,
41318
41557
  onChangeShowIssues: setShowIssues,
41319
41558
  onChangeShowCommits: setShowCommits,
41320
- onChangeAllBoards: setAllBoards,
41559
+ onChangeAllBoards: changeAllBoards,
41321
41560
  onReturnToLive
41322
41561
  },
41323
41562
  chart: {
@@ -41325,7 +41564,7 @@ var TimeScrubber = ({
41325
41564
  axis,
41326
41565
  layoutMode,
41327
41566
  animate,
41328
- windowKey: `${layoutMode}-${animationGeneration}`,
41567
+ windowKey,
41329
41568
  showIssues,
41330
41569
  showCommits,
41331
41570
  issueScatter,
@@ -41395,34 +41634,6 @@ var TimeScrubber = ({
41395
41634
  );
41396
41635
  };
41397
41636
 
41398
- // source/gui/client/lib/socket-send.ts
41399
- var pending = /* @__PURE__ */ new WeakMap();
41400
- var sendSocketJson = (socket, data2) => {
41401
- if (!socket) return;
41402
- const message = JSON.stringify(data2);
41403
- if (socket.readyState === WebSocket.OPEN) {
41404
- socket.send(message);
41405
- return;
41406
- }
41407
- if (socket.readyState !== WebSocket.CONNECTING) return;
41408
- const queued = pending.get(socket);
41409
- if (queued) {
41410
- queued.push(message);
41411
- return;
41412
- }
41413
- pending.set(socket, [message]);
41414
- socket.addEventListener(
41415
- "open",
41416
- () => {
41417
- for (const queuedMessage of pending.get(socket) ?? []) {
41418
- socket.send(queuedMessage);
41419
- }
41420
- pending.delete(socket);
41421
- },
41422
- { once: true }
41423
- );
41424
- };
41425
-
41426
41637
  // source/gui/client/lib/gui-move-issue.tsx
41427
41638
  var toMovePosition = (targetIssues, targetIndex) => {
41428
41639
  if (targetIssues.length === 0) return { at: "start" };
@@ -41467,47 +41678,36 @@ var findMove = (state, issueId, parentId, targetIndex) => {
41467
41678
  targetParentId: targetSwimlane.id
41468
41679
  };
41469
41680
  };
41470
- var moveIssue = (setState, socketRef) => (issueId, parentId, targetIndex) => {
41471
- let position3 = null;
41472
- setState((current) => {
41473
- if (!current) return current;
41474
- const move = findMove(current, issueId, parentId, targetIndex);
41475
- if (!move) return current;
41476
- position3 = move.position;
41477
- return {
41478
- ...current,
41479
- boards: current.boards.map((board) => ({
41480
- ...board,
41481
- swimlanes: board.swimlanes.map((swimlane) => {
41482
- const issuesWithoutMoved = swimlane.issues.filter(
41483
- (issue) => issue.id !== issueId
41484
- );
41485
- if (swimlane.id !== parentId) {
41486
- return {
41487
- ...swimlane,
41488
- issues: issuesWithoutMoved
41489
- };
41490
- }
41491
- const nextIssues = [...issuesWithoutMoved];
41492
- const index2 = targetIndex === "end" ? nextIssues.length : Math.max(0, Math.min(targetIndex, nextIssues.length));
41493
- nextIssues.splice(index2, 0, move.movedIssue);
41494
- return {
41495
- ...swimlane,
41496
- issues: nextIssues
41497
- };
41498
- })
41499
- }))
41500
- };
41501
- });
41502
- if (!position3) return;
41503
- sendSocketJson(socketRef.current, {
41504
- type: "issues:move",
41505
- payload: {
41506
- issueId,
41507
- parentId,
41508
- position: position3
41509
- }
41510
- });
41681
+ var applyMove = (state, issueId, parentId, targetIndex, movedIssue) => ({
41682
+ ...state,
41683
+ boards: state.boards.map((board) => ({
41684
+ ...board,
41685
+ swimlanes: board.swimlanes.map((swimlane) => {
41686
+ const issuesWithoutMoved = swimlane.issues.filter(
41687
+ (issue) => issue.id !== issueId
41688
+ );
41689
+ if (swimlane.id !== parentId) {
41690
+ return { ...swimlane, issues: issuesWithoutMoved };
41691
+ }
41692
+ const nextIssues = [...issuesWithoutMoved];
41693
+ const index2 = targetIndex === "end" ? nextIssues.length : Math.max(0, Math.min(targetIndex, nextIssues.length));
41694
+ nextIssues.splice(index2, 0, movedIssue);
41695
+ return { ...swimlane, issues: nextIssues };
41696
+ })
41697
+ }))
41698
+ });
41699
+ var moveIssue = (state, setState, send) => (issueIds, parentId, targetIndex) => {
41700
+ if (!state) return;
41701
+ let running = state;
41702
+ for (const [offset, issueId] of issueIds.entries()) {
41703
+ const index2 = targetIndex === "end" ? "end" : targetIndex + offset;
41704
+ const move = findMove(running, issueId, parentId, index2);
41705
+ if (!move) continue;
41706
+ running = applyMove(running, issueId, parentId, index2, move.movedIssue);
41707
+ send("issues:move", { issueId, parentId, position: move.position });
41708
+ }
41709
+ const next = running;
41710
+ setState((current) => current ? next : current);
41511
41711
  };
41512
41712
 
41513
41713
  // source/lib/utils/node-ref.ts
@@ -41552,6 +41752,34 @@ var updateIssueInGuiState = (state, issueId, updateIssue) => ({
41552
41752
  }))
41553
41753
  });
41554
41754
 
41755
+ // source/gui/client/lib/socket-send.ts
41756
+ var pending = /* @__PURE__ */ new WeakMap();
41757
+ var sendSocketJson = (socket, data2) => {
41758
+ if (!socket) return;
41759
+ const message = JSON.stringify(data2);
41760
+ if (socket.readyState === WebSocket.OPEN) {
41761
+ socket.send(message);
41762
+ return;
41763
+ }
41764
+ if (socket.readyState !== WebSocket.CONNECTING) return;
41765
+ const queued = pending.get(socket);
41766
+ if (queued) {
41767
+ queued.push(message);
41768
+ return;
41769
+ }
41770
+ pending.set(socket, [message]);
41771
+ socket.addEventListener(
41772
+ "open",
41773
+ () => {
41774
+ for (const queuedMessage of pending.get(socket) ?? []) {
41775
+ socket.send(queuedMessage);
41776
+ }
41777
+ pending.delete(socket);
41778
+ },
41779
+ { once: true }
41780
+ );
41781
+ };
41782
+
41555
41783
  // source/gui/client/lib/history-buffer.ts
41556
41784
  var createHistoryBuffer = (publish) => {
41557
41785
  let lastRequestId = 0;
@@ -41571,7 +41799,48 @@ var createHistoryBuffer = (publish) => {
41571
41799
  const { timeline, commits } = pending2;
41572
41800
  if (!timeline || !commits) return;
41573
41801
  pending2 = {};
41574
- publish({ timeline, commits });
41802
+ publish({ requestId, timeline, commits });
41803
+ }
41804
+ };
41805
+ };
41806
+
41807
+ // source/gui/client/lib/gui-mutations.ts
41808
+ var MUTATING_MESSAGE_TYPES = /* @__PURE__ */ new Set([
41809
+ "sync",
41810
+ "issues:create",
41811
+ "issues:move",
41812
+ "issue:close",
41813
+ "issue:reopen",
41814
+ "issue:edit:title",
41815
+ "issue:edit:description",
41816
+ "issue:tag:add",
41817
+ "issue:tag:remove",
41818
+ "contributor:remove",
41819
+ "issue:assignee:add",
41820
+ "issue:assignee:remove",
41821
+ "issue:comment:add",
41822
+ "issue:comment:delete"
41823
+ ]);
41824
+
41825
+ // source/gui/client/lib/mutation-gate.ts
41826
+ var createMutationGate = () => {
41827
+ let pending2 = 0;
41828
+ return {
41829
+ sent(type) {
41830
+ if (MUTATING_MESSAGE_TYPES.has(type)) pending2 += 1;
41831
+ },
41832
+ /** `error` counts too: the handler paths that fail early send no result. */
41833
+ received(type) {
41834
+ if (type === "error" || type?.endsWith(":result")) {
41835
+ pending2 = Math.max(0, pending2 - 1);
41836
+ }
41837
+ },
41838
+ /** A reply owed on a closed socket never arrives. */
41839
+ reset() {
41840
+ pending2 = 0;
41841
+ },
41842
+ holdsState() {
41843
+ return pending2 > 0;
41575
41844
  }
41576
41845
  };
41577
41846
  };
@@ -41641,8 +41910,8 @@ var blobToBase64 = (blob) => new Promise((resolve, reject) => {
41641
41910
  });
41642
41911
 
41643
41912
  // source/gui/client/App.tsx
41644
- var import_jsx_runtime32 = __toESM(require_jsx_runtime(), 1);
41645
- var DropIndicator = () => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
41913
+ var import_jsx_runtime33 = __toESM(require_jsx_runtime(), 1);
41914
+ var DropIndicator = () => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
41646
41915
  "div",
41647
41916
  {
41648
41917
  style: {
@@ -41655,38 +41924,44 @@ var DropIndicator = () => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
41655
41924
  }
41656
41925
  );
41657
41926
  var App = () => {
41658
- const { boardId, issueId } = useParams();
41927
+ const { boardId } = useParams();
41928
+ const issueMatch = useMatch("/board/:boardId/issue/:issueId");
41929
+ const legacyIssueMatch = useMatch("/board/:boardId/:issueId");
41930
+ const issueId = issueMatch?.params.issueId ?? legacyIssueMatch?.params.issueId;
41659
41931
  const [searchParams, setSearchParams] = useSearchParams();
41660
- const [connected, setConnected] = (0, import_react15.useState)(false);
41661
- const [syncStatus, setSyncStatus] = (0, import_react15.useState)({
41932
+ const [connected, setConnected] = (0, import_react16.useState)(false);
41933
+ const [syncStatus, setSyncStatus] = (0, import_react16.useState)({
41662
41934
  status: "synced",
41663
41935
  msg: "idle"
41664
41936
  });
41665
- const [state, setState] = (0, import_react15.useState)(null);
41666
- const [contributors, setContributors] = (0, import_react15.useState)([]);
41667
- const [history, setHistory] = (0, import_react15.useState)({ timeline: null, commits: [] });
41668
- const [historyBuffer] = (0, import_react15.useState)(() => createHistoryBuffer(setHistory));
41669
- const [commitInspectError, setCommitInspectError] = (0, import_react15.useState)(
41937
+ const [state, setState] = (0, import_react16.useState)(null);
41938
+ const [contributors, setContributors] = (0, import_react16.useState)([]);
41939
+ const [history, setHistory] = (0, import_react16.useState)({ requestId: 0, timeline: null, commits: [] });
41940
+ const [historyBuffer] = (0, import_react16.useState)(() => createHistoryBuffer(setHistory));
41941
+ const [commitInspectError, setCommitInspectError] = (0, import_react16.useState)(
41670
41942
  null
41671
41943
  );
41672
- const [removeError, setRemoveError] = (0, import_react15.useState)(null);
41673
- const [dragOverSwimlaneId, setDragOverSwimlaneId] = (0, import_react15.useState)(
41944
+ const [removeError, setRemoveError] = (0, import_react16.useState)(null);
41945
+ const [actionError, setActionError] = (0, import_react16.useState)(null);
41946
+ const [dragOverSwimlaneId, setDragOverSwimlaneId] = (0, import_react16.useState)(
41674
41947
  null
41675
41948
  );
41676
- const [dropTarget, setDropTarget] = (0, import_react15.useState)(null);
41677
- const [boardMenuOpen, setBoardMenuOpen] = (0, import_react15.useState)(false);
41678
- const [createIssueModal, setCreateIssueModal] = (0, import_react15.useState)(null);
41679
- const boardMenuRef = (0, import_react15.useRef)(null);
41680
- const socketRef = (0, import_react15.useRef)(null);
41949
+ const [dropTarget, setDropTarget] = (0, import_react16.useState)(null);
41950
+ const [pickedIssueIds, setPickedIssueIds] = (0, import_react16.useState)([]);
41951
+ const [boardMenuOpen, setBoardMenuOpen] = (0, import_react16.useState)(false);
41952
+ const [createIssueModal, setCreateIssueModal] = (0, import_react16.useState)(null);
41953
+ const boardMenuRef = (0, import_react16.useRef)(null);
41954
+ const socketRef = (0, import_react16.useRef)(null);
41955
+ const [mutationGate] = (0, import_react16.useState)(createMutationGate);
41681
41956
  const selectedTab = searchParams.get("tab") === "comments" ? "comments" : "overview";
41682
41957
  const navigate = useNavigate();
41683
41958
  const selectedBoard = (state && boardId ? findBoard(state, boardId) : null) ?? state?.boards[0] ?? null;
41684
41959
  const selectedBoardId = selectedBoard?.id ?? null;
41685
- const selectedBoardIdRef = (0, import_react15.useRef)(selectedBoardId);
41960
+ const selectedBoardIdRef = (0, import_react16.useRef)(selectedBoardId);
41686
41961
  selectedBoardIdRef.current = selectedBoardId;
41687
41962
  const boardSlug = selectedBoard?.ref ?? boardId;
41688
41963
  const selectedIssue = state && issueId ? findIssue(state, issueId) : null;
41689
- (0, import_react15.useEffect)(() => {
41964
+ (0, import_react16.useEffect)(() => {
41690
41965
  const onPaste = (event) => {
41691
41966
  if (!selectedIssue || selectedIssue.readonly) return;
41692
41967
  const target = event.target;
@@ -41707,28 +41982,31 @@ var App = () => {
41707
41982
  };
41708
41983
  const commentsByIssueId = state?.commentsByIssueId ?? {};
41709
41984
  const attachmentsByIssueId = state?.attachmentsByIssueId ?? {};
41710
- const [attachmentUploadStatus, setAttachmentUploadStatus] = (0, import_react15.useState)({ state: "idle" });
41985
+ const [attachmentUploadStatus, setAttachmentUploadStatus] = (0, import_react16.useState)({ state: "idle" });
41711
41986
  const requestState = () => {
41712
41987
  sendSocketJson(socketRef.current, { type: "state:get" });
41713
41988
  };
41714
- (0, import_react15.useEffect)(() => {
41989
+ (0, import_react16.useEffect)(() => {
41715
41990
  const socket = new WebSocket(
41716
41991
  `ws://${window.location.host}/ws${boardId ? `?boardId=${boardId}` : ""}`
41717
41992
  );
41718
41993
  socketRef.current = socket;
41719
41994
  socket.addEventListener("open", () => {
41720
41995
  setConnected(true);
41996
+ mutationGate.reset();
41721
41997
  sendSocketJson(socket, { type: "state:get" });
41722
41998
  });
41723
41999
  socket.addEventListener("close", () => {
41724
42000
  setConnected(false);
42001
+ mutationGate.reset();
41725
42002
  if (socketRef.current === socket) {
41726
42003
  socketRef.current = null;
41727
42004
  }
41728
42005
  });
41729
42006
  socket.addEventListener("message", (event) => {
41730
42007
  const message = JSON.parse(event.data);
41731
- if (message.type === "state") {
42008
+ mutationGate.received(message.type);
42009
+ if (message.type === "state" && !mutationGate.holdsState()) {
41732
42010
  const nextState = getResultValue(message.payload);
41733
42011
  if (nextState) setState(nextState);
41734
42012
  }
@@ -41741,7 +42019,9 @@ var App = () => {
41741
42019
  }
41742
42020
  }
41743
42021
  if (message.type === "failed") {
41744
- console.log("Failed", message);
42022
+ setActionError(
42023
+ typeof message.payload === "string" ? message.payload : "The board refused that change"
42024
+ );
41745
42025
  requestState();
41746
42026
  }
41747
42027
  if (message.type === "timeline") {
@@ -41789,12 +42069,12 @@ var App = () => {
41789
42069
  socket.close();
41790
42070
  };
41791
42071
  }, [boardId, navigate]);
41792
- (0, import_react15.useEffect)(() => {
42072
+ (0, import_react16.useEffect)(() => {
41793
42073
  if (!boardId && state?.boards[0]) {
41794
42074
  void navigate(`/board/${state.boards[0].ref}`, { replace: true });
41795
42075
  }
41796
42076
  }, [boardId, state, navigate]);
41797
- (0, import_react15.useEffect)(() => {
42077
+ (0, import_react16.useEffect)(() => {
41798
42078
  const close = (event) => {
41799
42079
  if (boardMenuRef.current && !boardMenuRef.current.contains(event.target)) {
41800
42080
  setBoardMenuOpen(false);
@@ -41808,9 +42088,24 @@ var App = () => {
41808
42088
  setDropTarget(null);
41809
42089
  };
41810
42090
  const send = (type, payload) => {
42091
+ mutationGate.sent(type);
41811
42092
  sendSocketJson(socketRef.current, { type, payload });
41812
42093
  };
41813
- const selectIssue = (nextIssueId) => {
42094
+ const togglePicked = (nextIssueId) => {
42095
+ setPickedIssueIds(
42096
+ (current) => current.includes(nextIssueId) ? current.filter((id) => id !== nextIssueId) : [...current, nextIssueId]
42097
+ );
42098
+ };
42099
+ const clearPicked = () => setPickedIssueIds([]);
42100
+ const [bulkTagName, setBulkTagName] = (0, import_react16.useState)("");
42101
+ const [bulkAssigneeName, setBulkAssigneeName] = (0, import_react16.useState)("");
42102
+ const pickedIssues = (state?.boards ?? []).flatMap((board) => board.swimlanes).flatMap((swimlane) => swimlane.issues).filter((issue) => pickedIssueIds.includes(issue.id));
42103
+ const forPicked = (act) => {
42104
+ for (const issue of pickedIssues) act(issue.id);
42105
+ };
42106
+ const selectIssue = (nextIssueId, { toggle } = { toggle: false }) => {
42107
+ if (toggle) return togglePicked(nextIssueId);
42108
+ setPickedIssueIds([nextIssueId]);
41814
42109
  if (!boardSlug) return;
41815
42110
  void navigate(
41816
42111
  `/board/${boardSlug}/issue/${nodeRef(nextIssueId)}?tab=overview`
@@ -41957,7 +42252,7 @@ var App = () => {
41957
42252
  const returnToLive = () => {
41958
42253
  send("time-travel:live", {});
41959
42254
  };
41960
- const requestBoardHistory = (0, import_react15.useCallback)(
42255
+ const requestBoardHistory = (0, import_react16.useCallback)(
41961
42256
  (start2, end, allBoards) => {
41962
42257
  const window2 = start2 !== void 0 ? { start: start2, end } : void 0;
41963
42258
  const requestId = historyBuffer.open();
@@ -41976,28 +42271,34 @@ var App = () => {
41976
42271
  },
41977
42272
  [selectedBoardId]
41978
42273
  );
41979
- const requestContributors = (0, import_react15.useCallback)(() => {
42274
+ const requestContributors = (0, import_react16.useCallback)(() => {
41980
42275
  sendSocketJson(socketRef.current, {
41981
42276
  type: "contributors:get",
41982
42277
  payload: { boardId: selectedBoardIdRef.current }
41983
42278
  });
41984
42279
  }, []);
41985
- (0, import_react15.useEffect)(() => {
42280
+ (0, import_react16.useEffect)(() => {
41986
42281
  setContributors([]);
42282
+ setPickedIssueIds([]);
41987
42283
  }, [selectedBoardId]);
41988
- const inspectCommit = (0, import_react15.useCallback)((sha) => {
42284
+ const inspectCommit = (0, import_react16.useCallback)((sha) => {
41989
42285
  sendSocketJson(socketRef.current, { type: "commit:inspect", payload: { sha } });
41990
42286
  }, []);
41991
- (0, import_react15.useEffect)(() => {
42287
+ (0, import_react16.useEffect)(() => {
41992
42288
  if (!commitInspectError) return;
41993
42289
  const timeout = setTimeout(() => setCommitInspectError(null), 8e3);
41994
42290
  return () => clearTimeout(timeout);
41995
42291
  }, [commitInspectError]);
41996
- (0, import_react15.useEffect)(() => {
42292
+ (0, import_react16.useEffect)(() => {
41997
42293
  if (!removeError) return;
41998
42294
  const timeout = setTimeout(() => setRemoveError(null), 8e3);
41999
42295
  return () => clearTimeout(timeout);
42000
42296
  }, [removeError]);
42297
+ (0, import_react16.useEffect)(() => {
42298
+ if (!actionError) return;
42299
+ const timeout = setTimeout(() => setActionError(null), 8e3);
42300
+ return () => clearTimeout(timeout);
42301
+ }, [actionError]);
42001
42302
  const selectBoard = (nextBoardId) => {
42002
42303
  setBoardMenuOpen(false);
42003
42304
  clearDragState();
@@ -42120,7 +42421,7 @@ var App = () => {
42120
42421
  });
42121
42422
  send("issue:comment:delete", { commentId });
42122
42423
  };
42123
- return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
42424
+ return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
42124
42425
  "div",
42125
42426
  {
42126
42427
  style: {
@@ -42132,27 +42433,35 @@ var App = () => {
42132
42433
  flexDirection: "column"
42133
42434
  },
42134
42435
  children: [
42135
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(GlobalScrollbarStyles, {}),
42136
- commitInspectError && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
42436
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(GlobalScrollbarStyles, {}),
42437
+ commitInspectError && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42137
42438
  ErrorToast,
42138
42439
  {
42139
42440
  message: `Couldn't open commit diff: ${commitInspectError}`,
42140
42441
  onDismiss: () => setCommitInspectError(null)
42141
42442
  }
42142
42443
  ),
42143
- removeError && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
42444
+ removeError && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42144
42445
  ErrorToast,
42145
42446
  {
42146
42447
  message: removeError,
42147
42448
  onDismiss: () => setRemoveError(null)
42148
42449
  }
42149
42450
  ),
42150
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(Header, { state, connected, syncStatus }),
42151
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
42451
+ actionError && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42452
+ ErrorToast,
42453
+ {
42454
+ message: actionError,
42455
+ onDismiss: () => setActionError(null)
42456
+ }
42457
+ ),
42458
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Header, { state, connected, syncStatus }),
42459
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42152
42460
  TimeScrubber,
42153
42461
  {
42154
42462
  timeline: history.timeline,
42155
42463
  commits: history.commits,
42464
+ historyId: history.requestId,
42156
42465
  boardId: selectedBoardId,
42157
42466
  connected,
42158
42467
  onRequestHistory: requestBoardHistory,
@@ -42162,7 +42471,7 @@ var App = () => {
42162
42471
  onReturnToLive: returnToLive
42163
42472
  }
42164
42473
  ),
42165
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
42474
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
42166
42475
  "div",
42167
42476
  {
42168
42477
  style: {
@@ -42171,9 +42480,10 @@ var App = () => {
42171
42480
  overflow: "hidden"
42172
42481
  },
42173
42482
  children: [
42174
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
42483
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
42175
42484
  "main",
42176
42485
  {
42486
+ onClick: clearPicked,
42177
42487
  style: {
42178
42488
  // No bottom padding: the board row is the horizontal scroll
42179
42489
  // container, and a gap below it would strand its scrollbar.
@@ -42185,7 +42495,7 @@ var App = () => {
42185
42495
  overflow: "hidden"
42186
42496
  },
42187
42497
  children: [
42188
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { style: { padding: "20px 10px" }, children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
42498
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { padding: "20px 10px" }, children: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42189
42499
  Dropdown,
42190
42500
  {
42191
42501
  testId: "board-switcher",
@@ -42202,7 +42512,7 @@ var App = () => {
42202
42512
  onSelect: selectBoard
42203
42513
  }
42204
42514
  ) }),
42205
- /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)(
42515
+ /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(
42206
42516
  "div",
42207
42517
  {
42208
42518
  style: {
@@ -42214,7 +42524,7 @@ var App = () => {
42214
42524
  overflowY: "hidden"
42215
42525
  },
42216
42526
  children: [
42217
- selectedBoard?.swimlanes.map((swimlane) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
42527
+ selectedBoard?.swimlanes.map((swimlane) => /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42218
42528
  SwimlaneColumn,
42219
42529
  {
42220
42530
  swimlane,
@@ -42226,21 +42536,58 @@ var App = () => {
42226
42536
  onSelectIssue: selectIssue,
42227
42537
  onSelectIssueComments: selectIssueComments,
42228
42538
  onCreateIssue: openCreateIssueModal,
42229
- onDropIssue: moveIssue(setState, socketRef),
42539
+ onDropIssue: (issueId2, swimlaneId, targetIndex) => {
42540
+ const moving = pickedIssueIds.includes(issueId2) ? pickedIssueIds : [issueId2];
42541
+ moveIssue(state, setState, send)(
42542
+ moving,
42543
+ swimlaneId,
42544
+ targetIndex
42545
+ );
42546
+ clearPicked();
42547
+ },
42548
+ pickedIssueIds,
42230
42549
  onDragOver: setDragOverSwimlaneId,
42231
42550
  onDragOverIssue: (swimlaneId, index2) => setDropTarget({ swimlaneId, index: index2 }),
42232
42551
  onDragLeave: clearDragState
42233
42552
  },
42234
42553
  swimlane.id
42235
42554
  )),
42236
- !(selectedIssue && state?.user) && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { style: { width: ASIDE_WIDTH, flexShrink: 0 } })
42555
+ !(selectedIssue && state?.user) && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("div", { style: { width: ASIDE_WIDTH, flexShrink: 0 } })
42237
42556
  ]
42238
42557
  }
42239
42558
  )
42240
42559
  ]
42241
42560
  }
42242
42561
  ),
42243
- selectedIssue && state?.user && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
42562
+ pickedIssues.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42563
+ BulkDetails,
42564
+ {
42565
+ issues: pickedIssues,
42566
+ knownTags: state?.tags ?? [],
42567
+ knownAssignees: contributors,
42568
+ tagName: bulkTagName,
42569
+ assigneeName: bulkAssigneeName,
42570
+ onChangeTagName: setBulkTagName,
42571
+ onChangeAssigneeName: setBulkAssigneeName,
42572
+ onAddTag: (name2) => {
42573
+ forPicked((id) => addIssueTag(id, name2));
42574
+ setBulkTagName("");
42575
+ },
42576
+ onRemoveTag: (tagId) => forPicked((id) => removeIssueTag(id, tagId)),
42577
+ onAddAssignee: (assigneeId) => forPicked((id) => addIssueAssignee(id, assigneeId)),
42578
+ onRemoveAssignee: (assigneeId) => forPicked((id) => removeIssueAssignee(id, assigneeId)),
42579
+ onCloseIssues: () => {
42580
+ forPicked(closeIssue);
42581
+ clearPicked();
42582
+ },
42583
+ onReopenIssues: () => {
42584
+ forPicked(reopenIssue);
42585
+ clearPicked();
42586
+ },
42587
+ onClear: clearPicked
42588
+ }
42589
+ ),
42590
+ pickedIssues.length <= 1 && selectedIssue && state?.user && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42244
42591
  IssueDetails,
42245
42592
  {
42246
42593
  whoAmI: state.user,
@@ -42273,7 +42620,7 @@ var App = () => {
42273
42620
  ]
42274
42621
  }
42275
42622
  ),
42276
- createIssueModal && /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
42623
+ createIssueModal && /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
42277
42624
  CreateIssueModal,
42278
42625
  {
42279
42626
  title: createIssueModal.title,
@@ -42288,14 +42635,12 @@ var App = () => {
42288
42635
  };
42289
42636
 
42290
42637
  // source/gui/client/main.tsx
42291
- var import_jsx_runtime33 = __toESM(require_jsx_runtime(), 1);
42638
+ var import_jsx_runtime34 = __toESM(require_jsx_runtime(), 1);
42292
42639
  (0, import_client.createRoot)(document.getElementById("root")).render(
42293
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(BrowserRouter, { children: /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)(Routes, { children: [
42294
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(App, {}) }),
42295
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Route, { path: "/board/:boardId", element: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(App, {}) }),
42296
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Route, { path: "/board/:boardId/issue/:issueId", element: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(App, {}) }),
42297
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Route, { path: "/board/:boardId/:issueId", element: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(App, {}) }),
42298
- /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(Navigate, { to: "/", replace: true }) })
42640
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(BrowserRouter, { children: /* @__PURE__ */ (0, import_jsx_runtime34.jsxs)(Routes, { children: [
42641
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Route, { path: "/", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(App, {}) }),
42642
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Route, { path: "/board/:boardId/*", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(App, {}) }),
42643
+ /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Route, { path: "*", element: /* @__PURE__ */ (0, import_jsx_runtime34.jsx)(Navigate, { to: "/", replace: true }) })
42299
42644
  ] }) })
42300
42645
  );
42301
42646
  /*! Bundled license information: