@ondrej-svec/hog 1.8.1 → 1.9.0

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/cli.js CHANGED
@@ -1998,7 +1998,8 @@ function useKeyboard({
1998
1998
  selectedIssue,
1999
1999
  selectedRepoStatusOptionsLength,
2000
2000
  actions,
2001
- onSearchEscape
2001
+ onSearchEscape,
2002
+ tabNav
2002
2003
  }) {
2003
2004
  const {
2004
2005
  exit,
@@ -2046,14 +2047,14 @@ function useKeyboard({
2046
2047
  multiSelect.clear();
2047
2048
  ui.clearMultiSelect();
2048
2049
  }
2049
- key.shift ? nav.prevSection() : nav.nextSection();
2050
+ key.shift ? tabNav.prev() : tabNav.next();
2050
2051
  return;
2051
2052
  }
2052
2053
  }
2053
2054
  if (ui.state.mode === "multiSelect") {
2054
2055
  if (input2 === " ") {
2055
2056
  const id = nav.selectedId;
2056
- if (id && !isHeaderId(id)) {
2057
+ if (id) {
2057
2058
  multiSelect.toggle(id);
2058
2059
  }
2059
2060
  return;
@@ -2075,6 +2076,11 @@ function useKeyboard({
2075
2076
  }
2076
2077
  if (input2 === "r" && handleErrorAction("retry")) return;
2077
2078
  if (ui.canAct) {
2079
+ const digit = parseInt(input2, 10);
2080
+ if (!Number.isNaN(digit) && digit >= 1 && digit <= tabNav.count) {
2081
+ tabNav.jumpTo(digit - 1);
2082
+ return;
2083
+ }
2078
2084
  if (input2 === "/") {
2079
2085
  multiSelect.clear();
2080
2086
  ui.enterSearch();
@@ -2138,10 +2144,6 @@ function useKeyboard({
2138
2144
  handleEnterFocus();
2139
2145
  return;
2140
2146
  }
2141
- if (input2 === "C") {
2142
- nav.collapseAll();
2143
- return;
2144
- }
2145
2147
  if (input2 === "l") {
2146
2148
  if (selectedIssue) {
2147
2149
  multiSelect.clear();
@@ -2169,19 +2171,13 @@ function useKeyboard({
2169
2171
  }
2170
2172
  if (input2 === " ") {
2171
2173
  const id = nav.selectedId;
2172
- if (id && !isHeaderId(id)) {
2174
+ if (id) {
2173
2175
  multiSelect.toggle(id);
2174
2176
  ui.enterMultiSelect();
2175
- } else if (isHeaderId(nav.selectedId)) {
2176
- nav.toggleSection();
2177
2177
  }
2178
2178
  return;
2179
2179
  }
2180
2180
  if (key.return) {
2181
- if (isHeaderId(nav.selectedId)) {
2182
- nav.toggleSection();
2183
- return;
2184
- }
2185
2181
  handleOpen();
2186
2182
  return;
2187
2183
  }
@@ -2190,6 +2186,7 @@ function useKeyboard({
2190
2186
  [
2191
2187
  ui,
2192
2188
  nav,
2189
+ tabNav,
2193
2190
  exit,
2194
2191
  refresh,
2195
2192
  handleSlack,
@@ -2228,7 +2225,6 @@ function useKeyboard({
2228
2225
  var init_use_keyboard = __esm({
2229
2226
  "src/board/hooks/use-keyboard.ts"() {
2230
2227
  "use strict";
2231
- init_constants();
2232
2228
  }
2233
2229
  });
2234
2230
 
@@ -2989,14 +2985,7 @@ var init_detail_panel = __esm({
2989
2985
  // src/board/components/hint-bar.tsx
2990
2986
  import { Box as Box3, Text as Text3 } from "ink";
2991
2987
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2992
- function HintBar({
2993
- uiMode,
2994
- multiSelectCount,
2995
- searchQuery,
2996
- mineOnly,
2997
- hasUndoable,
2998
- onHeader
2999
- }) {
2988
+ function HintBar({ uiMode, multiSelectCount, searchQuery, mineOnly, hasUndoable }) {
3000
2989
  if (uiMode === "multiSelect") {
3001
2990
  return /* @__PURE__ */ jsxs3(Box3, { children: [
3002
2991
  /* @__PURE__ */ jsxs3(Text3, { color: "cyan", bold: true, children: [
@@ -3027,20 +3016,9 @@ function HintBar({
3027
3016
  if (uiMode.startsWith("overlay:")) {
3028
3017
  return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "j/k:nav Enter:select Esc:cancel" }) });
3029
3018
  }
3030
- if (onHeader) {
3031
- return /* @__PURE__ */ jsxs3(Box3, { children: [
3032
- /* @__PURE__ */ jsx3(Text3, { color: "gray", children: "j/k:nav Enter/Space:expand-collapse Tab:next-section C:collapse-all ?:more q:quit" }),
3033
- mineOnly ? /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: " filter:@me" }) : null,
3034
- searchQuery ? /* @__PURE__ */ jsxs3(Text3, { color: "yellow", children: [
3035
- ' filter:"',
3036
- searchQuery,
3037
- '"'
3038
- ] }) : null
3039
- ] });
3040
- }
3041
3019
  return /* @__PURE__ */ jsxs3(Box3, { children: [
3042
3020
  /* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
3043
- "j/k:nav Enter:open m:status c:comment F:find t:@me e:edit L:log",
3021
+ "j/k:nav Tab:next-tab 1-9:jump Enter:open m:status c:comment F:find t:@me e:edit",
3044
3022
  hasUndoable ? " u:undo" : "",
3045
3023
  " ?:more q:quit"
3046
3024
  ] }),
@@ -4089,15 +4067,16 @@ var init_help_overlay = __esm({
4089
4067
  items: [
4090
4068
  { key: "j / Down", desc: "Move down" },
4091
4069
  { key: "k / Up", desc: "Move up" },
4092
- { key: "Tab", desc: "Next section" },
4093
- { key: "Shift+Tab", desc: "Previous section" }
4070
+ { key: "Tab", desc: "Next tab" },
4071
+ { key: "Shift+Tab", desc: "Previous tab" },
4072
+ { key: "1-9", desc: "Jump to tab by number" }
4094
4073
  ]
4095
4074
  },
4096
4075
  {
4097
4076
  category: "View",
4098
4077
  items: [
4099
- { key: "Enter", desc: "Toggle section / Open in browser" },
4100
- { key: "Space", desc: "Toggle section / Multi-select" },
4078
+ { key: "Enter", desc: "Open issue in browser" },
4079
+ { key: "Space", desc: "Multi-select item" },
4101
4080
  { key: "/", desc: "Search (inline filter)" },
4102
4081
  { key: "F", desc: "Fuzzy find issue (telescope-style)" },
4103
4082
  { key: "t", desc: "Toggle @me filter (my issues only)" },
@@ -4126,7 +4105,6 @@ var init_help_overlay = __esm({
4126
4105
  category: "Board",
4127
4106
  items: [
4128
4107
  { key: "L", desc: "Toggle action log" },
4129
- { key: "C", desc: "Collapse all sections" },
4130
4108
  { key: "r", desc: "Refresh data" },
4131
4109
  { key: "q", desc: "Quit" }
4132
4110
  ]
@@ -4919,23 +4897,45 @@ var init_row_renderer = __esm({
4919
4897
  }
4920
4898
  });
4921
4899
 
4900
+ // src/board/components/tab-bar.tsx
4901
+ import { Box as Box19, Text as Text19 } from "ink";
4902
+ import { jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
4903
+ function TabBar({ tabs, activeTabId, totalWidth }) {
4904
+ return /* @__PURE__ */ jsx20(Box19, { width: totalWidth, children: tabs.map((tab, i) => {
4905
+ const isActive = tab.id === activeTabId;
4906
+ return /* @__PURE__ */ jsx20(Box19, { marginRight: 2, children: /* @__PURE__ */ jsxs20(Text19, { bold: isActive, color: isActive ? "cyan" : "gray", children: [
4907
+ i + 1,
4908
+ ":",
4909
+ tab.label,
4910
+ " (",
4911
+ tab.count,
4912
+ ")"
4913
+ ] }) }, tab.id);
4914
+ }) });
4915
+ }
4916
+ var init_tab_bar = __esm({
4917
+ "src/board/components/tab-bar.tsx"() {
4918
+ "use strict";
4919
+ }
4920
+ });
4921
+
4922
4922
  // src/board/components/toast-container.tsx
4923
4923
  import { Spinner as Spinner3 } from "@inkjs/ui";
4924
- import { Box as Box19, Text as Text19 } from "ink";
4925
- import { Fragment as Fragment4, jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
4924
+ import { Box as Box20, Text as Text20 } from "ink";
4925
+ import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs21 } from "react/jsx-runtime";
4926
4926
  function ToastContainer({ toasts }) {
4927
4927
  if (toasts.length === 0) return null;
4928
- return /* @__PURE__ */ jsx20(Box19, { flexDirection: "column", children: toasts.map((t) => /* @__PURE__ */ jsx20(Box19, { children: t.type === "loading" ? /* @__PURE__ */ jsxs20(Fragment4, { children: [
4929
- /* @__PURE__ */ jsx20(Spinner3, { label: "" }),
4930
- /* @__PURE__ */ jsxs20(Text19, { color: "cyan", children: [
4928
+ return /* @__PURE__ */ jsx21(Box20, { flexDirection: "column", children: toasts.map((t) => /* @__PURE__ */ jsx21(Box20, { children: t.type === "loading" ? /* @__PURE__ */ jsxs21(Fragment4, { children: [
4929
+ /* @__PURE__ */ jsx21(Spinner3, { label: "" }),
4930
+ /* @__PURE__ */ jsxs21(Text20, { color: "cyan", children: [
4931
4931
  " ",
4932
4932
  t.message
4933
4933
  ] })
4934
- ] }) : /* @__PURE__ */ jsxs20(Text19, { color: TYPE_COLORS[t.type], children: [
4934
+ ] }) : /* @__PURE__ */ jsxs21(Text20, { color: TYPE_COLORS[t.type], children: [
4935
4935
  TYPE_PREFIXES[t.type],
4936
4936
  " ",
4937
4937
  t.message,
4938
- t.type === "error" ? /* @__PURE__ */ jsx20(Text19, { color: "gray", children: t.retry ? " [r]etry [d]ismiss" : " [d]ismiss" }) : null
4938
+ t.type === "error" ? /* @__PURE__ */ jsx21(Text20, { color: "gray", children: t.retry ? " [r]etry [d]ismiss" : " [d]ismiss" }) : null
4939
4939
  ] }) }, t.id)) });
4940
4940
  }
4941
4941
  var TYPE_COLORS, TYPE_PREFIXES;
@@ -4959,9 +4959,9 @@ var init_toast_container = __esm({
4959
4959
  // src/board/components/dashboard.tsx
4960
4960
  import { execFileSync as execFileSync3, spawnSync as spawnSync4 } from "child_process";
4961
4961
  import { Spinner as Spinner4 } from "@inkjs/ui";
4962
- import { Box as Box20, Text as Text20, useApp, useStdout } from "ink";
4962
+ import { Box as Box21, Text as Text21, useApp, useStdout } from "ink";
4963
4963
  import { useCallback as useCallback11, useEffect as useEffect9, useMemo as useMemo3, useRef as useRef13, useState as useState15 } from "react";
4964
- import { Fragment as Fragment5, jsx as jsx21, jsxs as jsxs21 } from "react/jsx-runtime";
4964
+ import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs22 } from "react/jsx-runtime";
4965
4965
  function resolveStatusGroups(statusOptions, configuredGroups) {
4966
4966
  if (configuredGroups && configuredGroups.length > 0) {
4967
4967
  return configuredGroups.map((entry) => {
@@ -5030,118 +5030,81 @@ function buildBoardTree(repos, tasks, activity) {
5030
5030
  });
5031
5031
  return { activity, sections, tasks };
5032
5032
  }
5033
- function buildNavItems(tree) {
5034
- const items = [];
5033
+ function buildTabs(tree) {
5034
+ const tabs = tree.sections.map(({ repo, groups }) => ({
5035
+ id: repo.name,
5036
+ label: repo.shortName,
5037
+ count: groups.reduce((s, g) => s + g.issues.length, 0)
5038
+ }));
5035
5039
  if (tree.activity.length > 0)
5036
- items.push({ id: "header:activity", section: "activity", type: "header" });
5037
- for (const { repo, sectionId, groups } of tree.sections) {
5038
- items.push({ id: `header:${sectionId}`, section: sectionId, type: "header" });
5039
- for (const group of groups) {
5040
- items.push({ id: group.subId, section: sectionId, type: "subHeader" });
5041
- for (const issue of group.issues) {
5042
- items.push({
5043
- id: `gh:${repo.name}:${issue.number}`,
5044
- section: sectionId,
5045
- type: "item",
5046
- subSection: group.subId
5047
- });
5048
- }
5049
- }
5050
- }
5051
- if (tree.tasks.length > 0) {
5052
- items.push({ id: "header:ticktick", section: "ticktick", type: "header" });
5053
- for (const task2 of tree.tasks)
5054
- items.push({ id: `tt:${task2.id}`, section: "ticktick", type: "item" });
5055
- }
5056
- return items;
5040
+ tabs.push({ id: "activity", label: "Activity", count: tree.activity.length });
5041
+ if (tree.tasks.length > 0)
5042
+ tabs.push({ id: "ticktick", label: "Tasks", count: tree.tasks.length });
5043
+ return tabs;
5044
+ }
5045
+ function buildNavItemsForTab(tabId, tree) {
5046
+ if (tabId === "activity") return [];
5047
+ if (tabId === "ticktick")
5048
+ return tree.tasks.map((task2) => ({
5049
+ id: `tt:${task2.id}`,
5050
+ section: tabId,
5051
+ type: "item"
5052
+ }));
5053
+ const section = tree.sections.find((s) => s.sectionId === tabId);
5054
+ if (!section) return [];
5055
+ return section.groups.flatMap(
5056
+ (group) => group.issues.map((issue) => ({
5057
+ id: `gh:${section.repo.name}:${issue.number}`,
5058
+ section: tabId,
5059
+ type: "item"
5060
+ }))
5061
+ );
5057
5062
  }
5058
- function buildFlatRows(tree, isCollapsed) {
5063
+ function buildFlatRowsForTab(tabId, tree) {
5064
+ if (tabId === "activity")
5065
+ return tree.activity.map((event, i) => ({
5066
+ type: "activity",
5067
+ key: `act:${i}`,
5068
+ navId: null,
5069
+ event
5070
+ }));
5071
+ if (tabId === "ticktick")
5072
+ return tree.tasks.map((task2) => ({
5073
+ type: "task",
5074
+ key: `tt:${task2.id}`,
5075
+ navId: `tt:${task2.id}`,
5076
+ task: task2
5077
+ }));
5078
+ const section = tree.sections.find((s) => s.sectionId === tabId);
5079
+ if (!section) return [];
5080
+ if (section.error)
5081
+ return [{ type: "error", key: `error:${tabId}`, navId: null, text: section.error }];
5082
+ if (section.groups.length === 0)
5083
+ return [
5084
+ { type: "subHeader", key: `empty:${tabId}`, navId: null, text: "No open issues" }
5085
+ ];
5059
5086
  const rows = [];
5060
- if (tree.activity.length > 0) {
5061
- const collapsed = isCollapsed("activity");
5062
- rows.push({
5063
- type: "sectionHeader",
5064
- key: "header:activity",
5065
- navId: "header:activity",
5066
- label: "Recent Activity (24h)",
5067
- count: tree.activity.length,
5068
- countLabel: "events",
5069
- isCollapsed: collapsed
5070
- });
5071
- if (!collapsed) {
5072
- for (const [i, event] of tree.activity.entries()) {
5073
- rows.push({ type: "activity", key: `act:${i}`, navId: null, event });
5074
- }
5075
- }
5076
- }
5077
- for (const { repo, sectionId, groups, error } of tree.sections) {
5078
- const collapsed = isCollapsed(sectionId);
5079
- const totalIssues = groups.reduce((s, g) => s + g.issues.length, 0);
5080
- rows.push({
5081
- type: "sectionHeader",
5082
- key: `header:${sectionId}`,
5083
- navId: `header:${sectionId}`,
5084
- label: repo.shortName,
5085
- // display label still shows shortName
5086
- count: totalIssues,
5087
- countLabel: "issues",
5088
- isCollapsed: collapsed
5089
- });
5090
- if (!collapsed) {
5091
- if (error) {
5092
- rows.push({ type: "error", key: `error:${sectionId}`, navId: null, text: error });
5093
- } else if (groups.length === 0) {
5094
- rows.push({
5095
- type: "subHeader",
5096
- key: `empty:${sectionId}`,
5097
- navId: null,
5098
- text: "No open issues"
5099
- });
5100
- } else {
5101
- let isFirstGroup = true;
5102
- for (const group of groups) {
5103
- if (!isFirstGroup)
5104
- rows.push({ type: "gap", key: `gap:${sectionId}:${group.label}`, navId: null });
5105
- isFirstGroup = false;
5106
- const subCollapsed = isCollapsed(group.subId);
5107
- rows.push({
5108
- type: "subHeader",
5109
- key: group.subId,
5110
- navId: group.subId,
5111
- text: group.label,
5112
- count: group.issues.length,
5113
- isCollapsed: subCollapsed
5114
- });
5115
- if (!subCollapsed) {
5116
- for (const issue of group.issues) {
5117
- rows.push({
5118
- type: "issue",
5119
- key: `gh:${repo.name}:${issue.number}`,
5120
- navId: `gh:${repo.name}:${issue.number}`,
5121
- issue,
5122
- repoName: repo.name
5123
- });
5124
- }
5125
- }
5126
- }
5127
- }
5128
- }
5129
- }
5130
- if (tree.tasks.length > 0) {
5131
- const collapsed = isCollapsed("ticktick");
5087
+ let isFirst = true;
5088
+ for (const group of section.groups) {
5089
+ if (!isFirst)
5090
+ rows.push({ type: "gap", key: `gap:${tabId}:${group.label}`, navId: null });
5091
+ isFirst = false;
5132
5092
  rows.push({
5133
- type: "sectionHeader",
5134
- key: "header:ticktick",
5135
- navId: "header:ticktick",
5136
- label: "Personal (TickTick)",
5137
- count: tree.tasks.length,
5138
- countLabel: "tasks",
5139
- isCollapsed: collapsed
5093
+ type: "subHeader",
5094
+ key: group.subId,
5095
+ navId: null,
5096
+ text: group.label,
5097
+ count: group.issues.length,
5098
+ isCollapsed: false
5140
5099
  });
5141
- if (!collapsed) {
5142
- for (const task2 of tree.tasks)
5143
- rows.push({ type: "task", key: `tt:${task2.id}`, navId: `tt:${task2.id}`, task: task2 });
5144
- }
5100
+ for (const issue of group.issues)
5101
+ rows.push({
5102
+ type: "issue",
5103
+ key: `gh:${section.repo.name}:${issue.number}`,
5104
+ navId: `gh:${section.repo.name}:${issue.number}`,
5105
+ issue,
5106
+ repoName: section.repo.name
5107
+ });
5145
5108
  }
5146
5109
  return rows;
5147
5110
  }
@@ -5169,7 +5132,7 @@ function RefreshAge({ lastRefresh }) {
5169
5132
  return () => clearInterval(id);
5170
5133
  }, []);
5171
5134
  if (!lastRefresh) return null;
5172
- return /* @__PURE__ */ jsxs21(Text20, { color: refreshAgeColor(lastRefresh), children: [
5135
+ return /* @__PURE__ */ jsxs22(Text21, { color: refreshAgeColor(lastRefresh), children: [
5173
5136
  "Updated ",
5174
5137
  timeAgo(lastRefresh)
5175
5138
  ] });
@@ -5238,7 +5201,29 @@ function Dashboard({ config: config2, options, activeProfile }) {
5238
5201
  () => buildBoardTree(repos, tasks, allActivity),
5239
5202
  [repos, tasks, allActivity]
5240
5203
  );
5241
- const navItems = useMemo3(() => buildNavItems(boardTree), [boardTree]);
5204
+ const tabs = useMemo3(() => buildTabs(boardTree), [boardTree]);
5205
+ const [activeTabId, setActiveTabId] = useState15(null);
5206
+ const effectiveTabId = activeTabId ?? tabs[0]?.id ?? null;
5207
+ const activeTabIdx = tabs.findIndex((t) => t.id === effectiveTabId);
5208
+ const nextTab = useCallback11(() => {
5209
+ if (tabs.length === 0) return;
5210
+ setActiveTabId(tabs[(Math.max(activeTabIdx, 0) + 1) % tabs.length]?.id ?? null);
5211
+ }, [activeTabIdx, tabs]);
5212
+ const prevTab = useCallback11(() => {
5213
+ if (tabs.length === 0) return;
5214
+ setActiveTabId(tabs[(Math.max(activeTabIdx, 0) - 1 + tabs.length) % tabs.length]?.id ?? null);
5215
+ }, [activeTabIdx, tabs]);
5216
+ const jumpToTab = useCallback11(
5217
+ (idx) => {
5218
+ const tab = tabs[idx];
5219
+ if (tab) setActiveTabId(tab.id);
5220
+ },
5221
+ [tabs]
5222
+ );
5223
+ const navItems = useMemo3(
5224
+ () => buildNavItemsForTab(effectiveTabId ?? "", boardTree),
5225
+ [effectiveTabId, boardTree]
5226
+ );
5242
5227
  const nav = useNavigation(navItems);
5243
5228
  const getRepoForId = useCallback11((id) => {
5244
5229
  if (id.startsWith("gh:")) {
@@ -5390,8 +5375,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
5390
5375
  termSize.rows - CHROME_ROWS - overlayBarRows - toastRows - logPaneRows
5391
5376
  );
5392
5377
  const flatRows = useMemo3(
5393
- () => buildFlatRows(boardTree, nav.isCollapsed),
5394
- [boardTree, nav.isCollapsed]
5378
+ () => buildFlatRowsForTab(effectiveTabId ?? "", boardTree),
5379
+ [effectiveTabId, boardTree]
5395
5380
  );
5396
5381
  const scrollRef = useRef13(0);
5397
5382
  const selectedRowIdx = useMemo3(
@@ -5552,6 +5537,12 @@ function Dashboard({ config: config2, options, activeProfile }) {
5552
5537
  const handleFuzzySelect = useCallback11(
5553
5538
  (navId) => {
5554
5539
  nav.select(navId);
5540
+ if (navId.startsWith("gh:")) {
5541
+ const parts = navId.split(":");
5542
+ if (parts.length >= 3 && parts[1]) setActiveTabId(parts[1]);
5543
+ } else if (navId.startsWith("tt:")) {
5544
+ setActiveTabId("ticktick");
5545
+ }
5555
5546
  ui.exitToNormal();
5556
5547
  },
5557
5548
  [nav, ui]
@@ -5585,10 +5576,11 @@ function Dashboard({ config: config2, options, activeProfile }) {
5585
5576
  handleUndo: undoLast,
5586
5577
  handleToggleLog: () => setLogVisible((v) => !v)
5587
5578
  },
5588
- onSearchEscape
5579
+ onSearchEscape,
5580
+ tabNav: { next: nextTab, prev: prevTab, jumpTo: jumpToTab, count: tabs.length }
5589
5581
  });
5590
5582
  if (status === "loading" && !data) {
5591
- return /* @__PURE__ */ jsx21(Box20, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx21(Spinner4, { label: "Loading dashboard..." }) });
5583
+ return /* @__PURE__ */ jsx22(Box21, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx22(Spinner4, { label: "Loading dashboard..." }) });
5592
5584
  }
5593
5585
  const now = data?.fetchedAt ?? /* @__PURE__ */ new Date();
5594
5586
  const dateStr = now.toLocaleDateString("en-US", {
@@ -5596,35 +5588,36 @@ function Dashboard({ config: config2, options, activeProfile }) {
5596
5588
  day: "numeric",
5597
5589
  year: "numeric"
5598
5590
  });
5599
- return /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", paddingX: 1, children: [
5600
- /* @__PURE__ */ jsxs21(Box20, { children: [
5601
- /* @__PURE__ */ jsx21(Text20, { color: "cyan", bold: true, children: "HOG BOARD" }),
5602
- activeProfile ? /* @__PURE__ */ jsxs21(Text20, { color: "yellow", children: [
5591
+ return /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", paddingX: 1, children: [
5592
+ /* @__PURE__ */ jsxs22(Box21, { children: [
5593
+ /* @__PURE__ */ jsx22(Text21, { color: "cyan", bold: true, children: "HOG BOARD" }),
5594
+ activeProfile ? /* @__PURE__ */ jsxs22(Text21, { color: "yellow", children: [
5603
5595
  " [",
5604
5596
  activeProfile,
5605
5597
  "]"
5606
5598
  ] }) : null,
5607
- /* @__PURE__ */ jsxs21(Text20, { color: "gray", children: [
5599
+ /* @__PURE__ */ jsxs22(Text21, { color: "gray", children: [
5608
5600
  " ",
5609
5601
  "\u2014",
5610
5602
  " ",
5611
5603
  dateStr
5612
5604
  ] }),
5613
- /* @__PURE__ */ jsx21(Text20, { children: " " }),
5614
- isRefreshing ? /* @__PURE__ */ jsxs21(Fragment5, { children: [
5615
- /* @__PURE__ */ jsx21(Spinner4, { label: "" }),
5616
- /* @__PURE__ */ jsx21(Text20, { color: "cyan", children: " Refreshing..." })
5617
- ] }) : /* @__PURE__ */ jsxs21(Fragment5, { children: [
5618
- /* @__PURE__ */ jsx21(RefreshAge, { lastRefresh }),
5619
- consecutiveFailures > 0 ? /* @__PURE__ */ jsx21(Text20, { color: "red", children: " (!)" }) : null
5605
+ /* @__PURE__ */ jsx22(Text21, { children: " " }),
5606
+ isRefreshing ? /* @__PURE__ */ jsxs22(Fragment5, { children: [
5607
+ /* @__PURE__ */ jsx22(Spinner4, { label: "" }),
5608
+ /* @__PURE__ */ jsx22(Text21, { color: "cyan", children: " Refreshing..." })
5609
+ ] }) : /* @__PURE__ */ jsxs22(Fragment5, { children: [
5610
+ /* @__PURE__ */ jsx22(RefreshAge, { lastRefresh }),
5611
+ consecutiveFailures > 0 ? /* @__PURE__ */ jsx22(Text21, { color: "red", children: " (!)" }) : null
5620
5612
  ] }),
5621
- autoRefreshPaused ? /* @__PURE__ */ jsx21(Text20, { color: "yellow", children: " Auto-refresh paused \u2014 press r to retry" }) : null
5613
+ autoRefreshPaused ? /* @__PURE__ */ jsx22(Text21, { color: "yellow", children: " Auto-refresh paused \u2014 press r to retry" }) : null
5622
5614
  ] }),
5623
- error ? /* @__PURE__ */ jsxs21(Text20, { color: "red", children: [
5615
+ error ? /* @__PURE__ */ jsxs22(Text21, { color: "red", children: [
5624
5616
  "Error: ",
5625
5617
  error
5626
5618
  ] }) : null,
5627
- /* @__PURE__ */ jsx21(
5619
+ /* @__PURE__ */ jsx22(TabBar, { tabs, activeTabId: effectiveTabId, totalWidth: termSize.cols }),
5620
+ /* @__PURE__ */ jsx22(
5628
5621
  OverlayRenderer,
5629
5622
  {
5630
5623
  uiState: ui.state,
@@ -5666,16 +5659,16 @@ function Dashboard({ config: config2, options, activeProfile }) {
5666
5659
  onPushEntry: pushEntry
5667
5660
  }
5668
5661
  ),
5669
- !ui.state.helpVisible && ui.state.mode !== "overlay:status" && ui.state.mode !== "overlay:create" && ui.state.mode !== "overlay:createNl" && ui.state.mode !== "overlay:bulkAction" && ui.state.mode !== "overlay:confirmPick" && ui.state.mode !== "focus" ? /* @__PURE__ */ jsxs21(Box20, { height: viewportHeight, children: [
5670
- /* @__PURE__ */ jsxs21(Box20, { flexDirection: "column", flexGrow: 1, children: [
5671
- hasMoreAbove ? /* @__PURE__ */ jsxs21(Text20, { color: "gray", dimColor: true, children: [
5662
+ !ui.state.helpVisible && ui.state.mode !== "overlay:status" && ui.state.mode !== "overlay:create" && ui.state.mode !== "overlay:createNl" && ui.state.mode !== "overlay:bulkAction" && ui.state.mode !== "overlay:confirmPick" && ui.state.mode !== "focus" ? /* @__PURE__ */ jsxs22(Box21, { height: viewportHeight, children: [
5663
+ /* @__PURE__ */ jsxs22(Box21, { flexDirection: "column", flexGrow: 1, children: [
5664
+ hasMoreAbove ? /* @__PURE__ */ jsxs22(Text21, { color: "gray", dimColor: true, children: [
5672
5665
  " ",
5673
5666
  "\u25B2",
5674
5667
  " ",
5675
5668
  aboveCount,
5676
5669
  " more above"
5677
5670
  ] }) : null,
5678
- visibleRows.map((row) => /* @__PURE__ */ jsx21(
5671
+ visibleRows.map((row) => /* @__PURE__ */ jsx22(
5679
5672
  RowRenderer,
5680
5673
  {
5681
5674
  row,
@@ -5685,7 +5678,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
5685
5678
  },
5686
5679
  row.key
5687
5680
  )),
5688
- hasMoreBelow ? /* @__PURE__ */ jsxs21(Text20, { color: "gray", dimColor: true, children: [
5681
+ hasMoreBelow ? /* @__PURE__ */ jsxs22(Text21, { color: "gray", dimColor: true, children: [
5689
5682
  " ",
5690
5683
  "\u25BC",
5691
5684
  " ",
@@ -5693,7 +5686,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
5693
5686
  " more below"
5694
5687
  ] }) : null
5695
5688
  ] }),
5696
- showDetailPanel ? /* @__PURE__ */ jsx21(Box20, { marginLeft: 1, width: detailPanelWidth, children: /* @__PURE__ */ jsx21(
5689
+ showDetailPanel ? /* @__PURE__ */ jsx22(Box21, { marginLeft: 1, width: detailPanelWidth, children: /* @__PURE__ */ jsx22(
5697
5690
  DetailPanel,
5698
5691
  {
5699
5692
  issue: selectedItem.issue,
@@ -5705,17 +5698,16 @@ function Dashboard({ config: config2, options, activeProfile }) {
5705
5698
  }
5706
5699
  ) }) : null
5707
5700
  ] }) : null,
5708
- /* @__PURE__ */ jsx21(ToastContainer, { toasts }),
5709
- logVisible ? /* @__PURE__ */ jsx21(ActionLog, { entries: logEntries }) : null,
5710
- /* @__PURE__ */ jsx21(
5701
+ /* @__PURE__ */ jsx22(ToastContainer, { toasts }),
5702
+ logVisible ? /* @__PURE__ */ jsx22(ActionLog, { entries: logEntries }) : null,
5703
+ /* @__PURE__ */ jsx22(
5711
5704
  HintBar,
5712
5705
  {
5713
5706
  uiMode: ui.state.mode,
5714
5707
  multiSelectCount: multiSelect.count,
5715
5708
  searchQuery,
5716
5709
  mineOnly,
5717
- hasUndoable,
5718
- onHeader: isHeaderId(nav.selectedId)
5710
+ hasUndoable
5719
5711
  }
5720
5712
  )
5721
5713
  ] });
@@ -5740,6 +5732,7 @@ var init_dashboard = __esm({
5740
5732
  init_hint_bar();
5741
5733
  init_overlay_renderer();
5742
5734
  init_row_renderer();
5735
+ init_tab_bar();
5743
5736
  init_toast_container();
5744
5737
  PRIORITY_RANK = {
5745
5738
  "priority:critical": 0,
@@ -5747,7 +5740,7 @@ var init_dashboard = __esm({
5747
5740
  "priority:medium": 2,
5748
5741
  "priority:low": 3
5749
5742
  };
5750
- CHROME_ROWS = 4;
5743
+ CHROME_ROWS = 5;
5751
5744
  }
5752
5745
  });
5753
5746
 
@@ -5757,10 +5750,10 @@ __export(live_exports, {
5757
5750
  runLiveDashboard: () => runLiveDashboard
5758
5751
  });
5759
5752
  import { render } from "ink";
5760
- import { jsx as jsx22 } from "react/jsx-runtime";
5753
+ import { jsx as jsx23 } from "react/jsx-runtime";
5761
5754
  async function runLiveDashboard(config2, options, activeProfile) {
5762
5755
  const instance = render(
5763
- /* @__PURE__ */ jsx22(Dashboard, { config: config2, options, activeProfile: activeProfile ?? null })
5756
+ /* @__PURE__ */ jsx23(Dashboard, { config: config2, options, activeProfile: activeProfile ?? null })
5764
5757
  );
5765
5758
  setInkInstance(instance);
5766
5759
  await instance.waitUntilExit();
@@ -6923,7 +6916,7 @@ function resolveProjectId(projectId) {
6923
6916
  process.exit(1);
6924
6917
  }
6925
6918
  var program = new Command();
6926
- program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.8.1").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
6919
+ program.name("hog").description("Personal command deck \u2014 unified task dashboard for GitHub Projects + TickTick").version("1.9.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
6927
6920
  const opts = thisCommand.opts();
6928
6921
  if (opts.json) setFormat("json");
6929
6922
  if (opts.human) setFormat("human");