mintree 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -23,11 +23,19 @@ export const description = "Interactive dashboard listing open issues assigned t
23
23
  function isOrphan(d) {
24
24
  return d.orphan === true;
25
25
  }
26
- function tabIssues(issues, tab) {
27
- return issues.filter((d) => (tab === "issues" ? !isOrphan(d) : isOrphan(d)));
26
+ // Matches an issue against the live numeric filter by substring on the digit
27
+ // portion of its id ("PLA-234" "234", "BE-34" → "34"). Letters are ignored,
28
+ // so the user filters by ticket number alone. Empty filter matches everything.
29
+ function issueMatchesFilter(d, filter) {
30
+ if (!filter)
31
+ return true;
32
+ return d.issue.id.replace(/\D/g, "").includes(filter);
33
+ }
34
+ function tabIssues(issues, tab, filter = "") {
35
+ return issues.filter((d) => (tab === "issues" ? !isOrphan(d) : isOrphan(d)) && issueMatchesFilter(d, filter));
28
36
  }
29
37
  function currentSelected(s) {
30
- const displayed = tabIssues(s.issues, s.activeTab);
38
+ const displayed = tabIssues(s.issues, s.activeTab, s.filter);
31
39
  const selectedIndex = s.activeTab === "issues" ? s.issuesIndex : s.worktreesIndex;
32
40
  return { displayed, selectedIndex };
33
41
  }
@@ -186,7 +194,7 @@ function FooterRow({ phase, overlayKind, latestVersion, listWidth, }) {
186
194
  // align under the left (list) pane; ticket-specific actions align under
187
195
  // the right (detail) pane. Falls back to a single inline row when no
188
196
  // width hint is available (e.g. the error path).
189
- const common = (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "j/k" }), _jsx(Text, { dimColor: true, children: " nav " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " PgUp/PgDn" }), _jsx(Text, { dimColor: true, children: " scroll " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " r" }), _jsx(Text, { dimColor: true, children: " refresh " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " q" }), _jsx(Text, { dimColor: true, children: " quit" })] }));
197
+ const common = (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "j/k" }), _jsx(Text, { dimColor: true, children: " nav " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " PgUp/PgDn" }), _jsx(Text, { dimColor: true, children: " scroll " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " r" }), _jsx(Text, { dimColor: true, children: " refresh " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " #" }), _jsx(Text, { dimColor: true, children: " filter " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " q" }), _jsx(Text, { dimColor: true, children: " quit" })] }));
190
198
  const ticket = (_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "\u21B5" }), _jsx(Text, { dimColor: true, children: " Switch " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " w" }), _jsx(Text, { dimColor: true, children: " Work " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " o" }), _jsx(Text, { dimColor: true, children: " Open " }), _jsx(Text, { dimColor: true, children: "\u00B7" }), _jsx(Text, { bold: true, children: " d" }), _jsx(Text, { dimColor: true, children: " Remove" })] }));
191
199
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: listWidth, children: common }), _jsx(Box, { flexGrow: 1, children: ticket })] }), latestVersion && (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "(*)" }), _jsx(Text, { dimColor: true, children: ` new version available — v${latestVersion} · npm i -g mintree` })] }))] }));
192
200
  }
@@ -674,14 +682,15 @@ export default function Dashboard() {
674
682
  const previousOverlay = prevReady?.overlay ?? null;
675
683
  const previousToast = prevReady?.toast ?? null;
676
684
  const previousScroll = prevReady?.detailScrollOffset ?? 0;
677
- const issuesList = tabIssues(issues, "issues");
678
- const worktreesList = tabIssues(issues, "worktrees");
685
+ const filter = prevReady?.filter ?? "";
686
+ const issuesList = tabIssues(issues, "issues", filter);
687
+ const worktreesList = tabIssues(issues, "worktrees", filter);
679
688
  const issuesIndex = Math.min(previousIssuesIndex, Math.max(0, issuesList.length - 1));
680
689
  const worktreesIndex = Math.min(previousWorktreesIndex, Math.max(0, worktreesList.length - 1));
681
690
  // Preserve scroll only when the active tab's selected issue still
682
691
  // resolves to the same row — clamping or list churn means the user
683
692
  // is now reading something else.
684
- const prevDisplayed = prevReady ? tabIssues(prevReady.issues, activeTab) : [];
693
+ const prevDisplayed = prevReady ? tabIssues(prevReady.issues, activeTab, filter) : [];
685
694
  const nextDisplayed = activeTab === "issues" ? issuesList : worktreesList;
686
695
  const prevSelectedId = prevDisplayed[activeTab === "issues" ? previousIssuesIndex : previousWorktreesIndex]?.issue
687
696
  .id ?? null;
@@ -697,6 +706,7 @@ export default function Dashboard() {
697
706
  refreshing: false,
698
707
  overlay: previousOverlay,
699
708
  toast: previousToast,
709
+ filter,
700
710
  };
701
711
  });
702
712
  };
@@ -818,12 +828,42 @@ export default function Dashboard() {
818
828
  handleOverlayInput(input, key);
819
829
  return;
820
830
  }
831
+ // Esc clears an active numeric filter before it falls through to quit —
832
+ // so the user can back out of a search without leaving the dashboard.
833
+ if (key.escape && state.phase === "ready" && state.filter) {
834
+ setState({ ...state, filter: "", issuesIndex: 0, worktreesIndex: 0, detailScrollOffset: 0 });
835
+ return;
836
+ }
821
837
  if (input === "q" || key.escape || (input === "c" && key.ctrl)) {
822
838
  exit();
823
839
  return;
824
840
  }
825
841
  if (state.phase !== "ready")
826
842
  return;
843
+ // Numeric filter: typing a digit narrows the list by ticket number
844
+ // (matched on the digits of the id, so "34" hits both PLA-234 and BE-34).
845
+ // Backspace pops a digit; Esc (handled above) clears it. Reset selection
846
+ // to the first match so the cursor stays on a visible row as it narrows.
847
+ if (/^[0-9]$/.test(input)) {
848
+ setState({
849
+ ...state,
850
+ filter: state.filter + input,
851
+ issuesIndex: 0,
852
+ worktreesIndex: 0,
853
+ detailScrollOffset: 0,
854
+ });
855
+ return;
856
+ }
857
+ if ((key.backspace || key.delete) && state.filter) {
858
+ setState({
859
+ ...state,
860
+ filter: state.filter.slice(0, -1),
861
+ issuesIndex: 0,
862
+ worktreesIndex: 0,
863
+ detailScrollOffset: 0,
864
+ });
865
+ return;
866
+ }
827
867
  if (key.leftArrow || key.rightArrow) {
828
868
  // Two tabs only — either arrow toggles. Per-tab indices are
829
869
  // preserved, so the user returns to the row they left.
@@ -1172,7 +1212,7 @@ export default function Dashboard() {
1172
1212
  if (state.phase === "error") {
1173
1213
  return (_jsxs(Box, { width: columns, height: rows, flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 1, children: [_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", state.message] }), state.hint && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "yellow", children: ["\u21B3 ", state.hint] }) })), _jsx(Box, { marginTop: 1, children: _jsx(FooterRow, { phase: "error" }) })] }));
1174
1214
  }
1175
- const { issues, refreshing, overlay, toast, activeTab } = state;
1215
+ const { issues, refreshing, overlay, toast, activeTab, filter } = state;
1176
1216
  const { displayed, selectedIndex } = currentSelected(state);
1177
1217
  const selected = displayed[selectedIndex] ?? null;
1178
1218
  const issuesTabCount = issues.reduce((n, d) => (isOrphan(d) ? n : n + 1), 0);
@@ -1224,7 +1264,9 @@ export default function Dashboard() {
1224
1264
  : displayed.map((d, index) => ({ kind: "issue", d, index }));
1225
1265
  const listView = windowListRows(listRows, selectedIndex, listVisibleRows);
1226
1266
  const listContentWidth = Math.max(8, listWidth - 4);
1227
- return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsx(Box, { paddingX: 1, paddingTop: 0, flexDirection: "column", children: _jsx(HeaderRow, { repoName: repoName, claudeVersion: claudeVersion, issueCount: issuesTabCount, worktreeCount: worktreesTabCount, activeTab: activeTab, updateAvailable: latestVersion !== null }) }), overlay ? (_jsx(Box, { flexGrow: 1, flexDirection: "column", borderStyle: "round", borderColor: overlay.kind === "remove" ? "yellow" : "cyan", children: overlay.kind === "create" ? (_jsx(CreateOverlayView, { overlay: overlay, onDescChange: onOverlayDescChange, onPromptChange: onOverlayPromptChange })) : (_jsx(RemoveOverlayView, { overlay: overlay })) })) : (_jsxs(Box, { flexGrow: 1, flexDirection: "row", children: [_jsx(Box, { width: listWidth, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: displayed.length === 0 ? (_jsx(Text, { dimColor: true, children: activeTab === "issues"
1228
- ? "No open issues assigned to you in this repo."
1229
- : "No orphaned worktrees — anything in `.mintree/worktrees/` matches an open issue." })) : (_jsxs(_Fragment, { children: [listView.sticky.map((row, i) => (_jsx(ListRowView, { row: row, selectedIndex: selectedIndex, identifierWidth: identifierWidth, width: listContentWidth }, `sticky-${i}`))), listView.issuesAbove > 0 && (_jsxs(Text, { dimColor: true, children: ["\u2191 ", listView.issuesAbove, " more above"] })), listView.body.map((row, i) => (_jsx(ListRowView, { row: row, selectedIndex: selectedIndex, identifierWidth: identifierWidth, width: listContentWidth }, `body-${i}`))), listView.issuesBelow > 0 && (_jsxs(Text, { dimColor: true, children: ["\u2193 ", listView.issuesBelow, " more below"] }))] })) }), _jsx(Box, { width: detailWidth, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsx(DetailPane, { d: selected, contentWidth: detailWidth - 4, contentHeight: detailContentHeight, scrollOffset: state.detailScrollOffset }) })] })), _jsxs(Box, { paddingX: 1, flexDirection: "column", children: [toast && (_jsx(Box, { children: _jsxs(Text, { color: toast.kind === "success" ? "green" : toast.kind === "error" ? "red" : "cyan", children: [toast.kind === "success" ? "✓ " : toast.kind === "error" ? "✗ " : "· ", toast.text] }) })), refreshing && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { dimColor: true, children: " refreshing" })] })), _jsx(FooterRow, { phase: "ready", overlayKind: overlay?.kind, latestVersion: latestVersion, listWidth: listWidth })] })] }));
1267
+ return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsx(Box, { paddingX: 1, paddingTop: 0, flexDirection: "column", children: _jsx(HeaderRow, { repoName: repoName, claudeVersion: claudeVersion, issueCount: issuesTabCount, worktreeCount: worktreesTabCount, activeTab: activeTab, updateAvailable: latestVersion !== null }) }), overlay ? (_jsx(Box, { flexGrow: 1, flexDirection: "column", borderStyle: "round", borderColor: overlay.kind === "remove" ? "yellow" : "cyan", children: overlay.kind === "create" ? (_jsx(CreateOverlayView, { overlay: overlay, onDescChange: onOverlayDescChange, onPromptChange: onOverlayPromptChange })) : (_jsx(RemoveOverlayView, { overlay: overlay })) })) : (_jsxs(Box, { flexGrow: 1, flexDirection: "row", children: [_jsx(Box, { width: listWidth, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: displayed.length === 0 ? (_jsx(Text, { dimColor: true, children: filter
1268
+ ? `No tickets match #${filter} — Esc to clear the filter.`
1269
+ : activeTab === "issues"
1270
+ ? "No open issues assigned to you in this repo."
1271
+ : "No orphaned worktrees — anything in `.mintree/worktrees/` matches an open issue." })) : (_jsxs(_Fragment, { children: [listView.sticky.map((row, i) => (_jsx(ListRowView, { row: row, selectedIndex: selectedIndex, identifierWidth: identifierWidth, width: listContentWidth }, `sticky-${i}`))), listView.issuesAbove > 0 && (_jsxs(Text, { dimColor: true, children: ["\u2191 ", listView.issuesAbove, " more above"] })), listView.body.map((row, i) => (_jsx(ListRowView, { row: row, selectedIndex: selectedIndex, identifierWidth: identifierWidth, width: listContentWidth }, `body-${i}`))), listView.issuesBelow > 0 && (_jsxs(Text, { dimColor: true, children: ["\u2193 ", listView.issuesBelow, " more below"] }))] })) }), _jsx(Box, { width: detailWidth, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsx(DetailPane, { d: selected, contentWidth: detailWidth - 4, contentHeight: detailContentHeight, scrollOffset: state.detailScrollOffset }) })] })), _jsxs(Box, { paddingX: 1, flexDirection: "column", children: [filter && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: `⌕ #${filter}` }), _jsx(Text, { dimColor: true, children: ` · ${displayed.length} match${displayed.length === 1 ? "" : "es"} · Esc clear` })] })), toast && (_jsx(Box, { children: _jsxs(Text, { color: toast.kind === "success" ? "green" : toast.kind === "error" ? "red" : "cyan", children: [toast.kind === "success" ? "✓ " : toast.kind === "error" ? "✗ " : "· ", toast.text] }) })), refreshing && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { dimColor: true, children: " refreshing" })] })), _jsx(FooterRow, { phase: "ready", overlayKind: overlay?.kind, latestVersion: latestVersion, listWidth: listWidth })] })] }));
1230
1272
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mintree",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Issue-driven git worktrees + Claude Code sessions for repos with an opinionated SDD+TDD flow.",
5
5
  "license": "MIT",
6
6
  "author": "Martin Mineo <mmineo@canarytechnologies.com>",