epiq 1.7.0 → 1.7.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
@@ -52557,7 +52557,7 @@ var User = ({
52557
52557
  );
52558
52558
 
52559
52559
  // source/version.ts
52560
- var EPIQ_VERSION = "1.7.0";
52560
+ var EPIQ_VERSION = "1.7.2";
52561
52561
 
52562
52562
  // source/gui/client/components/Header.tsx
52563
52563
  var import_jsx_runtime32 = __toESM(require_jsx_runtime(), 1);
@@ -52906,7 +52906,12 @@ var ManageContributorsModal = ({
52906
52906
  alignItems: "center",
52907
52907
  gap: 6,
52908
52908
  color: armed ? GUI_THEME.red : GUI_THEME.dim,
52909
- borderColor: armed ? GUI_THEME.red : void 0
52909
+ // The whole shorthand, and only when armed: Button sets
52910
+ // `border` itself, and React warns that an element given
52911
+ // both it and `borderColor` can drop whichever loses the
52912
+ // race. Spreading `border: undefined` is not the other
52913
+ // branch — that reads as "remove this style".
52914
+ ...armed ? { border: `1px solid ${GUI_THEME.red}` } : {}
52910
52915
  },
52911
52916
  children: [
52912
52917
  armed && /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { style: { fontSize: 10 }, children: "confirm" }),
@@ -53017,7 +53022,12 @@ var ManageTagsModal = ({ tags, onDelete, onClose }) => {
53017
53022
  alignItems: "center",
53018
53023
  gap: 6,
53019
53024
  color: armed ? GUI_THEME.red : GUI_THEME.dim,
53020
- borderColor: armed ? GUI_THEME.red : void 0
53025
+ // The whole shorthand, and only when armed: Button sets
53026
+ // `border` itself, and React warns that an element given
53027
+ // both it and `borderColor` can drop whichever loses the
53028
+ // race. Spreading `border: undefined` is not the other
53029
+ // branch — that reads as "remove this style".
53030
+ ...armed ? { border: `1px solid ${GUI_THEME.red}` } : {}
53021
53031
  },
53022
53032
  children: [
53023
53033
  armed && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { style: { fontSize: 10 }, children: "confirm" }),
@@ -66837,6 +66847,7 @@ var HOVER_HINT_WIDTH = 220;
66837
66847
  var SEGMENT_HIGHLIGHT_COLOR = "rgba(122, 157, 214, 0.14)";
66838
66848
  var BUCKET_HIGHLIGHT_COLOR = "rgba(255, 255, 255, 0.06)";
66839
66849
  var NEEDLE_COLOR = "rgba(255, 255, 255, 0.62)";
66850
+ var SCOPED_OUTLINE_COLOR = "rgba(118, 212, 255, 0.45)";
66840
66851
  var RANGE_SELECTION_COLOR = "rgba(122, 157, 214, 0.22)";
66841
66852
  var RANGE_SELECTION_EDGE = "rgba(160, 195, 250, 0.75)";
66842
66853
  var NEEDLE_GRIP_WIDTH = 11;
@@ -67112,6 +67123,7 @@ var SCOPE_RECENT_LABELS = {
67112
67123
  year: "Last 365 days"
67113
67124
  };
67114
67125
  var isScope = (value) => SCOPES.includes(value ?? "");
67126
+ var isPeriodWindow = (scope, zoomed) => scope !== "all" || zoomed;
67115
67127
  var getPeriodRange = (scope, offset) => {
67116
67128
  if (scope === "all") return null;
67117
67129
  const durationMs = SCOPE_DURATION_MS[scope];
@@ -67249,6 +67261,15 @@ var buildBoardFilter = (view, only) => {
67249
67261
  if (axis === null || only === null) return null;
67250
67262
  return { axis, visibleIds: new Set(only) };
67251
67263
  };
67264
+ var windowNamesIssues = (timeline) => timeline === null || timeline.events.length > 0 || timeline.buckets.length === 0;
67265
+ var windowIssueIds = (timeline) => {
67266
+ if (timeline === null || !windowNamesIssues(timeline)) return null;
67267
+ const ids = /* @__PURE__ */ new Set();
67268
+ for (const entry of timeline.events) {
67269
+ if (entry.issue !== null) ids.add(entry.issue);
67270
+ }
67271
+ return ids;
67272
+ };
67252
67273
  var issuePassesBoardFilter = (issue, commentAuthorIds, filter) => {
67253
67274
  if (!filter) return true;
67254
67275
  const ids = filter.axis === "tag" ? issue.tags.map((tag) => tag.id) : filter.axis === "assignee" ? issue.assignees.map((assignee) => assignee.id) : commentAuthorIds;
@@ -68930,7 +68951,8 @@ var DEFAULT_SELECTION = {
68930
68951
  zoom: null,
68931
68952
  layout: "even",
68932
68953
  view: "all",
68933
- only: null
68954
+ only: null,
68955
+ windowOnly: false
68934
68956
  };
68935
68957
  var PARAM_KEYS = [
68936
68958
  "scope",
@@ -68939,7 +68961,8 @@ var PARAM_KEYS = [
68939
68961
  "to",
68940
68962
  "layout",
68941
68963
  "view",
68942
- "only"
68964
+ "only",
68965
+ "window"
68943
68966
  ];
68944
68967
  var STORAGE_KEY = "epiq.board.selection";
68945
68968
  var unique = (ids) => [...new Set(ids)];
@@ -68954,7 +68977,7 @@ var normalize3 = (selection) => {
68954
68977
  only: selection.only === null ? null : unique(selection.only)
68955
68978
  };
68956
68979
  };
68957
- var isDefaultSelection = (selection) => selection.scope === DEFAULT_SELECTION.scope && selection.offset === DEFAULT_SELECTION.offset && selection.zoom === null && selection.layout === DEFAULT_SELECTION.layout && selection.view === DEFAULT_SELECTION.view && selection.only === null;
68980
+ var isDefaultSelection = (selection) => selection.scope === DEFAULT_SELECTION.scope && selection.offset === DEFAULT_SELECTION.offset && selection.zoom === null && selection.layout === DEFAULT_SELECTION.layout && selection.view === DEFAULT_SELECTION.view && selection.only === null && selection.windowOnly === DEFAULT_SELECTION.windowOnly;
68958
68981
  var applySelectionPatch = (current, patch2) => {
68959
68982
  const scopeChanged = patch2.scope !== void 0 && patch2.scope !== current.scope;
68960
68983
  const viewChanged = patch2.view !== void 0 && patch2.view !== current.view;
@@ -68982,7 +69005,8 @@ var readSelectionParams = (params) => {
68982
69005
  zoom: from === null || to === null ? null : { start: Number(from), end: Number(to) },
68983
69006
  layout: isLayoutMode(layout) ? layout : DEFAULT_SELECTION.layout,
68984
69007
  view: isBoardView(view) ? view : DEFAULT_SELECTION.view,
68985
- only: only === null ? null : only.split(",").filter(Boolean)
69008
+ only: only === null ? null : only.split(",").filter(Boolean),
69009
+ windowOnly: params.get("window") === "1"
68986
69010
  });
68987
69011
  };
68988
69012
  var writeSelectionParams = (params, selection) => {
@@ -69001,6 +69025,7 @@ var writeSelectionParams = (params, selection) => {
69001
69025
  put("layout", next.layout === DEFAULT_SELECTION.layout ? null : next.layout);
69002
69026
  put("view", next.view === DEFAULT_SELECTION.view ? null : next.view);
69003
69027
  put("only", next.only === null ? null : next.only.join(","));
69028
+ put("window", next.windowOnly ? "1" : null);
69004
69029
  };
69005
69030
  var readStoredSelection = () => {
69006
69031
  try {
@@ -69015,7 +69040,8 @@ var readStoredSelection = () => {
69015
69040
  zoom: null,
69016
69041
  layout: isLayoutMode(String(layout)) ? layout : DEFAULT_SELECTION.layout,
69017
69042
  view: isBoardView(view) ? view : DEFAULT_SELECTION.view,
69018
- only: Array.isArray(only) ? only.map(String) : null
69043
+ only: Array.isArray(only) ? only.map(String) : null,
69044
+ windowOnly: DEFAULT_SELECTION.windowOnly
69019
69045
  });
69020
69046
  } catch {
69021
69047
  return DEFAULT_SELECTION;
@@ -69081,7 +69107,10 @@ var Checkbox = ({
69081
69107
  fontSize: 10,
69082
69108
  color: color2,
69083
69109
  cursor: disabled ? "not-allowed" : "pointer",
69084
- opacity: disabled ? 0.4 : 1
69110
+ opacity: disabled ? 0.4 : 1,
69111
+ // A label that wraps takes the row's height with it.
69112
+ whiteSpace: "nowrap",
69113
+ flexShrink: 0
69085
69114
  },
69086
69115
  children: [
69087
69116
  /* @__PURE__ */ (0, import_jsx_runtime62.jsxs)(
@@ -69185,6 +69214,7 @@ var IconScatter = ({ size = 16 }) => /* @__PURE__ */ (0, import_jsx_runtime64.js
69185
69214
 
69186
69215
  // source/gui/client/components/ScrubberParts.tsx
69187
69216
  var import_jsx_runtime65 = __toESM(require_jsx_runtime(), 1);
69217
+ var SCOPE_ONLY_LABEL = "Scope only";
69188
69218
  var toggleButtonStyle = (active) => ({
69189
69219
  background: "transparent",
69190
69220
  border: `1px solid ${active ? GUI_THEME.accent : GUI_THEME.dim}`,
@@ -69554,6 +69584,8 @@ var ScrubberControls = ({
69554
69584
  periodRange,
69555
69585
  zoomed,
69556
69586
  atLatest,
69587
+ windowOnly,
69588
+ windowFilterable,
69557
69589
  layoutMode,
69558
69590
  showIssues,
69559
69591
  showCommits,
@@ -69568,6 +69600,7 @@ var ScrubberControls = ({
69568
69600
  onReturnToLive,
69569
69601
  onChangeScope,
69570
69602
  onChangeOffset,
69603
+ onChangeWindowOnly,
69571
69604
  onChangeLayoutMode,
69572
69605
  onChangeShowIssues,
69573
69606
  onChangeShowCommits,
@@ -69577,182 +69610,195 @@ var ScrubberControls = ({
69577
69610
  onOnlyIdentity,
69578
69611
  onToggleCategoriesExpanded,
69579
69612
  onSetIdentitiesExpanded
69580
- }) => /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)(
69581
- "div",
69582
- {
69583
- style: {
69584
- display: "flex",
69585
- alignItems: "center",
69586
- justifyContent: "flex-end",
69587
- gap: 12
69588
- },
69589
- children: [
69590
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
69591
- (scope !== "all" || zoomed) && /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 2 }, children: [
69613
+ }) => {
69614
+ const everythingInScope = !isPeriodWindow(scope, zoomed);
69615
+ return /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)(
69616
+ "div",
69617
+ {
69618
+ style: {
69619
+ display: "flex",
69620
+ alignItems: "center",
69621
+ justifyContent: "flex-end",
69622
+ gap: 12
69623
+ },
69624
+ children: [
69625
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
69626
+ (scope !== "all" || zoomed) && /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 2 }, children: [
69627
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69628
+ "button",
69629
+ {
69630
+ title: "Earlier",
69631
+ disabled: !connected,
69632
+ onClick: () => onChangeOffset(offset + 1),
69633
+ style: { ...navButtonStyle, ...connected ? {} : mutedStyle },
69634
+ children: "\u25C0"
69635
+ }
69636
+ ),
69637
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69638
+ "span",
69639
+ {
69640
+ style: {
69641
+ fontSize: 10,
69642
+ color: GUI_THEME.dim,
69643
+ whiteSpace: "nowrap",
69644
+ overflow: "hidden",
69645
+ // Fixed, not min, so the changing label never shifts the
69646
+ // buttons around it.
69647
+ width: 88,
69648
+ flexShrink: 0,
69649
+ textAlign: "center"
69650
+ },
69651
+ children: formatPeriodLabel(scope, offset, periodRange, zoomed)
69652
+ }
69653
+ ),
69654
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69655
+ "button",
69656
+ {
69657
+ title: "Later",
69658
+ disabled: atLatest || !connected,
69659
+ onClick: () => onChangeOffset(offset - 1),
69660
+ style: {
69661
+ ...navButtonStyle,
69662
+ opacity: atLatest ? 0.35 : 1,
69663
+ cursor: atLatest ? "default" : "pointer"
69664
+ },
69665
+ children: "\u25B6"
69666
+ }
69667
+ )
69668
+ ] }),
69669
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", gap: 2 }, children: [
69670
+ SCOPES.map((option2) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69671
+ "button",
69672
+ {
69673
+ "aria-pressed": !zoomed && scope === option2,
69674
+ disabled: !connected,
69675
+ onClick: () => onChangeScope(option2),
69676
+ style: {
69677
+ ...scopeButtonStyle(!zoomed && scope === option2),
69678
+ ...connected ? {} : mutedStyle
69679
+ },
69680
+ children: scopeButtonLabel(option2)
69681
+ },
69682
+ option2
69683
+ )),
69684
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69685
+ "button",
69686
+ {
69687
+ title: zoomed ? "A window dragged out on the chart \u2014 pick a period to leave it" : "Drag across the chart to zoom the window to a stretch of it",
69688
+ "aria-pressed": zoomed,
69689
+ disabled: true,
69690
+ style: {
69691
+ ...scopeButtonStyle(zoomed),
69692
+ opacity: zoomed ? 1 : 0.35,
69693
+ cursor: "default"
69694
+ },
69695
+ children: "Zoom"
69696
+ }
69697
+ )
69698
+ ] })
69699
+ ] }),
69700
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", gap: 2 }, children: [
69592
69701
  /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69593
69702
  "button",
69594
69703
  {
69595
- title: "Earlier",
69704
+ title: "Volume \u2014 how much happened, per equal-width period, with no empty gaps for quiet stretches",
69705
+ "aria-label": "Volume",
69706
+ "aria-pressed": layoutMode === "even",
69596
69707
  disabled: !connected,
69597
- onClick: () => onChangeOffset(offset + 1),
69598
- style: { ...navButtonStyle, ...connected ? {} : mutedStyle },
69599
- children: "\u25C0"
69600
- }
69601
- ),
69602
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69603
- "span",
69604
- {
69708
+ onClick: () => onChangeLayoutMode("even"),
69605
69709
  style: {
69606
- fontSize: 10,
69607
- color: GUI_THEME.dim,
69608
- whiteSpace: "nowrap",
69609
- overflow: "hidden",
69610
- // Fixed, not min, so the changing label never shifts the
69611
- // buttons around it.
69612
- width: 88,
69613
- flexShrink: 0,
69614
- textAlign: "center"
69710
+ ...iconToggleButtonStyle(layoutMode === "even"),
69711
+ ...connected ? {} : mutedStyle
69615
69712
  },
69616
- children: formatPeriodLabel(scope, offset, periodRange, zoomed)
69713
+ children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(IconBars, { size: 13 })
69617
69714
  }
69618
69715
  ),
69619
69716
  /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69620
69717
  "button",
69621
69718
  {
69622
- title: "Later",
69623
- disabled: atLatest || !connected,
69624
- onClick: () => onChangeOffset(offset - 1),
69719
+ title: "Events \u2014 individual events by exact moment, x is elapsed time and y is time of day",
69720
+ "aria-label": "Events",
69721
+ "aria-pressed": layoutMode === "real",
69722
+ disabled: !connected,
69723
+ onClick: () => onChangeLayoutMode("real"),
69625
69724
  style: {
69626
- ...navButtonStyle,
69627
- opacity: atLatest ? 0.35 : 1,
69628
- cursor: atLatest ? "default" : "pointer"
69725
+ ...iconToggleButtonStyle(layoutMode === "real"),
69726
+ ...connected ? {} : mutedStyle
69629
69727
  },
69630
- children: "\u25B6"
69728
+ children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(IconScatter, { size: 13 })
69631
69729
  }
69632
69730
  )
69633
69731
  ] }),
69634
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", gap: 2 }, children: [
69635
- SCOPES.map((option2) => /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69636
- "button",
69732
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
69733
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69734
+ Checkbox,
69637
69735
  {
69638
- "aria-pressed": !zoomed && scope === option2,
69736
+ label: "Code",
69737
+ checked: showCommits,
69738
+ activeColor: GUI_THEME.green,
69639
69739
  disabled: !connected,
69640
- onClick: () => onChangeScope(option2),
69641
- style: {
69642
- ...scopeButtonStyle(!zoomed && scope === option2),
69643
- ...connected ? {} : mutedStyle
69644
- },
69645
- children: scopeButtonLabel(option2)
69646
- },
69647
- option2
69648
- )),
69740
+ onChange: onChangeShowCommits
69741
+ }
69742
+ ),
69649
69743
  /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69650
- "button",
69744
+ BoardSeriesGroup,
69651
69745
  {
69652
- title: zoomed ? "A window dragged out on the chart \u2014 pick a period to leave it" : "Drag across the chart to zoom the window to a stretch of it",
69653
- "aria-pressed": zoomed,
69654
- disabled: true,
69655
- style: {
69656
- ...scopeButtonStyle(zoomed),
69657
- opacity: zoomed ? 1 : 0.35,
69658
- cursor: "default"
69659
- },
69660
- children: "Zoom"
69746
+ connected,
69747
+ showIssues,
69748
+ view: boardView,
69749
+ identities,
69750
+ hiddenIds: hiddenIdentityIds,
69751
+ expanded: categoriesExpanded,
69752
+ identitiesExpanded,
69753
+ filtered: categoriesFiltered,
69754
+ onChangeShowIssues,
69755
+ onChangeView: onChangeBoardView,
69756
+ onToggleIdentity,
69757
+ onOnlyIdentity,
69758
+ onToggleExpanded: onToggleCategoriesExpanded,
69759
+ onSetIdentitiesExpanded
69760
+ }
69761
+ ),
69762
+ /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69763
+ Checkbox,
69764
+ {
69765
+ label: SCOPE_ONLY_LABEL,
69766
+ title: everythingInScope ? "Every event is in scope \u2014 pick a period to narrow the board" : !windowFilterable ? "Too many events in this window to tell which tickets they belong to" : "Show only tickets with activity in the selected window",
69767
+ checked: windowOnly,
69768
+ disabled: everythingInScope || !windowFilterable || !connected && !windowOnly,
69769
+ onChange: onChangeWindowOnly
69661
69770
  }
69662
69771
  )
69663
- ] })
69664
- ] }),
69665
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", gap: 2 }, children: [
69666
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69667
- "button",
69668
- {
69669
- title: "Volume \u2014 how much happened, per equal-width period, with no empty gaps for quiet stretches",
69670
- "aria-label": "Volume",
69671
- "aria-pressed": layoutMode === "even",
69672
- disabled: !connected,
69673
- onClick: () => onChangeLayoutMode("even"),
69674
- style: {
69675
- ...iconToggleButtonStyle(layoutMode === "even"),
69676
- ...connected ? {} : mutedStyle
69677
- },
69678
- children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(IconBars, { size: 13 })
69679
- }
69680
- ),
69772
+ ] }),
69681
69773
  /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69682
69774
  "button",
69683
69775
  {
69684
- title: "Events \u2014 individual events by exact moment, x is elapsed time and y is time of day",
69685
- "aria-label": "Events",
69686
- "aria-pressed": layoutMode === "real",
69687
- disabled: !connected,
69688
- onClick: () => onChangeLayoutMode("real"),
69776
+ onClick: onReturnToLive,
69777
+ disabled: !isScrubbing,
69778
+ title: isScrubbing ? "Leave history and follow the board again" : void 0,
69689
69779
  style: {
69690
- ...iconToggleButtonStyle(layoutMode === "real"),
69691
- ...connected ? {} : mutedStyle
69780
+ background: "transparent",
69781
+ border: `1px solid ${isScrubbing ? GUI_THEME.accent : GUI_THEME.transparent}`,
69782
+ color: isScrubbing ? GUI_THEME.accent : GUI_THEME.dim,
69783
+ borderRadius: 6,
69784
+ fontFamily: "inherit",
69785
+ width: 60,
69786
+ display: "inline-flex",
69787
+ alignItems: "center",
69788
+ justifyContent: isScrubbing ? "center" : "flex-end",
69789
+ fontSize: 10,
69790
+ padding: "2px 8px",
69791
+ cursor: isScrubbing ? "pointer" : "default",
69792
+ whiteSpace: "nowrap",
69793
+ flexShrink: 0
69692
69794
  },
69693
- children: /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(IconScatter, { size: 13 })
69795
+ children: isScrubbing ? "Resume" : atLatest ? "Now" : ""
69694
69796
  }
69695
69797
  )
69696
- ] }),
69697
- /* @__PURE__ */ (0, import_jsx_runtime65.jsxs)("div", { style: { display: "flex", gap: 12, alignItems: "center" }, children: [
69698
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69699
- Checkbox,
69700
- {
69701
- label: "Code",
69702
- checked: showCommits,
69703
- activeColor: GUI_THEME.green,
69704
- disabled: !connected,
69705
- onChange: onChangeShowCommits
69706
- }
69707
- ),
69708
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69709
- BoardSeriesGroup,
69710
- {
69711
- connected,
69712
- showIssues,
69713
- view: boardView,
69714
- identities,
69715
- hiddenIds: hiddenIdentityIds,
69716
- expanded: categoriesExpanded,
69717
- identitiesExpanded,
69718
- filtered: categoriesFiltered,
69719
- onChangeShowIssues,
69720
- onChangeView: onChangeBoardView,
69721
- onToggleIdentity,
69722
- onOnlyIdentity,
69723
- onToggleExpanded: onToggleCategoriesExpanded,
69724
- onSetIdentitiesExpanded
69725
- }
69726
- )
69727
- ] }),
69728
- /* @__PURE__ */ (0, import_jsx_runtime65.jsx)(
69729
- "button",
69730
- {
69731
- onClick: onReturnToLive,
69732
- disabled: !isScrubbing,
69733
- title: isScrubbing ? "Leave history and follow the board again" : void 0,
69734
- style: {
69735
- background: "transparent",
69736
- border: `1px solid ${isScrubbing ? GUI_THEME.accent : GUI_THEME.transparent}`,
69737
- color: isScrubbing ? GUI_THEME.accent : GUI_THEME.dim,
69738
- borderRadius: 6,
69739
- fontFamily: "inherit",
69740
- width: 60,
69741
- display: "inline-flex",
69742
- alignItems: "center",
69743
- justifyContent: isScrubbing ? "center" : "flex-end",
69744
- fontSize: 10,
69745
- padding: "2px 8px",
69746
- cursor: isScrubbing ? "pointer" : "default",
69747
- whiteSpace: "nowrap",
69748
- flexShrink: 0
69749
- },
69750
- children: isScrubbing ? "Resume" : atLatest ? "Now" : ""
69751
- }
69752
- )
69753
- ]
69754
- }
69755
- );
69798
+ ]
69799
+ }
69800
+ );
69801
+ };
69756
69802
  var ScrubberHeader = ({
69757
69803
  collapsed,
69758
69804
  onToggleCollapsed
@@ -70334,7 +70380,21 @@ var ScrubberLayout = ({
70334
70380
  onToggleCollapsed
70335
70381
  }
70336
70382
  ),
70337
- !collapsed && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(ScrubberControls, { ...controls })
70383
+ collapsed ? (
70384
+ // The narrowing outlives the chart it belongs to — a link can
70385
+ // arrive with the scrubber shut, and collapsing it is remembered
70386
+ // — so its box comes up here rather than leaving the board
70387
+ // hiding tickets behind a control nobody can see.
70388
+ controls.windowOnly && /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(
70389
+ Checkbox,
70390
+ {
70391
+ label: SCOPE_ONLY_LABEL,
70392
+ title: "Show every ticket again",
70393
+ checked: true,
70394
+ onChange: controls.onChangeWindowOnly
70395
+ }
70396
+ )
70397
+ ) : /* @__PURE__ */ (0, import_jsx_runtime66.jsx)(ScrubberControls, { ...controls })
70338
70398
  ]
70339
70399
  }
70340
70400
  ),
@@ -70360,6 +70420,12 @@ var ScrubberLayout = ({
70360
70420
  // Crosshair, not a hand: a press picks a moment but a drag picks
70361
70421
  // out a range, and the pointer has to say the second is on offer.
70362
70422
  cursor: chart.connected ? "crosshair" : "default",
70423
+ // Outline rather than a border: it takes up no space, so
70424
+ // narrowing the board cannot reflow the charts under the
70425
+ // pointer that just clicked.
70426
+ outline: chart.scoped ? `1px solid ${SCOPED_OUTLINE_COLOR}` : void 0,
70427
+ outlineOffset: 5,
70428
+ borderRadius: 4,
70363
70429
  // A drag must never turn into a native text selection or a drag
70364
70430
  // of the axis labels underneath the pointer.
70365
70431
  userSelect: "none",
@@ -70530,7 +70596,8 @@ var TimeScrubber = ({
70530
70596
  onInspectCommit,
70531
70597
  selection,
70532
70598
  onChangeSelection,
70533
- knownIdentities
70599
+ knownIdentities,
70600
+ refreshOn
70534
70601
  }) => {
70535
70602
  const {
70536
70603
  scope,
@@ -70538,7 +70605,8 @@ var TimeScrubber = ({
70538
70605
  zoom,
70539
70606
  layout: layoutMode,
70540
70607
  view: boardView,
70541
- only
70608
+ only,
70609
+ windowOnly
70542
70610
  } = selection;
70543
70611
  const animate = !usePrefersReducedMotion();
70544
70612
  const trackRef = (0, import_react36.useRef)(null);
@@ -70598,7 +70666,8 @@ var TimeScrubber = ({
70598
70666
  boardId,
70599
70667
  allBoards,
70600
70668
  connected,
70601
- socketEpoch
70669
+ socketEpoch,
70670
+ refreshOn
70602
70671
  ]);
70603
70672
  const changeLayoutMode = (next) => onChangeSelection({ layout: next });
70604
70673
  const changeScope = (nextScope) => {
@@ -70901,12 +70970,15 @@ var TimeScrubber = ({
70901
70970
  periodRange,
70902
70971
  zoomed: zoom !== null,
70903
70972
  atLatest,
70973
+ windowOnly,
70974
+ windowFilterable: windowNamesIssues(timeline),
70904
70975
  layoutMode,
70905
70976
  showIssues,
70906
70977
  showCommits,
70907
70978
  allBoards,
70908
70979
  onChangeScope: changeScope,
70909
70980
  onChangeOffset: changeOffset,
70981
+ onChangeWindowOnly: (next) => onChangeSelection({ windowOnly: next }),
70910
70982
  onChangeLayoutMode: changeLayoutMode,
70911
70983
  onChangeShowIssues: setShowIssues,
70912
70984
  onChangeShowCommits: setShowCommits,
@@ -70926,6 +70998,10 @@ var TimeScrubber = ({
70926
70998
  onSetIdentitiesExpanded: setIdentitiesExpanded
70927
70999
  },
70928
71000
  chart: {
71001
+ // Only where it is actually hiding something: ticked over a window
71002
+ // that narrows nothing draws no outline, the way the box itself goes
71003
+ // flat there.
71004
+ scoped: windowOnly && isPeriodWindow(scope, zoom !== null) && windowNamesIssues(timeline),
70929
71005
  trackRef,
70930
71006
  axis,
70931
71007
  layoutMode,
@@ -71175,10 +71251,12 @@ var import_react38 = __toESM(require_react(), 1);
71175
71251
  var useBoardSelection = () => {
71176
71252
  const [searchParams, setSearchParams] = useSearchParams();
71177
71253
  const fromUrl = hasSelectionParams(searchParams);
71178
- const selection = (0, import_react38.useMemo)(
71179
- () => readSelectionParams(searchParams) ?? readStoredSelection(),
71180
- [searchParams]
71181
- );
71254
+ const windowOnly = (0, import_react38.useRef)(false);
71255
+ const selection = (0, import_react38.useMemo)(() => {
71256
+ const fromParams = readSelectionParams(searchParams);
71257
+ return fromParams ?? { ...readStoredSelection(), windowOnly: windowOnly.current };
71258
+ }, [searchParams]);
71259
+ windowOnly.current = selection.windowOnly;
71182
71260
  (0, import_react38.useEffect)(() => {
71183
71261
  if (fromUrl) {
71184
71262
  storeSelection(selection);
@@ -71483,6 +71561,11 @@ var App = () => {
71483
71561
  () => buildBoardFilter(selection.view, selection.only),
71484
71562
  [selection.view, selection.only]
71485
71563
  );
71564
+ const zoomed = selection.zoom !== null;
71565
+ const windowIds = (0, import_react39.useMemo)(
71566
+ () => selection.windowOnly && isPeriodWindow(selection.scope, zoomed) ? windowIssueIds(history.timeline) : null,
71567
+ [selection.windowOnly, selection.scope, zoomed, history.timeline]
71568
+ );
71486
71569
  const isolatedTagId = selection.view === "tagging" && selection.only?.length === 1 ? selection.only[0] ?? null : null;
71487
71570
  const filterByTag = (tagId) => changeSelection(
71488
71571
  isolatedTagId === tagId ? { only: null } : { view: "tagging", only: [tagId] }
@@ -71498,7 +71581,7 @@ var App = () => {
71498
71581
  const { visibleSwimlanes, hiddenIssueCount } = (0, import_react39.useMemo)(() => {
71499
71582
  const swimlanes = selectedBoard?.swimlanes ?? [];
71500
71583
  const query = textFilter.trim();
71501
- if (!boardFilter && !query)
71584
+ if (!boardFilter && !query && windowIds === null)
71502
71585
  return { visibleSwimlanes: swimlanes, hiddenIssueCount: 0 };
71503
71586
  let hidden = 0;
71504
71587
  const visible = swimlanes.map((swimlane) => {
@@ -71509,13 +71592,13 @@ var App = () => {
71509
71592
  (comment2) => comment2.author.id
71510
71593
  ),
71511
71594
  boardFilter
71512
- ) && issueMatchesText(issue, query)
71595
+ ) && issueMatchesText(issue, query) && (windowIds === null || windowIds.has(issue.id))
71513
71596
  );
71514
71597
  hidden += swimlane.issues.length - issues.length;
71515
71598
  return { ...swimlane, issues };
71516
71599
  });
71517
71600
  return { visibleSwimlanes: visible, hiddenIssueCount: hidden };
71518
- }, [selectedBoard, boardFilter, textFilter, commentsByIssueId]);
71601
+ }, [selectedBoard, boardFilter, textFilter, commentsByIssueId, windowIds]);
71519
71602
  const attachmentsByIssueId = state?.attachmentsByIssueId ?? {};
71520
71603
  const [attachmentUploadStatus, setAttachmentUploadStatus] = (0, import_react39.useState)({ state: "idle" });
71521
71604
  (0, import_react39.useEffect)(() => {
@@ -72337,7 +72420,8 @@ var App = () => {
72337
72420
  onReturnToLive: returnToLive,
72338
72421
  selection,
72339
72422
  onChangeSelection: changeSelection,
72340
- knownIdentities
72423
+ knownIdentities,
72424
+ refreshOn: selection.windowOnly ? state : null
72341
72425
  }
72342
72426
  ),
72343
72427
  /* @__PURE__ */ (0, import_jsx_runtime68.jsxs)(