jira-dashboard 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -22,7 +22,11 @@ A full-screen terminal dashboard for Jira Cloud, built with [Ink](https://github
22
22
  - **Top left** — hours you logged on each of the last 14 days (oldest first, so today is at the
23
23
  bottom), plus the issues you logged against.
24
24
  - **Top right** — your open tickets (not done, not cancelled/closed, not in the backlog), selectable
25
- by keyboard or mouse.
25
+ by keyboard or mouse. Each row starts with an issue-type icon — bug (red), task (blue), story
26
+ (green), sub-task (cyan); other types are left blank — which needs a
27
+ [Nerd Font](https://www.nerdfonts.com) in your terminal. Next comes a Jira-style priority arrow:
28
+ `⇈` highest, `↑` high, `=` medium, `↓` low, `⇊` lowest, `‼` critical/blocker. Priorities the site
29
+ names differently are left blank.
26
30
  - **Bottom left** — bar chart of the hours *you* logged on the **selected** ticket, day by day over
27
31
  the same 14 days. The scale is fixed so bars are comparable between tickets and between runs: 2h is
28
32
  half a row, 4h one row, 8h two rows; anything logged below 2h gets a thin mark so it stays visible.
@@ -109,6 +113,7 @@ coloured red, so what is left in red is what still needs hours.
109
113
  | `Home` / `End` | Jump to the first / last item in the focused panel |
110
114
  | `g` / `G` | Jump to first / last ticket |
111
115
  | `Tab` / `Shift+Tab` | Cycle panels (worklogs → tickets → preview) |
116
+ | `Alt+←` / `Alt+→` | Move the splitter of the focused panel's row (each pane keeps at least 20 columns) |
112
117
  | Click | Select a ticket / focus a panel |
113
118
  | Wheel | Scroll the panel under the cursor |
114
119
  | `t` | Toggle the full ticket tree (show epics/stories that are not assigned to you) |
@@ -164,7 +169,7 @@ To test what users actually get instead — a frozen copy built from the real ta
164
169
 
165
170
  ```sh
166
171
  npm pack
167
- npm install -g ./jira-dashboard-0.1.0.tgz
172
+ npm install -g ./jira-dashboard-*.tgz
168
173
  jdb --version
169
174
  ```
170
175
 
@@ -220,41 +225,18 @@ is one row taller than its height — see `PanelFrame` and the row-count arithme
220
225
 
221
226
  ## Publishing to npm
222
227
 
223
- The package publishes as **`jira-dashboard`** and installs the **`jdb`** command. Only `dist/` and
224
- `README.md` ship (`files` in `package.json`); source and configs stay out of the tarball.
228
+ The package is published as [**`jira-dashboard`**](https://www.npmjs.com/package/jira-dashboard) and
229
+ installs the **`jdb`** command. Only `dist/` and `README.md` ship (`files` in `package.json`); source
230
+ and configs stay out of the tarball.
225
231
 
226
232
  ```sh
227
- # 1. Authenticate (once per machine)
228
- npm login
229
- npm whoami
230
-
231
- # 2. Make sure it is releasable (lint + typecheck + build)
232
- npm run check
233
-
234
- # 3. Inspect exactly what will be uploaded — no files are published by this
235
- npm pack --dry-run
236
-
237
- # 4. Bump the version (also creates a git tag when this is a git repo;
238
- # add --no-git-tag-version if it is not, or if you tag separately)
233
+ npm run publish-check # lint + typecheck + build
234
+ npm pack --dry-run # inspect exactly what will be uploaded
239
235
  npm version patch # or: minor | major
240
-
241
- # 5. Publish. prepublishOnly rebuilds dist/ first.
242
- npm publish
243
-
244
- # 6. Verify the published artifact
236
+ npm publish # prepublishOnly rebuilds dist/ first
245
237
  npm view jira-dashboard version
246
- npx --yes jira-dashboard@latest --version
247
238
  ```
248
239
 
249
- Notes:
250
-
251
- - **The name `jira-dashboard` was unclaimed on the public registry as of this writing**, but that can
252
- change at any time. If `npm publish` fails with `E403`, the name is taken — publish under a scope
253
- instead (`@yourname/jira-dashboard`), which additionally requires `npm publish --access public`
254
- since scoped packages default to private.
255
- - **Publishing is effectively permanent.** Unpublishing is only allowed within 72 hours and a version
256
- number can never be reused. Prefer `npm deprecate jira-dashboard@1.2.3 "reason"` for a bad release
257
- and ship a fix as a new version.
240
+ - **Publishing is effectively permanent.** A version number can never be reused. Prefer
241
+ `npm deprecate jira-dashboard@1.2.3 "reason"` for a bad release and ship a fix as a new version.
258
242
  - **`engines` requires Node ≥ 20** (the code uses `AbortSignal.timeout` and built-in `fetch`).
259
- - To rehearse the whole flow without touching the public registry, publish to a local registry such
260
- as [Verdaccio](https://verdaccio.org/): `npm publish --registry http://localhost:4873`.
package/dist/cli.js CHANGED
@@ -69,6 +69,41 @@ function PanelError({ error }) {
69
69
  ] });
70
70
  }
71
71
 
72
+ // src/issueType.ts
73
+ var ISSUE_TYPE_WIDTH = 2;
74
+ var MARKS = [
75
+ { mark: { glyph: "\uF46F", color: "red" }, names: ["bug", "defect"] },
76
+ { mark: { glyph: "\uF14A", color: "blue" }, names: ["task"] },
77
+ { mark: { glyph: "\uF02E", color: "green" }, names: ["story", "user story"] },
78
+ { mark: { glyph: "\uF0AE", color: "cyan" }, names: ["sub-task", "subtask", "sub task"] }
79
+ ];
80
+ var BY_NAME = new Map(
81
+ MARKS.flatMap(({ mark, names }) => names.map((name) => [name, mark]))
82
+ );
83
+ function issueTypeMark(issueType) {
84
+ return BY_NAME.get(issueType.trim().toLowerCase()) ?? null;
85
+ }
86
+ var ISSUE_TYPE_GLYPHS = MARKS.map(({ mark }) => mark.glyph);
87
+
88
+ // src/priority.ts
89
+ var PRIORITY_WIDTH = 2;
90
+ var MARKS2 = [
91
+ { mark: { glyph: "\u203C", color: "red", bold: true }, names: ["blocker", "critical", "urgent"] },
92
+ { mark: { glyph: "\u21C8", color: "red", bold: true }, names: ["highest", "p1"] },
93
+ { mark: { glyph: "\u2191", color: "red", bold: false }, names: ["high", "major", "p2"] },
94
+ { mark: { glyph: "=", color: "yellow", bold: false }, names: ["medium", "normal", "p3"] },
95
+ { mark: { glyph: "\u2193", color: "blue", bold: false }, names: ["low", "minor", "p4"] },
96
+ { mark: { glyph: "\u21CA", color: "blue", bold: false }, names: ["lowest", "trivial", "p5"] }
97
+ ];
98
+ var BY_NAME2 = new Map(
99
+ MARKS2.flatMap(({ mark, names }) => names.map((name) => [name, mark]))
100
+ );
101
+ function priorityMark(priority) {
102
+ if (!priority) return null;
103
+ return BY_NAME2.get(priority.trim().toLowerCase()) ?? null;
104
+ }
105
+ var PRIORITY_GLYPHS = MARKS2.map(({ mark }) => mark.glyph);
106
+
72
107
  // src/tree.ts
73
108
  function buildIssueTree(issues, full) {
74
109
  const byKey = new Map(issues.map((issue) => [issue.key, issue]));
@@ -152,7 +187,10 @@ function IssueList({
152
187
  Math.max(0, Math.floor(width / 4)),
153
188
  rows.reduce((widest, row) => Math.max(widest, treePrefix(row).length + 2), 0)
154
189
  );
155
- const summaryWidth = Math.max(8, width - 4 - 2 - gutterWidth - KEY_WIDTH - STATUS_WIDTH);
190
+ const summaryWidth = Math.max(
191
+ 8,
192
+ width - 4 - 2 - gutterWidth - ISSUE_TYPE_WIDTH - PRIORITY_WIDTH - KEY_WIDTH - STATUS_WIDTH
193
+ );
156
194
  const label = fullTree ? "my tickets \xB7 full tree" : "my open tickets";
157
195
  const base = rows.length > visibleRows ? `${label} (${selectedIndex + 1}/${rows.length})` : `${label} (${rows.length})`;
158
196
  const title = state.loading && issues.length > 0 ? `${spinnerChar(spinnerFrame)} ${base}` : base;
@@ -161,9 +199,28 @@ function IssueList({
161
199
  const selected = offset + index === selectedIndex;
162
200
  const background = selected ? focused ? "cyan" : "blue" : void 0;
163
201
  const cellColor = (own) => selected ? "black" : own;
202
+ const issueType = issueTypeMark(issue.issueType);
203
+ const priority = priorityMark(issue.priority);
164
204
  return /* @__PURE__ */ jsxs3(Box3, { children: [
165
205
  /* @__PURE__ */ jsx3(Text3, { color: cellColor("cyan"), backgroundColor: background, children: selected ? "\u25B8 " : " " }),
166
206
  gutterWidth > 0 ? /* @__PURE__ */ jsx3(Text3, { color: cellColor("gray"), backgroundColor: background, children: fit(`${treePrefix(row)}${folderMark(row)} `, gutterWidth) }) : null,
207
+ /* @__PURE__ */ jsx3(
208
+ Text3,
209
+ {
210
+ color: issueType ? cellColor(issueType.color) : void 0,
211
+ backgroundColor: background,
212
+ children: fit(issueType ? issueType.glyph : "", ISSUE_TYPE_WIDTH)
213
+ }
214
+ ),
215
+ /* @__PURE__ */ jsx3(
216
+ Text3,
217
+ {
218
+ bold: priority?.bold,
219
+ color: priority ? cellColor(priority.color) : void 0,
220
+ backgroundColor: background,
221
+ children: fit(priority ? priority.glyph : "", PRIORITY_WIDTH)
222
+ }
223
+ ),
167
224
  /* @__PURE__ */ jsx3(
168
225
  Text3,
169
226
  {
@@ -1117,6 +1174,17 @@ function useTerminalSize() {
1117
1174
  return size;
1118
1175
  }
1119
1176
 
1177
+ // src/layout.ts
1178
+ var SPLIT_STEP = 2;
1179
+ var MIN_PANE_WIDTH = 20;
1180
+ function clampSplit(width, total) {
1181
+ if (total < 2 * MIN_PANE_WIDTH) return Math.max(0, Math.floor(total / 2));
1182
+ return Math.max(MIN_PANE_WIDTH, Math.min(width, total - MIN_PANE_WIDTH));
1183
+ }
1184
+ function nudgeSplit(shift, delta, base, total) {
1185
+ return clampSplit(base + shift + delta, total) - base;
1186
+ }
1187
+
1120
1188
  // src/openUrl.ts
1121
1189
  import { spawn } from "node:child_process";
1122
1190
  function opener() {
@@ -1181,16 +1249,20 @@ function App({ config, client, me }) {
1181
1249
  const [notice, setNotice] = useState2(null);
1182
1250
  const [dayMarks, setDayMarks] = useState2(() => /* @__PURE__ */ new Map());
1183
1251
  const [fullTree, setFullTree] = useState2(false);
1252
+ const [topShift, setTopShift] = useState2(0);
1253
+ const [bottomShift, setBottomShift] = useState2(0);
1184
1254
  const topHeight = Math.max(MIN_TOP_HEIGHT, Math.floor((rows - 1) / 2));
1185
1255
  const bottomHeight = Math.max(MIN_TOP_HEIGHT, rows - 1 - topHeight);
1186
- const leftWidth = Math.max(MIN_LEFT_WIDTH, Math.min(52, Math.floor(columns * 0.42)));
1256
+ const defaultLeftWidth = Math.max(MIN_LEFT_WIDTH, Math.min(52, Math.floor(columns * 0.42)));
1257
+ const leftWidth = clampSplit(defaultLeftWidth + topShift, columns);
1187
1258
  const issueRows = Math.max(1, topHeight - 3);
1188
1259
  const worklogRows = issueRows;
1189
1260
  const dayCount = worklogs.data?.days.length ?? config.worklogDays;
1190
- const chartWidth = Math.min(
1261
+ const defaultChartWidth = Math.min(
1191
1262
  Math.floor(columns / 2),
1192
1263
  Math.max(chartWidthFor(dayCount, 1), Math.min(chartWidthFor(dayCount, 2), columns - 40))
1193
1264
  );
1265
+ const chartWidth = clampSplit(defaultChartWidth + bottomShift, columns);
1194
1266
  useEffect4(() => {
1195
1267
  let cancelled = false;
1196
1268
  setIssues(pending);
@@ -1399,6 +1471,15 @@ function App({ config, client, me }) {
1399
1471
  });
1400
1472
  return;
1401
1473
  }
1474
+ if (key.meta && (key.leftArrow || key.rightArrow)) {
1475
+ const delta = key.leftArrow ? -SPLIT_STEP : SPLIT_STEP;
1476
+ if (focus === "preview") {
1477
+ setBottomShift((shift) => nudgeSplit(shift, delta, defaultChartWidth, columns));
1478
+ } else {
1479
+ setTopShift((shift) => nudgeSplit(shift, delta, defaultLeftWidth, columns));
1480
+ }
1481
+ return;
1482
+ }
1402
1483
  if (key.downArrow) scrollPanel(focus, 1);
1403
1484
  else if (key.upArrow) scrollPanel(focus, -1);
1404
1485
  else if (key.pageDown) scrollPanel(focus, issueRows);
@@ -1409,7 +1490,7 @@ function App({ config, client, me }) {
1409
1490
  else if (input === "G") selectIndex(issueList.length - 1);
1410
1491
  });
1411
1492
  const hint = useMemo(
1412
- () => focus === "issues" ? "\u2191\u2193/jk move \xB7 enter open \xB7 t tree \xB7 tab panel \xB7 r reload \xB7 q quit" : "\u2191\u2193/jk scroll \xB7 home/end top/bottom \xB7 tab panel \xB7 r reload \xB7 q quit",
1493
+ () => focus === "issues" ? "\u2191\u2193/jk move \xB7 enter open \xB7 t tree \xB7 alt\u2190\u2192 resize \xB7 tab panel \xB7 r reload \xB7 q quit" : "\u2191\u2193/jk scroll \xB7 home/end top/bottom \xB7 alt\u2190\u2192 resize \xB7 tab panel \xB7 r reload \xB7 q quit",
1413
1494
  [focus]
1414
1495
  );
1415
1496
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", width: columns, height: rows, children: [
package/dist/cli.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/cli.tsx", "../src/app.tsx", "../src/components/IssueList.tsx", "../src/components/PanelFrame.tsx", "../src/components/PanelStatus.tsx", "../src/tree.ts", "../src/components/IssuePreview.tsx", "../src/components/WorkChartPanel.tsx", "../src/dates.ts", "../src/components/WorklogPanel.tsx", "../src/data/calendar.ts", "../src/data/config.ts", "../src/types.ts", "../src/data/adf.ts", "../src/data/sprintField.ts", "../src/data/issues.ts", "../src/data/worklogs.ts", "../src/hooks/useHomeEndKeys.ts", "../src/hooks/useMouse.ts", "../src/hooks/useTerminalSize.ts", "../src/openUrl.ts", "../src/data/client.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\nimport { render } from 'ink';\nimport { createRequire } from 'node:module';\n\nimport { App } from './app';\nimport { DAYS_OFF_PATH } from './data/calendar';\nimport { JiraClient } from './data/client';\nimport { CONFIG_PATH, DEFAULT_ISSUES_JQL, loadConfig } from './data/config';\nimport { JiraError } from './types';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json') as { version: string };\n\n/**\n * Built from a char code on purpose: a literal escape byte in source is\n * invisible and easy to lose to an editor or a copy-paste.\n */\nconst ESC = String.fromCharCode(27);\nconst ALT_SCREEN_ON = `${ESC}[?1049h`;\nconst ALT_SCREEN_OFF = `${ESC}[?1049l`;\nconst CURSOR_SHOW = `${ESC}[?25h`;\n\nconst HELP = `\n jdb \u2014 Jira dashboard in your terminal\n\n Usage\n $ jdb\n\n Options\n -h, --help Show this help\n -v, --version Show the version\n\n Configuration (environment variables take precedence)\n JIRA_BASE_URL https://your-team.atlassian.net\n JIRA_EMAIL your Atlassian account email\n JIRA_API_TOKEN https://id.atlassian.com/manage-profile/security/api-tokens\n JIRA_HOLIDAY_COUNTRY ISO country code for statutory holidays, e.g. UA\n JIRA_HOLIDAY_REGION your subdivision, e.g. CA-ON \u2014 regional holidays then\n count as days off, and other regions' are hidden\n\n Or ${CONFIG_PATH}:\n {\n \"baseUrl\": \"https://your-team.atlassian.net\",\n \"email\": \"you@example.com\",\n \"apiToken\": \"...\",\n \"issuesJql\": ${JSON.stringify(DEFAULT_ISSUES_JQL)},\n \"worklogDays\": 14,\n \"holidayCountry\": \"UA\"\n }\n\n Personal leave goes in ${DAYS_OFF_PATH}:\n [\"2026-08-10\", { \"date\": \"2026-08-11\", \"label\": \"moving day\" }]\n\n Holidays are fetched from date.nager.at and cached next to the config;\n changing the country invalidates the cache.\n\n Keys\n \u2191 \u2193 / j k move within the focused panel\n home / end jump to the first / last item\n enter open the selected ticket in your browser\n tab cycle panels (worklogs \u2192 tickets \u2192 preview)\n click/wheel select and scroll\n r reload\n q / esc quit\n`;\n\n/** Print an error the way clig.dev recommends: what broke, then how to fix it. */\nfunction reportError(error: unknown): void {\n const jiraError = error instanceof JiraError ? error : null;\n process.stderr.write(`\\njdb: ${jiraError?.message ?? String(error)}\\n`);\n for (const hint of jiraError?.hints ?? []) {\n process.stderr.write(` ${hint}\\n`);\n }\n process.stderr.write('\\n');\n}\n\nasync function main(): Promise<number> {\n const args = process.argv.slice(2);\n\n if (args.includes('-h') || args.includes('--help')) {\n process.stdout.write(`${HELP}\\n`);\n return 0;\n }\n if (args.includes('-v') || args.includes('--version')) {\n process.stdout.write(`${version}\\n`);\n return 0;\n }\n\n if (!process.stdout.isTTY) {\n process.stderr.write('jdb: needs an interactive terminal (stdout is not a TTY).\\n');\n return 1;\n }\n\n const config = loadConfig();\n const client = new JiraClient(config);\n const me = await client.myself();\n\n // The alternate screen keeps the user's scrollback intact: everything jdb\n // draws goes to a separate buffer that the terminal discards on the way out.\n let restored = false;\n const restoreScreen = (): void => {\n if (restored) return;\n restored = true;\n process.stdout.write(ALT_SCREEN_OFF + CURSOR_SHOW);\n };\n\n process.stdout.write(ALT_SCREEN_ON);\n // Cover every way the process can end, including a crash or a signal.\n process.on('exit', restoreScreen);\n for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) {\n process.once(signal, () => {\n restoreScreen();\n process.exit(signal === 'SIGINT' ? 130 : 143);\n });\n }\n\n try {\n const { waitUntilExit } = render(<App config={config} client={client} me={me} />, {\n exitOnCtrlC: true,\n });\n await waitUntilExit();\n } finally {\n restoreScreen();\n }\n\n return 0;\n}\n\nmain()\n .then((code) => {\n process.exitCode = code;\n })\n .catch((error: unknown) => {\n reportError(error);\n process.exitCode = 1;\n });\n", "import { Box, Text, useApp, useInput } from 'ink';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { ReactElement } from 'react';\n\nimport { IssueList } from './components/IssueList';\nimport { IssuePreview } from './components/IssuePreview';\nimport { PANEL_HEADER_ROWS } from './components/PanelFrame';\nimport { spinnerChar } from './components/PanelStatus';\nimport { chartWidthFor, WorkChartPanel } from './components/WorkChartPanel';\nimport { WorklogPanel } from './components/WorklogPanel';\nimport { loadDayMarks, type DayMarks } from './data/calendar';\nimport type { JiraClient } from './data/client';\nimport { fetchOpenIssues } from './data/issues';\nimport { fetchWorklogDays } from './data/worklogs';\nimport { useHomeEndKeys } from './hooks/useHomeEndKeys';\nimport { isMouseSequence, useMouse, type MouseEvent } from './hooks/useMouse';\nimport { useTerminalSize } from './hooks/useTerminalSize';\nimport { openUrl } from './openUrl';\nimport { buildIssueTree } from './tree';\nimport { JiraError } from './types';\nimport type { Config, CurrentUser, Issue, Loadable, WorklogDay, WorklogSummary } from './types';\n\ninterface AppProps {\n config: Config;\n client: JiraClient;\n me: CurrentUser;\n}\n\ntype Panel = 'worklog' | 'issues' | 'preview';\nconst PANEL_ORDER: Panel[] = ['worklog', 'issues', 'preview'];\n\nconst MIN_TOP_HEIGHT = 6;\nconst MIN_LEFT_WIDTH = 34;\nconst SPINNER_INTERVAL_MS = 90;\nconst NOTICE_TIMEOUT_MS = 3000;\n\nconst pending = <T,>(previous?: Loadable<T>): Loadable<T> => ({\n data: previous?.data ?? null,\n loading: true,\n error: null,\n});\n\nfunction toJiraError(cause: unknown): JiraError {\n if (cause instanceof JiraError) return cause;\n return new JiraError(cause instanceof Error ? cause.message : String(cause));\n}\n\nexport function App({ config, client, me }: AppProps): ReactElement {\n const { exit } = useApp();\n const { columns, rows } = useTerminalSize();\n\n const [worklogs, setWorklogs] = useState<Loadable<WorklogSummary>>(() => pending());\n const [issues, setIssues] = useState<Loadable<Issue[]>>(() => pending());\n const [focus, setFocus] = useState<Panel>('issues');\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [issueOffset, setIssueOffset] = useState(0);\n const [worklogOffset, setWorklogOffset] = useState(0);\n const [previewOffset, setPreviewOffset] = useState(0);\n const [spinnerFrame, setSpinnerFrame] = useState(0);\n const [reloadToken, setReloadToken] = useState(0);\n const [notice, setNotice] = useState<{ text: string; error: boolean } | null>(null);\n const [dayMarks, setDayMarks] = useState<DayMarks>(() => new Map());\n // \"t\" toggles between my own tickets and the full tree around them.\n const [fullTree, setFullTree] = useState(false);\n\n // ---- layout -------------------------------------------------------------\n const topHeight = Math.max(MIN_TOP_HEIGHT, Math.floor((rows - 1) / 2));\n const bottomHeight = Math.max(MIN_TOP_HEIGHT, rows - 1 - topHeight);\n const leftWidth = Math.max(MIN_LEFT_WIDTH, Math.min(52, Math.floor(columns * 0.42)));\n const issueRows = Math.max(1, topHeight - 3);\n const worklogRows = issueRows;\n\n // The chart takes the width its bars need, but never more than half the screen;\n // the preview gets the rest.\n const dayCount = worklogs.data?.days.length ?? config.worklogDays;\n const chartWidth = Math.min(\n Math.floor(columns / 2),\n Math.max(chartWidthFor(dayCount, 1), Math.min(chartWidthFor(dayCount, 2), columns - 40)),\n );\n\n // ---- data ---------------------------------------------------------------\n useEffect(() => {\n let cancelled = false;\n\n setIssues(pending);\n fetchOpenIssues(client, config)\n .then((data) => {\n if (!cancelled) setIssues({ data, loading: false, error: null });\n })\n .catch((cause: unknown) => {\n if (!cancelled) setIssues({ data: null, loading: false, error: toJiraError(cause) });\n });\n\n setWorklogs(pending);\n fetchWorklogDays(client, config, me)\n .then((data) => {\n if (!cancelled) setWorklogs({ data, loading: false, error: null });\n })\n .catch((cause: unknown) => {\n if (!cancelled) setWorklogs({ data: null, loading: false, error: toJiraError(cause) });\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, config, me, reloadToken]);\n\n // The worklog window is known up front, so the calendar need not wait on Jira.\n // `loadDayMarks` swallows its own failures, hence no error state here.\n useEffect(() => {\n let cancelled = false;\n\n const today = new Date();\n const oldest = new Date(today);\n oldest.setDate(oldest.getDate() - config.worklogDays);\n const years = [...new Set([oldest.getFullYear(), today.getFullYear()])];\n\n void loadDayMarks(config, years).then((marks) => {\n if (!cancelled) setDayMarks(marks);\n });\n\n return () => {\n cancelled = true;\n };\n }, [config, reloadToken]);\n\n const loading = worklogs.loading || issues.loading;\n\n useEffect(() => {\n if (!loading) return;\n const timer = setInterval(() => {\n setSpinnerFrame((frame) => frame + 1);\n }, SPINNER_INTERVAL_MS);\n return () => {\n clearInterval(timer);\n };\n }, [loading]);\n\n const keepSelectedKey = useRef<string | null>(null);\n const issueRowsData = useMemo(\n () => buildIssueTree(issues.data ?? [], fullTree),\n [issues.data, fullTree],\n );\n const issueList = useMemo(() => issueRowsData.map((row) => row.issue), [issueRowsData]);\n const selected = issueList[selectedIndex] ?? null;\n\n // Keep the selection valid when a reload or a mode switch shortens the list,\n // and follow the remembered ticket when the tree mode changed under it.\n useEffect(() => {\n const wanted = keepSelectedKey.current;\n keepSelectedKey.current = null;\n const target = wanted === null ? -1 : issueList.findIndex((issue) => issue.key === wanted);\n setSelectedIndex((index) =>\n target >= 0 ? target : Math.min(index, Math.max(0, issueList.length - 1)),\n );\n }, [issueList]);\n\n // Days run oldest-first, so today sits at the bottom \u2014 start scrolled there.\n useEffect(() => {\n setWorklogOffset(Math.max(0, (worklogs.data?.days.length ?? 0) - worklogRows));\n }, [worklogs.data, worklogRows]);\n\n // Reading a new ticket always starts at the top of its description.\n useEffect(() => {\n setPreviewOffset(0);\n }, [selected?.key]);\n\n // ---- selection ----------------------------------------------------------\n const selectIndex = useCallback(\n (next: number) => {\n if (issueList.length === 0) return;\n const clamped = Math.max(0, Math.min(next, issueList.length - 1));\n setSelectedIndex(clamped);\n setIssueOffset((offset) => {\n if (clamped < offset) return clamped;\n if (clamped >= offset + issueRows) return clamped - issueRows + 1;\n return offset;\n });\n },\n [issueList.length, issueRows],\n );\n\n // Selection can also move without `selectIndex` (reload, tree toggle), so the\n // viewport is re-anchored here rather than at every call site.\n useEffect(() => {\n setIssueOffset((offset) => {\n if (selectedIndex < offset) return selectedIndex;\n if (selectedIndex >= offset + issueRows) return selectedIndex - issueRows + 1;\n return Math.min(offset, Math.max(0, issueList.length - issueRows));\n });\n }, [selectedIndex, issueRows, issueList.length]);\n\n const scrollPanel = useCallback(\n (panel: Panel, delta: number) => {\n switch (panel) {\n case 'issues':\n selectIndex(selectedIndex + delta);\n break;\n case 'worklog':\n setWorklogOffset((offset) =>\n Math.max(0, Math.min(offset + delta, (worklogs.data?.days.length ?? 0) - worklogRows)),\n );\n break;\n case 'preview':\n setPreviewOffset((offset) => Math.max(0, offset + delta));\n break;\n }\n },\n [selectIndex, selectedIndex, worklogs.data?.days.length, worklogRows],\n );\n\n /**\n * The selected issue's hours across the whole window, zero-filled.\n *\n * `byIssue` only holds days that have work, so the days axis comes from the\n * summary to keep every ticket's chart on the same 14-day scale.\n */\n const chartDays = useMemo<WorklogDay[]>(() => {\n const summary = worklogs.data;\n if (!summary || !selected) return [];\n\n const hoursByDate = new Map(\n (summary.byIssue[selected.key] ?? []).map((entry) => [entry.date, entry.hours]),\n );\n return summary.days.map((day) => ({\n date: day.date,\n hours: hoursByDate.get(day.date) ?? 0,\n issueKeys: [],\n }));\n }, [worklogs.data, selected]);\n\n // Notices are transient; clearing on a timer keeps the status bar honest.\n useEffect(() => {\n if (!notice) return;\n const timer = setTimeout(() => {\n setNotice(null);\n }, NOTICE_TIMEOUT_MS);\n return () => {\n clearTimeout(timer);\n };\n }, [notice]);\n\n const openSelected = useCallback(() => {\n if (!selected) return;\n openUrl(selected.url)\n .then(() => {\n setNotice({ text: `opened ${selected.key} in your browser`, error: false });\n })\n .catch((cause: unknown) => {\n setNotice({\n text: cause instanceof Error ? cause.message : String(cause),\n error: true,\n });\n });\n }, [selected]);\n\n // Home/End jump to the ends of whichever panel has focus.\n const goToStart = useCallback(() => {\n switch (focus) {\n case 'issues':\n selectIndex(0);\n break;\n case 'worklog':\n setWorklogOffset(0);\n break;\n case 'preview':\n setPreviewOffset(0);\n break;\n }\n }, [focus, selectIndex]);\n\n const goToEnd = useCallback(() => {\n switch (focus) {\n case 'issues':\n selectIndex(issueList.length - 1);\n break;\n case 'worklog':\n setWorklogOffset(Math.max(0, (worklogs.data?.days.length ?? 0) - worklogRows));\n break;\n case 'preview':\n // The description's total line count lives inside the preview, so there\n // is no \"last line\" to jump to from here.\n break;\n }\n }, [focus, issueList.length, selectIndex, worklogs.data?.days.length, worklogRows]);\n\n useHomeEndKeys(goToStart, goToEnd);\n\n // ---- mouse --------------------------------------------------------------\n const layout = useRef({ leftWidth, topHeight, issueRows, issueOffset });\n layout.current = { leftWidth, topHeight, issueRows, issueOffset };\n\n const onMouse = useCallback(\n (event: MouseEvent) => {\n const { leftWidth: left, topHeight: top, issueRows: visible, issueOffset: offset } =\n layout.current;\n\n const panel: Panel =\n event.row >= top ? 'preview' : event.column >= left ? 'issues' : 'worklog';\n\n if (event.type === 'wheel') {\n setFocus(panel);\n scrollPanel(panel, event.direction === 'up' ? -1 : 1);\n return;\n }\n\n setFocus(panel);\n if (panel !== 'issues') return;\n\n const row = event.row - PANEL_HEADER_ROWS;\n if (row >= 0 && row < visible) selectIndex(offset + row);\n },\n [scrollPanel, selectIndex],\n );\n\n useMouse(onMouse);\n\n // ---- keyboard -----------------------------------------------------------\n useInput((input, key) => {\n if (isMouseSequence(input)) return; // Ink also sees the raw mouse bytes.\n\n if (key.ctrl && input === 'c') {\n exit();\n return;\n }\n if (input === 'q' || key.escape) {\n exit();\n return;\n }\n if (input === 't') {\n // Keep the reading position on the same ticket across the mode switch.\n keepSelectedKey.current = selected?.key ?? null;\n setFullTree((current) => !current);\n return;\n }\n if (input === 'r') {\n setReloadToken((token) => token + 1);\n return;\n }\n if (key.return && focus === 'issues') {\n openSelected();\n return;\n }\n if (key.tab) {\n const step = key.shift ? -1 : 1;\n setFocus((current) => {\n const next = (PANEL_ORDER.indexOf(current) + step + PANEL_ORDER.length) % PANEL_ORDER.length;\n return PANEL_ORDER[next] ?? 'issues';\n });\n return;\n }\n\n // A held key arrives as one chunk (\"jjj\"), so count the repeats.\n if (key.downArrow) scrollPanel(focus, 1);\n else if (key.upArrow) scrollPanel(focus, -1);\n else if (key.pageDown) scrollPanel(focus, issueRows);\n else if (key.pageUp) scrollPanel(focus, -issueRows);\n else if (/^j+$/.test(input)) scrollPanel(focus, input.length);\n else if (/^k+$/.test(input)) scrollPanel(focus, -input.length);\n else if (input === 'g') scrollPanel(focus, -Number.MAX_SAFE_INTEGER);\n else if (input === 'G') selectIndex(issueList.length - 1);\n });\n\n const hint = useMemo(\n () =>\n focus === 'issues'\n ? '\u2191\u2193/jk move \u00B7 enter open \u00B7 t tree \u00B7 tab panel \u00B7 r reload \u00B7 q quit'\n : '\u2191\u2193/jk scroll \u00B7 home/end top/bottom \u00B7 tab panel \u00B7 r reload \u00B7 q quit',\n [focus],\n );\n\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexDirection=\"row\" height={topHeight} flexShrink={0}>\n <WorklogPanel\n state={worklogs}\n focused={focus === 'worklog'}\n height={topHeight}\n width={leftWidth}\n offset={worklogOffset}\n spinnerFrame={spinnerFrame}\n dayMarks={dayMarks}\n />\n <IssueList\n state={issues}\n rows={issueRowsData}\n fullTree={fullTree}\n focused={focus === 'issues'}\n height={topHeight}\n width={columns - leftWidth}\n selectedIndex={selectedIndex}\n offset={issueOffset}\n spinnerFrame={spinnerFrame}\n />\n </Box>\n\n <Box flexDirection=\"row\" height={bottomHeight} flexShrink={0}>\n <WorkChartPanel\n days={chartDays}\n issueKey={selected?.key ?? null}\n state={worklogs}\n focused={false}\n height={bottomHeight}\n width={chartWidth}\n spinnerFrame={spinnerFrame}\n dayMarks={dayMarks}\n />\n <IssuePreview\n issue={selected}\n worklog={(selected && worklogs.data?.byIssue[selected.key]) || []}\n worklogWindowDays={worklogs.data?.windowDays ?? config.worklogDays}\n focused={focus === 'preview'}\n height={bottomHeight}\n width={columns - chartWidth}\n offset={previewOffset}\n />\n </Box>\n\n <Box height={1} flexShrink={0} justifyContent=\"space-between\">\n <Text color=\"gray\" wrap=\"truncate-end\">\n {' '}\n {hint}\n </Text>\n {notice ? (\n <Text color={notice.error ? 'red' : 'green'} wrap=\"truncate-end\">\n {notice.error ? '\u2716' : '\u2713'} {notice.text}{' '}\n </Text>\n ) : loading ? (\n <Text color=\"cyan\" wrap=\"truncate-end\">\n {spinnerChar(spinnerFrame)} loading\u2026{' '}\n </Text>\n ) : (\n <Text color=\"gray\" wrap=\"truncate-end\">\n {me.displayName}{' '}\n </Text>\n )}\n </Box>\n </Box>\n );\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport { PANEL_CHROME_ROWS, PanelFrame } from './PanelFrame';\nimport { PanelError, Spinner, spinnerChar } from './PanelStatus';\nimport { folderMark, treePrefix } from '../tree';\nimport type { Issue, IssueRow, Loadable } from '../types';\n\ninterface IssueListProps {\n state: Loadable<Issue[]>;\n rows: IssueRow[];\n fullTree: boolean;\n focused: boolean;\n height: number;\n width: number;\n selectedIndex: number;\n offset: number;\n spinnerFrame: number;\n}\n\n/** Status colouring keyed on Jira's status category, which is stable per site. */\nfunction statusColor(category: string): string {\n switch (category.toLowerCase()) {\n case 'in progress':\n return 'yellow';\n case 'done':\n return 'green';\n default:\n return 'blue';\n }\n}\n\n/** Truncate or pad `text` so a cell always covers exactly `width` columns. */\nfunction fit(text: string, width: number): string {\n if (width <= 0) return '';\n return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text.padEnd(width);\n}\n\nconst KEY_WIDTH = 10;\nconst STATUS_WIDTH = 14;\n\nexport function IssueList({\n state,\n rows,\n fullTree,\n focused,\n height,\n width,\n selectedIndex,\n offset,\n spinnerFrame,\n}: IssueListProps): ReactElement {\n const issues = state.data ?? [];\n const visibleRows = Math.max(1, height - PANEL_CHROME_ROWS);\n\n // One gutter width for the whole list keeps the key/status columns aligned,\n // and it is capped so deep trees cannot squeeze the summary off screen.\n // +2 for the folder marker column that follows the branch glyphs.\n const gutterWidth = Math.min(\n Math.max(0, Math.floor(width / 4)),\n rows.reduce((widest, row) => Math.max(widest, treePrefix(row).length + 2), 0),\n );\n const summaryWidth = Math.max(8, width - 4 - 2 - gutterWidth - KEY_WIDTH - STATUS_WIDTH);\n\n const label = fullTree ? 'my tickets \u00B7 full tree' : 'my open tickets';\n const base =\n rows.length > visibleRows\n ? `${label} (${selectedIndex + 1}/${rows.length})`\n : `${label} (${rows.length})`;\n // Reloading keeps the old rows on screen, so the spinner moves into the title.\n const title = state.loading && issues.length > 0 ? `${spinnerChar(spinnerFrame)} ${base}` : base;\n\n return (\n <PanelFrame title={title} focused={focused} height={height} flexGrow={1}>\n {state.loading && issues.length === 0 ? (\n <Spinner label=\"loading tickets\u2026\" frame={spinnerFrame} />\n ) : state.error ? (\n <PanelError error={state.error} />\n ) : issues.length === 0 ? (\n <Text color=\"gray\">No open tickets matched the query.</Text>\n ) : (\n <Box flexDirection=\"column\">\n {rows.slice(offset, offset + visibleRows).map((row, index) => {\n const issue = row.issue;\n const selected = offset + index === selectedIndex;\n // The highlight has to paint the padding too, so every cell is\n // padded to its column width instead of relying on Box layout.\n const background = selected ? (focused ? 'cyan' : 'blue') : undefined;\n const cellColor = (own: string): string => (selected ? 'black' : own);\n return (\n <Box key={issue.key}>\n <Text color={cellColor('cyan')} backgroundColor={background}>\n {selected ? '\u25B8 ' : ' '}\n </Text>\n {gutterWidth > 0 ? (\n <Text color={cellColor('gray')} backgroundColor={background}>\n {fit(`${treePrefix(row)}${folderMark(row)} `, gutterWidth)}\n </Text>\n ) : null}\n <Text\n bold={selected}\n color={cellColor(issue.mine ? 'white' : 'gray')}\n backgroundColor={background}\n >\n {fit(issue.key, KEY_WIDTH)}\n </Text>\n <Text\n color={cellColor(statusColor(issue.statusCategory))}\n backgroundColor={background}\n >\n {fit(issue.status, STATUS_WIDTH)}\n </Text>\n <Text\n color={cellColor('gray')}\n bold={selected}\n backgroundColor={background}\n >\n {fit(issue.summary, summaryWidth)}\n </Text>\n </Box>\n );\n })}\n </Box>\n )}\n </PanelFrame>\n );\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement, ReactNode } from 'react';\n\ninterface PanelFrameProps {\n title: string;\n focused: boolean;\n height: number;\n width?: number | string;\n flexGrow?: number;\n children: ReactNode;\n}\n\n/**\n * Bordered panel with a one-line header.\n *\n * The border and header occupy fixed rows, so callers can map a screen row to\n * their own content: content starts at the panel's top + {@link PANEL_HEADER_ROWS}.\n */\nexport const PANEL_HEADER_ROWS = 2; // top border + header line\nexport const PANEL_CHROME_ROWS = 3; // ...plus the bottom border\n\nexport function PanelFrame({\n title,\n focused,\n height,\n width,\n flexGrow,\n children,\n}: PanelFrameProps): ReactElement {\n return (\n <Box\n flexDirection=\"column\"\n height={height}\n width={width}\n flexGrow={flexGrow}\n // A panel given an explicit width must keep it: shrinking one column makes\n // fixed-width content (chart rows, aligned labels) wrap and fall apart.\n flexShrink={width === undefined ? 1 : 0}\n borderStyle=\"round\"\n borderColor={focused ? 'cyan' : 'gray'}\n paddingX={1}\n overflow=\"hidden\"\n >\n <Text bold color={focused ? 'cyan' : 'white'}>\n {title}\n </Text>\n {children}\n </Box>\n );\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport type { JiraError } from '../types';\n\n/** Spinner frames chosen to render on any terminal that supports Braille. */\nconst FRAMES = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];\n\n/** Current spinner glyph, for inlining into a panel title or status bar. */\nexport function spinnerChar(frame: number): string {\n return FRAMES[frame % FRAMES.length] ?? FRAMES[0] ?? '\u00B7';\n}\n\nexport function Spinner({ label, frame }: { label: string; frame: number }): ReactElement {\n return (\n <Text color=\"cyan\">\n {spinnerChar(frame)} <Text color=\"gray\">{label}</Text>\n </Text>\n );\n}\n\n/** Panel-local error: one panel failing must not blank the other. */\nexport function PanelError({ error }: { error: JiraError }): ReactElement {\n return (\n <Box flexDirection=\"column\">\n <Text color=\"red\" wrap=\"truncate-end\">\n \u2716 {error.message}\n </Text>\n {error.hints.slice(0, 3).map((hint, index) => (\n <Text key={index} color=\"gray\" wrap=\"truncate-end\">\n {hint}\n </Text>\n ))}\n <Text color=\"gray\">Press r to retry.</Text>\n </Box>\n );\n}\n", "import type { Issue, IssueRow } from './types';\n\n/**\n * Flatten issues into tree rows.\n *\n * In `full` mode every loaded ancestor gets its own row, so a subtask sits\n * under its story under its epic even when those levels are someone else's\n * work. Otherwise only the user's own issues are rendered, re-parented onto\n * their nearest own ancestor \u2014 an epic of mine with a foreign story in between\n * still adopts my subtask directly.\n *\n * Order follows the JQL result: each issue appears as soon as its branch does.\n */\nexport function buildIssueTree(issues: Issue[], full: boolean): IssueRow[] {\n const byKey = new Map(issues.map((issue) => [issue.key, issue]));\n const visible = full ? issues : issues.filter((issue) => issue.mine);\n const visibleKeys = new Set(visible.map((issue) => issue.key));\n\n /** Nearest ancestor that is itself rendered, or null for a root. */\n const parentOf = (issue: Issue): string | null => {\n let key = issue.parent?.key ?? null;\n const seen = new Set<string>([issue.key]);\n while (key && !seen.has(key)) {\n seen.add(key);\n if (visibleKeys.has(key)) return key;\n key = byKey.get(key)?.parent?.key ?? null;\n }\n return null;\n };\n\n const roots: Issue[] = [];\n const children = new Map<string, Issue[]>();\n\n for (const issue of visible) {\n const parent = parentOf(issue);\n if (parent === null) {\n roots.push(issue);\n continue;\n }\n const siblings = children.get(parent);\n if (siblings) siblings.push(issue);\n else children.set(parent, [issue]);\n }\n\n const rows: IssueRow[] = [];\n\n const walk = (issue: Issue, guides: boolean[], last: boolean): void => {\n const kids = children.get(issue.key) ?? [];\n rows.push({ issue, guides, last, hasChildren: kids.length > 0 });\n kids.forEach((kid, index) => {\n // One guide per ancestor edge: the column stays open while that ancestor\n // still has siblings queued below it.\n walk(kid, [...guides, !last], index === kids.length - 1);\n });\n };\n\n // Roots hang off a virtual trunk, so they carry branch marks of their own.\n roots.forEach((root, index) => {\n walk(root, [], index === roots.length - 1);\n });\n\n return rows;\n}\n\n/** The `\u2502 \u251C\u2500 ` gutter for a row, one level of indent per ancestor. */\nexport function treePrefix(row: IssueRow): string {\n const stem = row.guides.map((continues) => (continues ? '\u2502 ' : ' ')).join('');\n return `${stem}${row.last ? '\u2514\u2500 ' : '\u251C\u2500 '}`;\n}\n\n/** Folder marker: filled for a row with children, blank for a leaf. */\nexport function folderMark(row: IssueRow): string {\n return row.hasChildren ? '\u25BE' : ' ';\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport { PANEL_CHROME_ROWS, PanelFrame } from './PanelFrame';\nimport type { Issue, IssueWorklogEntry } from '../types';\n\ninterface IssuePreviewProps {\n issue: Issue | null;\n /** The current user's worklog entries for this issue, within the window. */\n worklog: IssueWorklogEntry[];\n /** How many days that worklog window covers, for labelling. */\n worklogWindowDays: number;\n focused: boolean;\n height: number;\n width: number;\n offset: number;\n}\n\nconst MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\nconst FIELD_LABEL_WIDTH = 11;\n\nfunction formatUpdated(value: string | null): string {\n if (!value) return 'unknown';\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n return date.toLocaleString();\n}\n\n/** `28 Jul` from a `YYYY-MM-DD` key. */\nfunction formatDay(key: string): string {\n const [, month = '01', day = '01'] = key.split('-');\n return `${day} ${MONTHS[Number(month) - 1] ?? month}`;\n}\n\n/** `7.5h`, or `45m` for sub-hour amounts where decimals read badly. */\nfunction formatHours(hours: number): string {\n if (hours === 0) return '0h';\n return hours < 1 ? `${Math.round(hours * 60)}m` : `${hours.toFixed(1)}h`;\n}\n\n/**\n * Parent chain, most general first: project \u2192 parent \u2192 this issue.\n *\n * Jira's `parent` field only exposes one level, so a subtask shows its story but\n * not the story's epic.\n */\nfunction breadcrumbs(issue: Issue): string {\n const parts = [issue.projectName, issue.parent && `${issue.parent.key} ${issue.parent.summary}`]\n .filter((part): part is string => Boolean(part))\n .map((part) => part.trim());\n return [...parts, issue.key].join(' \u203A ');\n}\n\n/** Hard-wrap description text so we can window it by line for scrolling. */\nfunction wrapLines(text: string, width: number): string[] {\n const out: string[] = [];\n for (const line of text.split('\\n')) {\n if (line.length <= width) {\n out.push(line);\n continue;\n }\n let rest = line;\n while (rest.length > width) {\n const cut = rest.lastIndexOf(' ', width);\n const at = cut > width / 2 ? cut : width;\n out.push(rest.slice(0, at));\n rest = rest.slice(at).trimStart();\n }\n out.push(rest);\n }\n return out;\n}\n\nfunction Field({ label, value, color }: { label: string; value: string; color?: string }): ReactElement {\n return (\n <Box>\n <Box width={FIELD_LABEL_WIDTH} flexShrink={0}>\n <Text color=\"gray\">{label}</Text>\n </Box>\n <Text color={color} wrap=\"truncate-end\">\n {value}\n </Text>\n </Box>\n );\n}\n\nexport function IssuePreview({\n issue,\n worklog,\n worklogWindowDays,\n focused,\n height,\n width,\n offset,\n}: IssuePreviewProps): ReactElement {\n if (!issue) {\n return (\n <PanelFrame title=\"preview\" focused={focused} height={height} flexGrow={1}>\n <Text color=\"gray\">Select a ticket above to preview it.</Text>\n </PanelFrame>\n );\n }\n\n const loggedHours = worklog.reduce((sum, entry) => sum + entry.hours, 0);\n const loggedDates = worklog.map((entry) => `${formatDay(entry.date)} (${formatHours(entry.hours)})`);\n const myWork =\n worklog.length > 0\n ? `${formatHours(loggedHours)} \u00B7 ${loggedDates.join(', ')}`\n : `nothing in the last ${worklogWindowDays} days`;\n\n const total =\n issue.totalTimeSpentSeconds !== null && issue.totalTimeSpentSeconds > 0\n ? `${formatHours(issue.totalTimeSpentSeconds / 3600)} logged by everyone`\n : null;\n\n // Breadcrumbs + summary + type/status/sprint/assignee/reporter/priority/labels/\n // your work/updated. Keep in step with the fields rendered below.\n const fieldRows = 11 + (total ? 1 : 0);\n const lines = wrapLines(issue.description || '(no description)', Math.max(20, width - 4));\n\n // Fields are fixed-height; the description gets the rest, minus one row for the\n // \"more lines\" indicator whenever it will be shown. Overshooting here makes Ink\n // clip the panel, which silently eats the title row.\n const available = Math.max(1, height - PANEL_CHROME_ROWS - fieldRows);\n\n // Clamp so scrolling past the end keeps the last screenful visible instead of\n // sliding into blank space.\n const start = Math.min(offset, Math.max(0, lines.length - available));\n const bodyRows = lines.length - start > available ? Math.max(1, available - 1) : available;\n\n const visible = lines.slice(start, start + bodyRows);\n const more = lines.length - start - visible.length;\n\n return (\n <PanelFrame\n title={`${issue.key} \u00B7 ${issue.url}`}\n focused={focused}\n height={height}\n flexGrow={1}\n >\n <Text color=\"gray\" wrap=\"truncate-end\">\n {breadcrumbs(issue)}\n </Text>\n <Text bold wrap=\"truncate-end\">\n {issue.summary}\n </Text>\n <Field label=\"type\" value={issue.issueType || '\u2014'} />\n <Field label=\"status\" value={issue.status} />\n <Field label=\"sprint\" value={issue.sprints.length > 0 ? issue.sprints.join(', ') : 'None'} />\n <Field label=\"assignee\" value={issue.assignee ?? 'Unassigned'} />\n <Field label=\"reporter\" value={issue.reporter ?? '\u2014'} />\n <Field label=\"priority\" value={issue.priority ?? '\u2014'} />\n <Field label=\"labels\" value={issue.labels.length > 0 ? issue.labels.join(', ') : '\u2014'} />\n <Field\n label=\"your work\"\n value={myWork}\n color={worklog.length > 0 ? 'green' : undefined}\n />\n {total && <Field label=\"total work\" value={total} />}\n <Field label=\"updated\" value={formatUpdated(issue.updated)} />\n\n <Box flexDirection=\"column\">\n {visible.map((line, index) => (\n <Text key={start + index} color=\"white\" wrap=\"truncate-end\">\n {line || ' '}\n </Text>\n ))}\n {more > 0 && <Text color=\"gray\">\u2026 {more} more lines (\u2193 to scroll)</Text>}\n </Box>\n </PanelFrame>\n );\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport { PANEL_CHROME_ROWS, PanelFrame } from './PanelFrame';\nimport { PanelError, Spinner } from './PanelStatus';\nimport type { DayMarks } from '../data/calendar';\nimport { dayOfMonth, isToday, isWeekend, MONTHS } from '../dates';\nimport type { Loadable, WorklogDay, WorklogSummary } from '../types';\n\ninterface WorkChartPanelProps {\n /** One entry per day in the window, oldest first, already scoped to an issue. */\n days: WorklogDay[];\n /** The issue the bars belong to; `null` when nothing is selected. */\n issueKey: string | null;\n /** Loading/error state of the worklog fetch the days come from. */\n state: Loadable<WorklogSummary>;\n focused: boolean;\n height: number;\n width: number;\n spinnerFrame: number;\n /** Holidays and personal days off, so a bare column can be read as expected. */\n dayMarks: DayMarks;\n}\n\n/** One full row of bar equals this many hours; half a row equals half of it. */\nexport const HOURS_PER_BAR = 4;\n\n/** Rows reserved at the bottom for the day labels. */\nconst LABEL_ROWS = 1;\n\n/** A full working day is two rows, so never draw a shorter plot than that. */\nconst MIN_BAR_ROWS = 2;\n\nconst FULL_BLOCK = '\u2588';\nconst HALF_BLOCK = '\u2584';\n/** Anything logged but under half a bar, so a short day still reads as worked. */\nconst TRACE_BLOCK = '\u2581';\n\n/** Borders (2) + horizontal padding (2), plus one spare column of slack. */\nconst PANEL_SIDE_COLUMNS = 5;\n\n/** Marks a non-working day that has no bar, which would otherwise be blank. */\nconst REST_DOT = '\u00B7';\n\n/**\n * Width the chart needs for `dayCount` bars at the given bar width, including a\n * one-column gap between bars. A row that overflows by even one column wraps and\n * destroys the chart, so this has to be exact.\n */\nexport function chartWidthFor(dayCount: number, barWidth: number): number {\n const bars = dayCount * barWidth + Math.max(0, dayCount - 1);\n return bars + PANEL_SIDE_COLUMNS;\n}\n\n/**\n * Vertical bar chart of hours logged per day on one issue.\n *\n * The scale is fixed rather than fitted to the data, so a bar height means the\n * same thing between issues and between reloads: 2h is half a row, 4h one row,\n * 8h two rows.\n */\nexport function WorkChartPanel({\n days,\n issueKey,\n state,\n focused,\n height,\n width,\n spinnerFrame,\n dayMarks,\n}: WorkChartPanelProps): ReactElement {\n if (state.loading && days.length === 0) {\n return (\n <PanelFrame title=\"logged work\" focused={focused} height={height} width={width}>\n <Spinner label=\"loading\u2026\" frame={spinnerFrame} />\n </PanelFrame>\n );\n }\n if (state.error) {\n return (\n <PanelFrame title=\"logged work\" focused={focused} height={height} width={width}>\n <PanelError error={state.error} />\n </PanelFrame>\n );\n }\n if (!issueKey || days.length === 0) {\n return (\n <PanelFrame title=\"logged work\" focused={focused} height={height} width={width}>\n <Text color=\"gray\">Select a ticket to see your work on it.</Text>\n </PanelFrame>\n );\n }\n\n // Two columns per bar reads better, but fall back to one when space is tight.\n const barWidth = chartWidthFor(days.length, 2) <= width ? 2 : 1;\n\n const tallest = days.reduce((max, day) => Math.max(max, day.hours), 0);\n const roomForRows = Math.max(1, height - PANEL_CHROME_ROWS - LABEL_ROWS);\n const barRows = Math.min(roomForRows, Math.max(MIN_BAR_ROWS, Math.ceil(tallest / HOURS_PER_BAR)));\n\n // Drawn top-down, so the first row is the highest and the last is the baseline.\n const rows = Array.from({ length: barRows }, (_, index) => barRows - 1 - index);\n\n const total = days.reduce((sum, day) => sum + day.hours, 0);\n const clipped = tallest / HOURS_PER_BAR > barRows;\n const title =\n `${issueKey} \u00B7 ${total.toFixed(1)}h over ${days.length}d \u00B7 ${HOURS_PER_BAR}h = 1 bar` +\n (clipped ? ` \u00B7 peak ${tallest.toFixed(1)}h` : '');\n\n return (\n <PanelFrame title={title} focused={focused} height={height} width={width}>\n {/* Bars grow upward from a baseline pinned to the bottom of the panel. */}\n <Box flexDirection=\"column\" flexGrow={1} justifyContent=\"flex-end\">\n {/* An all-blank plot reads as broken, so say so instead. */}\n {total === 0 && <Text color=\"gray\">no time logged in this window</Text>}\n {total > 0 &&\n rows.map((rowFromBottom) => (\n <Text key={rowFromBottom}>\n {days.map((day, index) => {\n // Only days that are actually off for you get the rest treatment;\n // a holiday observed elsewhere is still a working day here.\n const rest = dayMarks.get(day.date)?.observed === true;\n // A rest day with no bar still gets a baseline dot, so it never\n // reads as an unexplained gap.\n const glyph =\n rest && day.hours === 0 && rowFromBottom === 0\n ? REST_DOT\n : glyphFor(day.hours, rowFromBottom);\n return (\n <Text key={day.date}>\n {index > 0 ? ' ' : ''}\n <Text color={barColor(day, rest)}>{glyph.repeat(barWidth)}</Text>\n </Text>\n );\n })}\n </Text>\n ))}\n\n {barWidth >= 2 ? (\n <Text>\n {days.map((day, index) => (\n <Text key={day.date}>\n {index > 0 ? ' ' : ''}\n <Text\n color={\n isToday(day.date)\n ? 'cyan'\n : dayMarks.get(day.date)?.observed\n ? 'magenta'\n : 'gray'\n }\n bold={isToday(day.date)}\n >\n {dayOfMonth(day.date)}\n </Text>\n </Text>\n ))}\n </Text>\n ) : (\n // One column per bar leaves no room for a label per day; a date range\n // reads better than a row of single digits.\n <Text color=\"gray\" wrap=\"truncate-end\">\n {rangeLabel(days)}\n </Text>\n )}\n </Box>\n </PanelFrame>\n );\n}\n\n/**\n * Which block character a bar shows on a given row.\n *\n * `rowFromBottom` 0 is the baseline. A row is a full block once the day's hours\n * reach its top and a half block once they reach its middle, so 2h renders as a\n * half block sitting on the baseline. Below that, any logged time still gets a\n * thin mark \u2014 an hour of work must not look the same as none.\n */\nfunction glyphFor(hours: number, rowFromBottom: number): string {\n const rowsFilled = hours / HOURS_PER_BAR - rowFromBottom;\n if (rowsFilled >= 1) return FULL_BLOCK;\n if (rowsFilled >= 0.5) return HALF_BLOCK;\n if (rowsFilled > 0) return TRACE_BLOCK;\n return ' ';\n}\n\n/** Rest days are tinted differently so an empty column reads as expected. */\nfunction barColor(day: WorklogDay, rest: boolean): string {\n if (rest) return 'magenta';\n if (day.hours === 0) return 'gray';\n return isWeekend(day.date) ? 'blue' : 'green';\n}\n\n/** `16 Jul \u2192 29 Jul`, for when per-bar labels do not fit. */\nfunction rangeLabel(days: WorklogDay[]): string {\n const format = (key: string): string => {\n const [, month = '01', day = '01'] = key.split('-');\n return `${day} ${MONTHS[Number(month) - 1] ?? month}`;\n };\n const first = days[0]?.date;\n const last = days[days.length - 1]?.date;\n return first && last ? `${format(first)} \u2192 ${format(last)}` : '';\n}\n", "export const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\nexport const MONTHS = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\n/** A `YYYY-MM-DD` key as a local date, so day boundaries match the user's clock. */\nexport function parseDate(key: string): Date {\n const [year = '1970', month = '01', day = '01'] = key.split('-');\n return new Date(Number(year), Number(month) - 1, Number(day));\n}\n\n/** `Mon 28 Jul` from a `YYYY-MM-DD` key. */\nexport function formatDate(key: string): string {\n const [, , day = '01'] = key.split('-');\n const date = parseDate(key);\n return `${WEEKDAYS[date.getDay()]} ${day} ${MONTHS[date.getMonth()]}`;\n}\n\nexport function isWeekend(key: string): boolean {\n const weekday = parseDate(key).getDay();\n return weekday === 0 || weekday === 6;\n}\n\nexport function isToday(key: string): boolean {\n return parseDate(key).toDateString() === new Date().toDateString();\n}\n\n/** Two-digit day of month, so labels line up under their two-column bars. */\nexport function dayOfMonth(key: string): string {\n const [, , day = '01'] = key.split('-');\n return day.padStart(2, '0');\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport { PANEL_CHROME_ROWS, PanelFrame } from './PanelFrame';\nimport { PanelError, Spinner, spinnerChar } from './PanelStatus';\nimport { isNonWorkingDay, type DayMark, type DayMarks } from '../data/calendar';\nimport { formatDate, isWeekend } from '../dates';\nimport type { Loadable, WorklogSummary } from '../types';\n\ninterface WorklogPanelProps {\n state: Loadable<WorklogSummary>;\n focused: boolean;\n height: number;\n width: number;\n offset: number;\n spinnerFrame: number;\n /** Holidays and personal days off, so empty days can be told apart. */\n dayMarks: DayMarks;\n}\n\n/**\n * `Mon 28 Jul` (10) plus a marker glyph and a two-cell emoji badge, and one\n * column of gap before the hours.\n */\nconst DATE_WIDTH = 14;\n\n/** Colour hours by how close the day is to a full working day. */\nfunction hoursColor(hours: number, nonWorking: boolean): string {\n if (hours === 0) return nonWorking ? 'gray' : 'red';\n if (hours < 6) return 'yellow';\n return 'green';\n}\n\n/** A trailing glyph on the date keeps holidays legible without extra columns. */\nfunction markGlyph(mark: DayMark | undefined): string {\n if (!mark) return '';\n if (mark.kind === 'dayoff') return '\u00B7';\n // A holiday you do not observe is worth knowing about, but it is not your day off.\n return mark.observed ? '*' : '~';\n}\n\n/**\n * Statutory holidays get a badge next to the date; personal leave keeps its\n * plainer `\u00B7`, so the emoji stays a reliable \"public holiday\" signal.\n */\nfunction markBadge(mark: DayMark | undefined): string {\n return mark?.kind === 'holiday' ? '\uD83C\uDF89' : '';\n}\n\nexport function WorklogPanel({\n state,\n focused,\n height,\n width,\n offset,\n spinnerFrame,\n dayMarks,\n}: WorklogPanelProps): ReactElement {\n const days = state.data?.days ?? [];\n const visibleRows = Math.max(1, height - PANEL_CHROME_ROWS);\n const total = days.reduce((sum, day) => sum + day.hours, 0);\n // Panel width minus borders, padding, date column and hours column.\n const keysWidth = Math.max(6, width - 4 - DATE_WIDTH - 7);\n\n const base =\n days.length > 0\n ? `last ${days.length} days \u00B7 ${total.toFixed(1)}h total`\n : `last ${days.length || 14} days`;\n // Reloading keeps the old rows on screen, so the spinner moves into the title.\n const title = state.loading && days.length > 0 ? `${spinnerChar(spinnerFrame)} ${base}` : base;\n\n return (\n <PanelFrame title={title} focused={focused} height={height} width={width}>\n {state.loading && days.length === 0 ? (\n <Spinner label=\"loading worklogs\u2026\" frame={spinnerFrame} />\n ) : state.error ? (\n <PanelError error={state.error} />\n ) : (\n <Box flexDirection=\"column\">\n {days.slice(offset, offset + visibleRows).map((day) => {\n const mark = dayMarks.get(day.date);\n const weekend = isWeekend(day.date);\n const nonWorking = isNonWorkingDay(day.date, dayMarks);\n // With nothing logged, the reason the day is empty is the useful thing to show.\n const keys =\n day.issueKeys.length > 0 ? day.issueKeys.join(', ') : mark ? `(${mark.label})` : '\u2014';\n return (\n <Box key={day.date}>\n <Box width={DATE_WIDTH} flexShrink={0}>\n <Text\n color={mark ? 'magenta' : weekend ? 'gray' : 'white'}\n dimColor={mark !== undefined && !mark.observed}\n >\n {formatDate(day.date) + markGlyph(mark) + markBadge(mark)}\n </Text>\n </Box>\n <Box width={7} flexShrink={0}>\n <Text color={hoursColor(day.hours, nonWorking)}>\n {`${day.hours.toFixed(1)}h`.padStart(6)}\n </Text>\n </Box>\n <Box width={keysWidth} flexShrink={1}>\n <Text\n color={mark && day.issueKeys.length === 0 ? 'magenta' : 'gray'}\n dimColor={mark !== undefined && !mark.observed}\n wrap=\"truncate-end\"\n >\n {keys}\n </Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n )}\n </PanelFrame>\n );\n}\n", "import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { CONFIG_DIR } from './config';\nimport { isWeekend } from '../dates';\nimport type { Config } from '../types';\n\nexport const HOLIDAY_CACHE_PATH = join(CONFIG_DIR, 'holidays.json');\nexport const DAYS_OFF_PATH = join(CONFIG_DIR, 'days-off.json');\n\nconst NAGER_URL = 'https://date.nager.at/api/v3/PublicHolidays';\nconst REQUEST_TIMEOUT_MS = 20_000;\n\n/** Holiday tables get corrected after publication, so cached years expire. */\nconst CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport type DayMarkKind = 'holiday' | 'dayoff';\n\nexport interface DayMark {\n kind: DayMarkKind;\n label: string;\n /**\n * Whether the day is actually off for *you*. Only reachable as `false` when\n * no region is configured: a holiday some regions observe \u2014 Canada's Civic\n * Holiday, say \u2014 is then still worth showing, since we cannot tell whether it\n * is yours, but it does not excuse an empty timesheet. Once `holidayRegion`\n * is set, holidays that miss it are dropped instead.\n */\n observed: boolean;\n}\n\n/** Non-working days keyed by local `YYYY-MM-DD` date. */\nexport type DayMarks = Map<string, DayMark>;\n\n/** A day nobody is expected to log hours on. */\nexport function isNonWorkingDay(key: string, marks: DayMarks): boolean {\n return isWeekend(key) || marks.get(key)?.observed === true;\n}\n\nexport interface CachedHoliday {\n date: string;\n name: string;\n /** Subdivisions observing it; absent or empty means the whole country does. */\n counties?: string[];\n}\n\ninterface CachedYear {\n fetchedAt: string;\n days: CachedHoliday[];\n}\n\n/**\n * Bumped when the cached shape changes so old files are discarded rather than\n * misread. v1 stored only nationwide holidays; v2 keeps regional ones too.\n */\nconst CACHE_VERSION = 2;\n\ninterface HolidayCache {\n version: number;\n country: string | null;\n years: Record<string, CachedYear>;\n}\n\nconst DATE_KEY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Statutory holidays plus personal days off across the given years.\n *\n * Never throws: a failed fetch falls back to whatever is cached, and a missing\n * cache to no holidays at all. A dashboard that starts without holiday colours\n * beats one that does not start.\n */\nexport async function loadDayMarks(config: Config, years: number[]): Promise<DayMarks> {\n return buildDayMarks(await loadHolidays(config, years), config.holidayRegion, readDaysOff());\n}\n\n/**\n * The marking rule on its own, free of network and disk so it can be tested\n * directly: which holidays survive, which count as days off, and how personal\n * leave overrides them.\n */\nexport function buildDayMarks(\n holidays: CachedHoliday[],\n region: string | null,\n daysOff: Array<[string, string]>,\n): DayMarks {\n const marks: DayMarks = new Map();\n\n // One date can carry several regional holidays \u2014 Canada's first Monday in\n // August is Civic Holiday, Saskatchewan Day and BC Day at once \u2014 so they are\n // merged rather than overwriting each other.\n const regional = new Map<string, { names: string[]; counties: string[] }>();\n\n for (const holiday of holidays) {\n const counties = holiday.counties ?? [];\n const observed = counties.length === 0 || (region !== null && counties.includes(region));\n\n if (observed) {\n // An observed holiday always wins the date over merely-regional ones.\n marks.set(holiday.date, { kind: 'holiday', label: holiday.name, observed: true });\n continue;\n }\n\n // Once you have named your region, another region's holiday is simply not\n // yours \u2014 showing it only makes a working day look like a day off. Without\n // a region there is no way to tell, so they are listed instead.\n if (region !== null) continue;\n\n const group = regional.get(holiday.date) ?? { names: [], counties: [] };\n if (!group.names.includes(holiday.name)) group.names.push(holiday.name);\n for (const county of counties) if (!group.counties.includes(county)) group.counties.push(county);\n regional.set(holiday.date, group);\n }\n\n for (const [date, { names, counties }] of regional) {\n if (marks.has(date)) continue;\n // Naming the regions explains why the day is flagged but still yours to work.\n marks.set(date, {\n kind: 'holiday',\n label: `${names.join(' / ')} \u00B7 ${counties.join(', ')}`,\n observed: false,\n });\n }\n\n // Personal leave wins over a holiday on the same date \u2014 it is the more\n // specific statement about the day.\n for (const [date, label] of daysOff) {\n marks.set(date, { kind: 'dayoff', label, observed: true });\n }\n\n return marks;\n}\n\nasync function loadHolidays(config: Config, years: number[]): Promise<CachedHoliday[]> {\n const country = config.holidayCountry;\n if (!country) return [];\n\n const cached = readCache();\n // A different country makes every cached year meaningless. The region is not\n // part of the key: every holiday is cached with the regions observing it, so\n // changing region only re-reads what is already on disk.\n const cache: HolidayCache =\n cached && cached.country === country\n ? cached\n : { version: CACHE_VERSION, country, years: {} };\n\n const stale = years.filter((year) => !isFresh(cache.years[String(year)]));\n const fetched = await Promise.all(\n stale.map(async (year) => [year, await fetchYear(country, year)] as const),\n );\n\n let changed = false;\n for (const [year, days] of fetched) {\n if (!days) continue; // Fetch failed; keep any stale entry rather than losing it.\n cache.years[String(year)] = { fetchedAt: new Date().toISOString(), days };\n changed = true;\n }\n if (changed || cached !== cache) writeCache(cache);\n\n return years.flatMap((year) => cache.years[String(year)]?.days ?? []);\n}\n\nfunction isFresh(entry: CachedYear | undefined): boolean {\n if (!entry) return false;\n const age = Date.now() - Date.parse(entry.fetchedAt);\n return Number.isFinite(age) && age >= 0 && age < CACHE_TTL_MS;\n}\n\ninterface NagerHoliday {\n date?: unknown;\n localName?: unknown;\n name?: unknown;\n global?: unknown;\n counties?: unknown;\n}\n\n/** `null` on any failure, so the caller can fall back to the cache. */\nasync function fetchYear(country: string, year: number): Promise<CachedHoliday[] | null> {\n try {\n const response = await fetch(`${NAGER_URL}/${year}/${country}`, {\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n if (!response.ok) return null;\n\n const body: unknown = await response.json();\n if (!Array.isArray(body)) return null;\n\n const days: CachedHoliday[] = [];\n for (const entry of body as NagerHoliday[]) {\n const date = entry.date;\n if (typeof date !== 'string' || !DATE_KEY.test(date)) continue;\n // Regional holidays are kept, not dropped: which regions observe them is\n // decided at read time against the configured region.\n const counties =\n entry.global === true || !Array.isArray(entry.counties)\n ? []\n : entry.counties.filter((county): county is string => typeof county === 'string');\n const name = typeof entry.localName === 'string' ? entry.localName : entry.name;\n days.push({ date, name: typeof name === 'string' ? name : 'holiday', counties });\n }\n return days;\n } catch {\n return null;\n }\n}\n\nfunction readCache(): HolidayCache | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(HOLIDAY_CACHE_PATH, 'utf8'));\n } catch {\n return null;\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n\n const { version, country, years } = parsed as Partial<HolidayCache>;\n if (version !== CACHE_VERSION) return null;\n if (typeof years !== 'object' || years === null) return null;\n\n return {\n version: CACHE_VERSION,\n country: typeof country === 'string' ? country : null,\n years,\n };\n}\n\nfunction writeCache(cache: HolidayCache): void {\n // Written via a temp file so a crash mid-write cannot leave truncated JSON\n // that every later run would have to throw away.\n const temp = `${HOLIDAY_CACHE_PATH}.tmp`;\n try {\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(temp, `${JSON.stringify(cache, null, 2)}\\n`, 'utf8');\n renameSync(temp, HOLIDAY_CACHE_PATH);\n } catch {\n // A read-only config dir costs colours, not the session.\n }\n}\n\n/**\n * Hand-maintained leave from `days-off.json`. Accepts a bare array or\n * `{ \"dates\": [...] }`, holding either date strings or `{ date, label }`.\n * Malformed entries are skipped rather than failing the whole file.\n */\nfunction readDaysOff(): Array<[string, string]> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(DAYS_OFF_PATH, 'utf8'));\n } catch {\n return [];\n }\n\n const list = Array.isArray(parsed)\n ? parsed\n : typeof parsed === 'object' && parsed !== null && Array.isArray((parsed as { dates?: unknown }).dates)\n ? ((parsed as { dates: unknown[] }).dates)\n : [];\n\n const days: Array<[string, string]> = [];\n for (const entry of list) {\n if (typeof entry === 'string') {\n if (DATE_KEY.test(entry)) days.push([entry, 'day off']);\n continue;\n }\n if (typeof entry !== 'object' || entry === null) continue;\n const { date, label } = entry as { date?: unknown; label?: unknown };\n if (typeof date !== 'string' || !DATE_KEY.test(date)) continue;\n days.push([date, typeof label === 'string' && label.trim() ? label.trim() : 'day off']);\n }\n return days;\n}\n", "import { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { JiraError, type Config } from '../types';\n\n/** Everything the dashboard persists \u2014 config, caches, hand-edited lists \u2014 lives here. */\nexport const CONFIG_DIR = join(\n process.env['XDG_CONFIG_HOME'] ?? join(homedir(), '.config'),\n 'jira-dashboard',\n);\n\nexport const CONFIG_PATH = join(CONFIG_DIR, 'config.json');\n\nconst TOKEN_URL = 'https://id.atlassian.com/manage-profile/security/api-tokens';\n\nexport const DEFAULT_ISSUES_JQL = [\n 'assignee = currentUser()',\n 'AND statusCategory != Done',\n 'AND status NOT IN (\"Cancelled\", \"Closed\", \"Backlog\")',\n 'ORDER BY updated DESC',\n].join(' ');\n\nconst DEFAULT_WORKLOG_DAYS = 14;\n\ninterface FileConfig {\n baseUrl?: string;\n email?: string;\n apiToken?: string;\n issuesJql?: string;\n worklogDays?: number;\n holidayCountry?: string;\n holidayRegion?: string;\n}\n\n/** Trim and upper-case a code, collapsing blank/absent values to `null`. */\nfunction normalizeCode(value: string | undefined): string | null {\n const trimmed = value?.trim().toUpperCase();\n return trimmed ? trimmed : null;\n}\n\nfunction readConfigFile(): FileConfig {\n let raw: string;\n try {\n raw = readFileSync(CONFIG_PATH, 'utf8');\n } catch {\n return {};\n }\n\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('expected a JSON object');\n }\n return parsed;\n } catch (cause) {\n throw new JiraError(`Config file at ${CONFIG_PATH} is not valid JSON.`, [\n cause instanceof Error ? cause.message : String(cause),\n 'Fix the file, or delete it and use environment variables instead.',\n ]);\n }\n}\n\n/** Strip a trailing slash so path joining stays predictable. */\nfunction normalizeBaseUrl(value: string): string {\n const trimmed = value.trim().replace(/\\/+$/, '');\n if (!/^https?:\\/\\//.test(trimmed)) {\n throw new JiraError(`JIRA_BASE_URL must start with https:// (got \"${trimmed}\")`, [\n 'Example: https://your-team.atlassian.net',\n ]);\n }\n return trimmed;\n}\n\n/**\n * Resolve credentials from the environment first, then the config file.\n * Throws a JiraError carrying setup instructions when anything is missing.\n */\nexport function loadConfig(): Config {\n const file = readConfigFile();\n\n const baseUrl = process.env['JIRA_BASE_URL'] ?? file.baseUrl;\n const email = process.env['JIRA_EMAIL'] ?? file.email;\n const apiToken = process.env['JIRA_API_TOKEN'] ?? file.apiToken;\n\n const missing: string[] = [];\n if (!baseUrl) missing.push('JIRA_BASE_URL');\n if (!email) missing.push('JIRA_EMAIL');\n if (!apiToken) missing.push('JIRA_API_TOKEN');\n\n if (missing.length > 0 || !baseUrl || !email || !apiToken) {\n throw new JiraError(`Missing Jira credentials: ${missing.join(', ')}`, [\n 'Set them in your shell:',\n ' export JIRA_BASE_URL=\"https://your-team.atlassian.net\"',\n ' export JIRA_EMAIL=\"you@example.com\"',\n ' export JIRA_API_TOKEN=\"...\"',\n '',\n `Or write them to ${CONFIG_PATH}:`,\n ' { \"baseUrl\": \"...\", \"email\": \"...\", \"apiToken\": \"...\" }',\n '',\n `Create an API token at ${TOKEN_URL}`,\n ]);\n }\n\n return {\n baseUrl: normalizeBaseUrl(baseUrl),\n email: email.trim(),\n apiToken: apiToken.trim(),\n issuesJql: file.issuesJql?.trim() || DEFAULT_ISSUES_JQL,\n worklogDays:\n typeof file.worklogDays === 'number' && file.worklogDays > 0\n ? Math.floor(file.worklogDays)\n : DEFAULT_WORKLOG_DAYS,\n holidayCountry: normalizeCode(process.env['JIRA_HOLIDAY_COUNTRY'] ?? file.holidayCountry),\n holidayRegion: normalizeCode(process.env['JIRA_HOLIDAY_REGION'] ?? file.holidayRegion),\n };\n}\n", "export interface Config {\n baseUrl: string;\n email: string;\n apiToken: string;\n /** Overridable JQL for the open-tickets panel. */\n issuesJql: string;\n /** How many days of worklog history the left panel covers. */\n worklogDays: number;\n /** ISO-3166 alpha-2 country whose statutory holidays count as non-working days. */\n holidayCountry: string | null;\n /** Optional Nager subdivision code (e.g. \"US-CA\") to narrow regional holidays. */\n holidayRegion: string | null;\n}\n\nexport interface Issue {\n key: string;\n summary: string;\n status: string;\n statusCategory: string;\n issueType: string;\n priority: string | null;\n assignee: string | null;\n reporter: string | null;\n labels: string[];\n updated: string | null;\n description: string;\n url: string;\n projectName: string | null;\n /** Immediate parent (epic for a story, story for a subtask), if any. */\n parent: { key: string; summary: string; issueType: string } | null;\n /** Total time logged on the issue by everyone, per Jira's aggregate. */\n totalTimeSpentSeconds: number | null;\n /** Sprint names the issue belongs to, active ones first; empty if none. */\n sprints: string[];\n /** True when the issue came from the user's own JQL, false for a pulled-in ancestor. */\n mine: boolean;\n}\n\n/** One rendered line of the issue tree. */\nexport interface IssueRow {\n issue: Issue;\n /**\n * Ancestor guides, outermost first: `true` where that ancestor still has\n * siblings below it (draw `\u2502`), `false` where it was the last child.\n */\n guides: boolean[];\n /** Whether this node is the last child of its parent, for `\u2514` vs `\u251C`. */\n last: boolean;\n /** Whether the row has rows nested under it, for the folder marker. */\n hasChildren: boolean;\n}\n\nexport interface WorklogDay {\n /** Local calendar date, `YYYY-MM-DD`. */\n date: string;\n hours: number;\n issueKeys: string[];\n}\n\n/** One day's worth of the current user's work on a single issue. */\nexport interface IssueWorklogEntry {\n date: string;\n hours: number;\n}\n\nexport interface WorklogSummary {\n /** One row per day in the window, oldest first. */\n days: WorklogDay[];\n /** Per-issue breakdown within the same window, keyed by issue key. */\n byIssue: Record<string, IssueWorklogEntry[]>;\n /** How many days the window covers, for labelling. */\n windowDays: number;\n}\n\nexport interface CurrentUser {\n accountId: string;\n displayName: string;\n}\n\n/** An error carrying user-facing remediation advice. */\nexport class JiraError extends Error {\n readonly hints: string[];\n\n constructor(message: string, hints: string[] = []) {\n super(message);\n this.name = 'JiraError';\n this.hints = hints;\n }\n}\n\n/** Async resource with independent loading/error state per panel. */\nexport interface Loadable<T> {\n data: T | null;\n loading: boolean;\n error: JiraError | null;\n}\n", "/**\n * Minimal Atlassian Document Format \u2192 plain text renderer.\n *\n * Jira returns rich descriptions as an ADF node tree. We only need something\n * readable in a terminal pane, so unknown node types simply recurse into their\n * children rather than failing.\n */\n\ninterface AdfNode {\n type?: string;\n text?: string;\n content?: AdfNode[];\n attrs?: Record<string, unknown>;\n marks?: { type?: string; attrs?: Record<string, unknown> }[];\n}\n\nexport function adfToText(doc: unknown): string {\n if (typeof doc === 'string') return doc;\n if (typeof doc !== 'object' || doc === null) return '';\n return renderNodes((doc as AdfNode).content ?? [], 0)\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nfunction renderNodes(nodes: AdfNode[], depth: number): string[] {\n return nodes.flatMap((node) => renderNode(node, depth));\n}\n\nfunction renderNode(node: AdfNode, depth: number): string[] {\n const indent = ' '.repeat(depth);\n\n switch (node.type) {\n case 'text':\n return [applyMarks(node)];\n\n case 'hardBreak':\n return [''];\n\n case 'paragraph':\n return [indent + inline(node.content ?? []), ''];\n\n case 'heading': {\n const level = Number(node.attrs?.['level'] ?? 1);\n return [`${'#'.repeat(Math.min(level, 6))} ${inline(node.content ?? [])}`, ''];\n }\n\n case 'bulletList':\n return [...listItems(node, depth, () => '\u2022'), ''];\n\n case 'orderedList':\n return [...listItems(node, depth, (i) => `${i + 1}.`), ''];\n\n case 'codeBlock': {\n const lang = typeof node.attrs?.['language'] === 'string' ? node.attrs['language'] : '';\n const body = inline(node.content ?? []).split('\\n');\n return [`${indent}\\`\\`\\`${lang}`, ...body.map((l) => indent + l), `${indent}\\`\\`\\``, ''];\n }\n\n case 'blockquote':\n return [...renderNodes(node.content ?? [], depth).map((l) => `${indent}> ${l}`), ''];\n\n case 'rule':\n return ['\u2500\u2500\u2500', ''];\n\n case 'mediaSingle':\n case 'mediaGroup':\n return [`${indent}[attachment]`];\n\n case 'table':\n // Rendering real table borders is out of scope; flatten rows instead.\n return [...renderNodes(node.content ?? [], depth), ''];\n\n case 'tableRow':\n return [indent + (node.content ?? []).map((cell) => inline(cell.content ?? [])).join(' | ')];\n\n default:\n return node.content ? renderNodes(node.content, depth) : [];\n }\n}\n\nfunction listItems(node: AdfNode, depth: number, bullet: (index: number) => string): string[] {\n const indent = ' '.repeat(depth);\n return (node.content ?? []).flatMap((item, index) => {\n const lines = renderNodes(item.content ?? [], depth + 1).filter((l) => l.trim() !== '');\n const [first = '', ...rest] = lines;\n return [`${indent}${bullet(index)} ${first.trim()}`, ...rest];\n });\n}\n\n/** Flatten a run of inline nodes onto a single line. */\nfunction inline(nodes: AdfNode[]): string {\n return nodes\n .map((node) => {\n switch (node.type) {\n case 'text':\n return applyMarks(node);\n case 'hardBreak':\n return '\\n';\n case 'mention':\n return attr(node, 'text') || '@unknown';\n case 'emoji':\n return attr(node, 'text') || attr(node, 'shortName');\n case 'inlineCard':\n return attr(node, 'url');\n case 'date':\n return attr(node, 'timestamp');\n case 'status':\n return `[${attr(node, 'text')}]`;\n default:\n return node.content ? inline(node.content) : '';\n }\n })\n .join('');\n}\n\n/** Read a node attribute as a plain string; non-scalar attrs are dropped. */\nfunction attr(node: AdfNode, name: string): string {\n const value = node.attrs?.[name];\n return typeof value === 'string' || typeof value === 'number' ? String(value) : '';\n}\n\n/** Only links carry information that would be lost in plain text. */\nfunction applyMarks(node: AdfNode): string {\n const text = node.text ?? '';\n const link = node.marks?.find((m) => m.type === 'link');\n const href = link?.attrs?.['href'];\n return typeof href === 'string' && href !== text ? `${text} (${href})` : text;\n}\n", "import type { JiraClient } from './client';\n\n/** Jira's schema id for the Scrum board sprint field, stable across sites. */\nconst SPRINT_SCHEMA = 'com.pyxis.greenhopper.jira:gh-sprint';\n\ninterface FieldMeta {\n id?: string;\n schema?: { custom?: string };\n}\n\n/** `undefined` = not looked up yet, `null` = looked up and unavailable. */\nlet cached: string | null | undefined;\n\n/**\n * Resolve the site's sprint custom-field id (e.g. `customfield_10020`).\n *\n * The id differs per Jira site, so it has to be discovered rather than\n * hard-coded. Resolved once per process; a failure is not fatal \u2014 the preview\n * just shows \"None\" rather than breaking the whole ticket list.\n */\nexport async function resolveSprintFieldId(client: JiraClient): Promise<string | null> {\n if (cached !== undefined) return cached;\n\n try {\n const fields = await client.get<FieldMeta[]>('/rest/api/3/field');\n cached = fields.find((field) => field.schema?.custom === SPRINT_SCHEMA)?.id ?? null;\n } catch {\n cached = null;\n }\n\n return cached;\n}\n\ninterface RawSprint {\n name?: string;\n state?: string;\n}\n\n/**\n * Sprint names for display, active ones first.\n *\n * The field is an array because an issue can span sprints. Older Jira versions\n * return stringified Java objects instead of JSON, so both shapes are handled.\n */\nexport function sprintNames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n\n const sprints = value\n .map((entry): RawSprint | null => {\n if (typeof entry === 'string') return parseLegacySprint(entry);\n if (typeof entry === 'object' && entry !== null) return entry as RawSprint;\n return null;\n })\n .filter((sprint): sprint is RawSprint => Boolean(sprint?.name));\n\n // Active first, then future, then closed \u2014 the order a person cares about.\n const rank = (state?: string): number => {\n switch (state?.toLowerCase()) {\n case 'active':\n return 0;\n case 'future':\n return 1;\n default:\n return 2;\n }\n };\n\n return sprints\n .sort((a, b) => rank(a.state) - rank(b.state))\n .map((sprint) =>\n sprint.state && sprint.state.toLowerCase() !== 'active'\n ? `${sprint.name ?? ''} (${sprint.state.toLowerCase()})`\n : (sprint.name ?? ''),\n );\n}\n\n/** `...Sprint@1a2b[id=5,name=Sprint 7,state=ACTIVE,...]` \u2192 `{ name, state }`. */\nfunction parseLegacySprint(raw: string): RawSprint | null {\n const name = /\\bname=([^,\\]]+)/.exec(raw)?.[1];\n const state = /\\bstate=([^,\\]]+)/.exec(raw)?.[1];\n return name ? { name, state } : null;\n}\n", "import type { JiraClient } from './client';\nimport { adfToText } from './adf';\nimport { resolveSprintFieldId, sprintNames } from './sprintField';\nimport type { Config, Issue } from '../types';\n\nconst MAX_ISSUES = 100;\n\nconst FIELDS = [\n 'summary',\n 'status',\n 'priority',\n 'assignee',\n 'reporter',\n 'labels',\n 'updated',\n 'description',\n 'issuetype',\n 'parent',\n 'project',\n 'aggregatetimespent',\n 'timespent',\n];\n\ninterface RawIssue {\n key: string;\n fields?: {\n summary?: string;\n status?: { name?: string; statusCategory?: { name?: string } };\n priority?: { name?: string } | null;\n assignee?: { displayName?: string } | null;\n reporter?: { displayName?: string } | null;\n labels?: string[];\n updated?: string;\n description?: unknown;\n issuetype?: { name?: string };\n project?: { name?: string; key?: string };\n parent?: {\n key?: string;\n fields?: { summary?: string; issuetype?: { name?: string } };\n } | null;\n aggregatetimespent?: number | null;\n timespent?: number | null;\n /** The sprint custom field, whose id varies per site. */\n [customField: string]: unknown;\n };\n}\n\nexport interface SearchResponse {\n issues?: RawIssue[];\n}\n\n/** How many parent hops to follow when filling in ancestors (epic \u2192 story \u2192 subtask). */\nconst MAX_ANCESTOR_ROUNDS = 4;\n\n/**\n * Fetch the open tickets shown in the top-right panel, plus every ancestor\n * needed to place them in a tree. Ancestors are marked `mine: false` so the\n * default view can hide them.\n */\nexport async function fetchOpenIssues(client: JiraClient, config: Config): Promise<Issue[]> {\n const sprintFieldId = await resolveSprintFieldId(client);\n\n const response = await client.post<SearchResponse>('/rest/api/3/search/jql', {\n jql: config.issuesJql,\n maxResults: MAX_ISSUES,\n fields: sprintFieldId ? [...FIELDS, sprintFieldId] : FIELDS,\n });\n\n const issues = (response.issues ?? []).map((raw) => toIssue(raw, client, sprintFieldId, true));\n const ancestors = await fetchAncestors(client, issues, sprintFieldId);\n\n return [...issues, ...ancestors];\n}\n\n/** Walk the parent links upward, fetching each level that is not already loaded. */\nasync function fetchAncestors(\n client: JiraClient,\n issues: Issue[],\n sprintFieldId: string | null,\n): Promise<Issue[]> {\n const known = new Set(issues.map((issue) => issue.key));\n const collected: Issue[] = [];\n let frontier = issues;\n\n for (let round = 0; round < MAX_ANCESTOR_ROUNDS; round += 1) {\n const missing = [\n ...new Set(\n frontier\n .map((issue) => issue.parent?.key)\n .filter((key): key is string => Boolean(key) && !known.has(key as string)),\n ),\n ];\n if (missing.length === 0) break;\n\n for (const key of missing) known.add(key);\n const fetched = await fetchByKeys(client, missing, sprintFieldId);\n if (fetched.length === 0) break;\n\n collected.push(...fetched);\n frontier = fetched;\n }\n\n return collected;\n}\n\nasync function fetchByKeys(\n client: JiraClient,\n keys: string[],\n sprintFieldId: string | null,\n): Promise<Issue[]> {\n const response = await client.post<SearchResponse>('/rest/api/3/search/jql', {\n jql: `key in (${keys.join(',')})`,\n maxResults: keys.length,\n fields: sprintFieldId ? [...FIELDS, sprintFieldId] : FIELDS,\n });\n\n return (response.issues ?? []).map((raw) => toIssue(raw, client, sprintFieldId, false));\n}\n\nfunction toIssue(\n raw: RawIssue,\n client: JiraClient,\n sprintFieldId: string | null,\n mine: boolean,\n): Issue {\n const fields = raw.fields ?? {};\n return {\n key: raw.key,\n summary: fields.summary ?? '(no summary)',\n status: fields.status?.name ?? 'Unknown',\n statusCategory: fields.status?.statusCategory?.name ?? '',\n issueType: fields.issuetype?.name ?? '',\n priority: fields.priority?.name ?? null,\n assignee: fields.assignee?.displayName ?? null,\n reporter: fields.reporter?.displayName ?? null,\n labels: fields.labels ?? [],\n updated: fields.updated ?? null,\n description: adfToText(fields.description),\n url: client.issueUrl(raw.key),\n projectName: fields.project?.name ?? fields.project?.key ?? null,\n parent: fields.parent?.key\n ? {\n key: fields.parent.key,\n summary: fields.parent.fields?.summary ?? '',\n issueType: fields.parent.fields?.issuetype?.name ?? '',\n }\n : null,\n totalTimeSpentSeconds: fields.aggregatetimespent ?? fields.timespent ?? null,\n sprints: sprintFieldId ? sprintNames(fields[sprintFieldId]) : [],\n mine,\n };\n}\n", "import type { JiraClient } from './client';\nimport type { Config, CurrentUser, IssueWorklogEntry, WorklogSummary } from '../types';\nimport type { SearchResponse } from './issues';\n\nconst WORKLOG_PAGE_SIZE = 1000;\nconst CONCURRENCY = 5;\n\ninterface RawWorklog {\n author?: { accountId?: string };\n started?: string;\n timeSpentSeconds?: number;\n}\n\ninterface WorklogResponse {\n worklogs?: RawWorklog[];\n}\n\n/** Local `YYYY-MM-DD` key \u2014 worklog days are calendar days in the user's timezone. */\nexport function localDateKey(date: Date): string {\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\n/** Midnight local time, `daysAgo` days back from today. */\nfunction startOfDay(daysAgo: number): Date {\n const date = new Date();\n date.setHours(0, 0, 0, 0);\n date.setDate(date.getDate() - daysAgo);\n return date;\n}\n\n/**\n * Aggregate the current user's worklogs over the last `config.worklogDays` days\n * two ways: one row per day (oldest first, zero-hour days included so the panel\n * shows a complete window) and a per-issue breakdown for the preview panel.\n */\nexport async function fetchWorklogDays(\n client: JiraClient,\n config: Config,\n me: CurrentUser,\n): Promise<WorklogSummary> {\n const days = config.worklogDays;\n const windowStart = startOfDay(days - 1);\n\n const search = await client.post<SearchResponse>('/rest/api/3/search/jql', {\n jql: `worklogAuthor = currentUser() AND worklogDate >= -${days}d ORDER BY updated DESC`,\n maxResults: 200,\n fields: ['summary'],\n });\n\n const keys = (search.issues ?? []).map((issue) => issue.key);\n\n // Pre-seed every day in the window so gaps render as 0.0h.\n const buckets = new Map<string, { seconds: number; keys: Set<string> }>();\n for (let i = 0; i < days; i += 1) {\n buckets.set(localDateKey(startOfDay(i)), { seconds: 0, keys: new Set() });\n }\n\n // Seconds logged per issue per day: issue key \u2192 date \u2192 seconds.\n const perIssue = new Map<string, Map<string, number>>();\n\n const startedAfter = String(windowStart.getTime());\n await forEachLimited(keys, CONCURRENCY, async (key) => {\n const response = await client.get<WorklogResponse>(`/rest/api/3/issue/${key}/worklog`, {\n startedAfter,\n maxResults: String(WORKLOG_PAGE_SIZE),\n });\n\n for (const entry of response.worklogs ?? []) {\n if (entry.author?.accountId !== me.accountId || !entry.started) continue;\n\n const started = new Date(entry.started);\n if (Number.isNaN(started.getTime()) || started < windowStart) continue;\n\n const date = localDateKey(started);\n const bucket = buckets.get(date);\n if (!bucket) continue; // A worklog dated in the future.\n\n const seconds = entry.timeSpentSeconds ?? 0;\n bucket.seconds += seconds;\n bucket.keys.add(key);\n\n const issueDays = perIssue.get(key) ?? new Map<string, number>();\n issueDays.set(date, (issueDays.get(date) ?? 0) + seconds);\n perIssue.set(key, issueDays);\n }\n });\n\n const byIssue: Record<string, IssueWorklogEntry[]> = {};\n for (const [key, issueDays] of perIssue) {\n byIssue[key] = Array.from(issueDays.entries())\n .map(([date, seconds]) => ({ date, hours: seconds / 3600 }))\n .sort((a, b) => a.date.localeCompare(b.date));\n }\n\n return {\n days: Array.from(buckets.entries())\n .map(([date, { seconds, keys: issueKeys }]) => ({\n date,\n hours: seconds / 3600,\n issueKeys: Array.from(issueKeys).sort(compareIssueKeys),\n }))\n .sort((a, b) => a.date.localeCompare(b.date)),\n byIssue,\n windowDays: days,\n };\n}\n\n/** Sort by project then numeric part, so MR-2 precedes MR-10. */\nfunction compareIssueKeys(a: string, b: string): number {\n const [aProject = a, aNumber = '0'] = a.split('-');\n const [bProject = b, bNumber = '0'] = b.split('-');\n return aProject === bProject\n ? Number(aNumber) - Number(bNumber)\n : aProject.localeCompare(bProject);\n}\n\n/** Run `worker` over `items` with at most `limit` requests in flight. */\nasync function forEachLimited<T>(\n items: T[],\n limit: number,\n worker: (item: T) => Promise<void>,\n): Promise<void> {\n let cursor = 0;\n const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {\n while (cursor < items.length) {\n const item = items[cursor];\n cursor += 1;\n if (item !== undefined) await worker(item);\n }\n });\n await Promise.all(runners);\n}\n", "import { useEffect, useRef } from 'react';\nimport { useStdin } from 'ink';\n\n/**\n * Built from a char code on purpose: a literal escape byte in source is\n * invisible and easy to lose to an editor or a copy-paste.\n */\nconst ESC = String.fromCharCode(27);\n\n/**\n * Home and End have no single encoding \u2014 xterm, VT and rxvt families each pick\n * a different one, and terminals vary in application vs normal cursor mode.\n */\nconst HOME_SEQUENCES = [`${ESC}[H`, `${ESC}[1~`, `${ESC}[7~`, `${ESC}OH`];\nconst END_SEQUENCES = [`${ESC}[F`, `${ESC}[4~`, `${ESC}[8~`, `${ESC}OF`];\n\n/**\n * Report Home and End key presses.\n *\n * Ink's `useInput` cannot help here: it parses all of these into an empty input\n * with no key flags set, making Home and End indistinguishable. Reading stdin\n * directly is the only way to tell them apart. Ink still sees the same bytes and\n * emits its own (harmless) empty events.\n */\nexport function useHomeEndKeys(onHome: () => void, onEnd: () => void): void {\n const { stdin, isRawModeSupported } = useStdin();\n\n // Handlers change on nearly every render; a ref keeps the listener stable.\n const handlers = useRef({ onHome, onEnd });\n handlers.current = { onHome, onEnd };\n\n useEffect(() => {\n if (!isRawModeSupported) return;\n\n const onData = (data: Buffer | string): void => {\n const text = typeof data === 'string' ? data : data.toString('utf8');\n if (HOME_SEQUENCES.some((sequence) => text.includes(sequence))) {\n handlers.current.onHome();\n } else if (END_SEQUENCES.some((sequence) => text.includes(sequence))) {\n handlers.current.onEnd();\n }\n };\n\n stdin.on('data', onData);\n return () => {\n stdin.off('data', onData);\n };\n }, [stdin, isRawModeSupported]);\n}\n", "import { useEffect } from 'react';\nimport { useStdin, useStdout } from 'ink';\n\n/**\n * Built from a char code on purpose: a literal escape byte in source is\n * invisible and easy to lose to an editor or a copy-paste.\n */\nconst ESC = String.fromCharCode(27);\n\n/** SGR mouse reporting: button presses + wheel, with unbounded coordinates. */\nconst MOUSE_ON = `${ESC}[?1000h${ESC}[?1006h`;\nconst MOUSE_OFF = `${ESC}[?1006l${ESC}[?1000l`;\n\n/** `ESC [ < button ; col ; row (M|m)` \u2014 M is press, m is release. */\nconst SGR_MOUSE = new RegExp(`${ESC}\\\\[<(\\\\d+);(\\\\d+);(\\\\d+)([Mm])`, 'g');\nconst SGR_PREFIX = `${ESC}[<`;\n\nexport type MouseEvent =\n | { type: 'press'; column: number; row: number }\n | { type: 'wheel'; direction: 'up' | 'down'; column: number; row: number };\n\n/**\n * True when `input` is (or contains) an SGR mouse report. Keyboard handlers\n * should ignore these, since Ink's key parser sees the same bytes.\n */\nexport function isMouseSequence(input: string): boolean {\n return input.includes(SGR_PREFIX);\n}\n\n/**\n * Enable SGR mouse reporting for the lifetime of the component and report\n * parsed events. Terminal state is restored on unmount and on abrupt exits.\n */\nexport function useMouse(onEvent: (event: MouseEvent) => void): void {\n const { stdin, isRawModeSupported } = useStdin();\n const { stdout } = useStdout();\n\n useEffect(() => {\n if (!isRawModeSupported) return;\n\n stdout.write(MOUSE_ON);\n const disable = (): void => {\n stdout.write(MOUSE_OFF);\n };\n // Ink's own cleanup does not know about mouse mode, so cover hard exits.\n process.on('exit', disable);\n\n const onData = (data: Buffer | string): void => {\n const text = typeof data === 'string' ? data : data.toString('utf8');\n SGR_MOUSE.lastIndex = 0;\n\n for (let match = SGR_MOUSE.exec(text); match; match = SGR_MOUSE.exec(text)) {\n const [, rawButton = '0', rawColumn = '1', rawRow = '1', kind] = match;\n if (kind !== 'M') continue; // Ignore releases.\n\n const button = Number(rawButton);\n const column = Number(rawColumn) - 1;\n const row = Number(rawRow) - 1;\n\n // Bit 6 (64) marks wheel events; the low bit gives the direction.\n if (button & 64) {\n onEvent({ type: 'wheel', direction: (button & 1) === 0 ? 'up' : 'down', column, row });\n } else if ((button & 3) === 0) {\n onEvent({ type: 'press', column, row });\n }\n }\n };\n\n stdin.on('data', onData);\n return () => {\n stdin.off('data', onData);\n process.off('exit', disable);\n disable();\n };\n }, [stdin, stdout, isRawModeSupported, onEvent]);\n}\n", "import { useEffect, useState } from 'react';\nimport { useStdout } from 'ink';\n\nexport interface TerminalSize {\n columns: number;\n rows: number;\n}\n\n/** Track the terminal dimensions so the layout fills the window on resize. */\nexport function useTerminalSize(): TerminalSize {\n const { stdout } = useStdout();\n const [size, setSize] = useState<TerminalSize>(() => ({\n columns: stdout.columns || 80,\n rows: stdout.rows || 24,\n }));\n\n useEffect(() => {\n const onResize = (): void => {\n setSize({ columns: stdout.columns || 80, rows: stdout.rows || 24 });\n };\n\n stdout.on('resize', onResize);\n onResize();\n return () => {\n stdout.off('resize', onResize);\n };\n }, [stdout]);\n\n return size;\n}\n", "import { spawn } from 'node:child_process';\n\n/** Platform command that hands a URL to the user's default browser. */\nfunction opener(): { command: string; args: string[] } {\n switch (process.platform) {\n case 'darwin':\n return { command: 'open', args: [] };\n case 'win32':\n // `start` is a cmd builtin; the empty string is the (ignored) window title.\n return { command: 'cmd', args: ['/c', 'start', ''] };\n default:\n return { command: 'xdg-open', args: [] };\n }\n}\n\n/**\n * Open `url` in the default browser.\n *\n * The child is detached and its streams are discarded, so a browser writing to\n * stderr cannot corrupt the TUI. Resolves once the process has been spawned;\n * rejects if the opener is missing or exits non-zero straight away.\n */\nexport async function openUrl(url: string): Promise<void> {\n const { command, args } = opener();\n\n return new Promise((resolve, reject) => {\n // Arguments are passed as an array, so the URL is never shell-interpreted.\n const child = spawn(command, [...args, url], {\n detached: true,\n stdio: 'ignore',\n });\n\n child.once('error', (cause: NodeJS.ErrnoException) => {\n reject(\n new Error(\n cause.code === 'ENOENT'\n ? `${command} is not installed, so the browser could not be opened.`\n : `${command} failed: ${cause.message}`,\n ),\n );\n });\n\n child.once('spawn', () => {\n child.unref(); // Let jdb exit without waiting for the browser.\n resolve();\n });\n });\n}\n", "import { JiraError, type Config, type CurrentUser } from '../types';\n\nconst REQUEST_TIMEOUT_MS = 20_000;\n\n/** Thin authenticated wrapper around the Jira Cloud REST API v3. */\nexport class JiraClient {\n private readonly baseUrl: string;\n private readonly authHeader: string;\n\n constructor(config: Config) {\n this.baseUrl = config.baseUrl;\n this.authHeader = `Basic ${Buffer.from(`${config.email}:${config.apiToken}`).toString('base64')}`;\n }\n\n /** Browser URL for an issue, for display in the preview panel. */\n issueUrl(key: string): string {\n return `${this.baseUrl}/browse/${key}`;\n }\n\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n for (const [key, value] of Object.entries(params ?? {})) {\n url.searchParams.set(key, value);\n }\n return this.request<T>('GET', url.toString());\n }\n\n async post<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>('POST', `${this.baseUrl}${path}`, body);\n }\n\n private async request<T>(method: string, url: string, body?: unknown): Promise<T> {\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers: {\n Authorization: this.authHeader,\n Accept: 'application/json',\n ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),\n },\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (cause) {\n throw asNetworkError(cause, this.baseUrl);\n }\n\n if (!response.ok) {\n throw await asHttpError(response, url);\n }\n\n return (await response.json()) as T;\n }\n\n /** Identify the caller; doubles as the credential check at startup. */\n async myself(): Promise<CurrentUser> {\n const me = await this.get<{ accountId: string; displayName: string }>('/rest/api/3/myself');\n return { accountId: me.accountId, displayName: me.displayName };\n }\n}\n\nfunction asNetworkError(cause: unknown, baseUrl: string): JiraError {\n const name = cause instanceof Error ? cause.name : '';\n if (name === 'TimeoutError' || name === 'AbortError') {\n return new JiraError('Jira did not respond within 20s.', [\n 'Check your network connection or VPN, then press r to retry.',\n ]);\n }\n return new JiraError(`Could not reach ${baseUrl}.`, [\n cause instanceof Error ? cause.message : String(cause),\n 'Verify JIRA_BASE_URL points at your Atlassian site.',\n ]);\n}\n\nasync function asHttpError(response: Response, url: string): Promise<JiraError> {\n const detail = await readErrorDetail(response);\n\n switch (response.status) {\n case 401:\n return new JiraError('Jira rejected your credentials (401).', [\n 'JIRA_EMAIL must be your Atlassian account email and JIRA_API_TOKEN an API token',\n '(not your password). Tokens: https://id.atlassian.com/manage-profile/security/api-tokens',\n ]);\n case 403:\n return new JiraError('Jira denied access (403).', [\n detail || 'Your account may lack permission for this project.',\n ]);\n case 400:\n return new JiraError('Jira rejected the request (400).', [\n detail || 'The JQL query may be invalid for this site.',\n 'Adjust \"issuesJql\" in your config file.',\n ]);\n case 429:\n return new JiraError('Jira rate-limited this client (429).', [\n 'Wait a moment, then press r to retry.',\n ]);\n default:\n return new JiraError(`Jira returned ${response.status} for ${new URL(url).pathname}.`, [\n detail || response.statusText,\n ]);\n }\n}\n\n/** Jira reports failures as `errorMessages` and/or a field-keyed `errors` map. */\nasync function readErrorDetail(response: Response): Promise<string> {\n try {\n const body: unknown = await response.json();\n if (typeof body !== 'object' || body === null) return '';\n const { errorMessages, errors } = body as {\n errorMessages?: unknown;\n errors?: Record<string, unknown>;\n };\n\n const parts: string[] = [];\n if (Array.isArray(errorMessages)) parts.push(...errorMessages.map(String));\n if (errors && typeof errors === 'object') {\n parts.push(...Object.entries(errors).map(([k, v]) => `${k}: ${String(v)}`));\n }\n return parts.join('; ');\n } catch {\n return '';\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["Box", "Text", "useEffect", "useRef", "useState", "Box", "Text", "Box", "Text", "jsx", "jsxs", "jsx", "jsxs", "Text", "Box", "Box", "Text", "jsx", "jsxs", "Box", "Text", "Box", "Text", "MONTHS", "jsx", "jsxs", "Text", "Box", "MONTHS", "Box", "Text", "readFileSync", "join", "join", "cached", "readFileSync", "version", "jsx", "jsxs", "Box", "Text", "useEffect", "useStdin", "ESC", "useEffect", "useStdout", "jsx", "jsxs", "useState", "useEffect", "useRef", "Box", "Text", "REQUEST_TIMEOUT_MS", "jsx", "require", "ESC"]
3
+ "sources": ["../src/cli.tsx", "../src/app.tsx", "../src/components/IssueList.tsx", "../src/components/PanelFrame.tsx", "../src/components/PanelStatus.tsx", "../src/issueType.ts", "../src/priority.ts", "../src/tree.ts", "../src/components/IssuePreview.tsx", "../src/components/WorkChartPanel.tsx", "../src/dates.ts", "../src/components/WorklogPanel.tsx", "../src/data/calendar.ts", "../src/data/config.ts", "../src/types.ts", "../src/data/adf.ts", "../src/data/sprintField.ts", "../src/data/issues.ts", "../src/data/worklogs.ts", "../src/hooks/useHomeEndKeys.ts", "../src/hooks/useMouse.ts", "../src/hooks/useTerminalSize.ts", "../src/layout.ts", "../src/openUrl.ts", "../src/data/client.ts"],
4
+ "sourcesContent": ["#!/usr/bin/env node\nimport { render } from 'ink';\nimport { createRequire } from 'node:module';\n\nimport { App } from './app';\nimport { DAYS_OFF_PATH } from './data/calendar';\nimport { JiraClient } from './data/client';\nimport { CONFIG_PATH, DEFAULT_ISSUES_JQL, loadConfig } from './data/config';\nimport { JiraError } from './types';\n\nconst require = createRequire(import.meta.url);\nconst { version } = require('../package.json') as { version: string };\n\n/**\n * Built from a char code on purpose: a literal escape byte in source is\n * invisible and easy to lose to an editor or a copy-paste.\n */\nconst ESC = String.fromCharCode(27);\nconst ALT_SCREEN_ON = `${ESC}[?1049h`;\nconst ALT_SCREEN_OFF = `${ESC}[?1049l`;\nconst CURSOR_SHOW = `${ESC}[?25h`;\n\nconst HELP = `\n jdb \u2014 Jira dashboard in your terminal\n\n Usage\n $ jdb\n\n Options\n -h, --help Show this help\n -v, --version Show the version\n\n Configuration (environment variables take precedence)\n JIRA_BASE_URL https://your-team.atlassian.net\n JIRA_EMAIL your Atlassian account email\n JIRA_API_TOKEN https://id.atlassian.com/manage-profile/security/api-tokens\n JIRA_HOLIDAY_COUNTRY ISO country code for statutory holidays, e.g. UA\n JIRA_HOLIDAY_REGION your subdivision, e.g. CA-ON \u2014 regional holidays then\n count as days off, and other regions' are hidden\n\n Or ${CONFIG_PATH}:\n {\n \"baseUrl\": \"https://your-team.atlassian.net\",\n \"email\": \"you@example.com\",\n \"apiToken\": \"...\",\n \"issuesJql\": ${JSON.stringify(DEFAULT_ISSUES_JQL)},\n \"worklogDays\": 14,\n \"holidayCountry\": \"UA\"\n }\n\n Personal leave goes in ${DAYS_OFF_PATH}:\n [\"2026-08-10\", { \"date\": \"2026-08-11\", \"label\": \"moving day\" }]\n\n Holidays are fetched from date.nager.at and cached next to the config;\n changing the country invalidates the cache.\n\n Keys\n \u2191 \u2193 / j k move within the focused panel\n home / end jump to the first / last item\n enter open the selected ticket in your browser\n tab cycle panels (worklogs \u2192 tickets \u2192 preview)\n click/wheel select and scroll\n r reload\n q / esc quit\n`;\n\n/** Print an error the way clig.dev recommends: what broke, then how to fix it. */\nfunction reportError(error: unknown): void {\n const jiraError = error instanceof JiraError ? error : null;\n process.stderr.write(`\\njdb: ${jiraError?.message ?? String(error)}\\n`);\n for (const hint of jiraError?.hints ?? []) {\n process.stderr.write(` ${hint}\\n`);\n }\n process.stderr.write('\\n');\n}\n\nasync function main(): Promise<number> {\n const args = process.argv.slice(2);\n\n if (args.includes('-h') || args.includes('--help')) {\n process.stdout.write(`${HELP}\\n`);\n return 0;\n }\n if (args.includes('-v') || args.includes('--version')) {\n process.stdout.write(`${version}\\n`);\n return 0;\n }\n\n if (!process.stdout.isTTY) {\n process.stderr.write('jdb: needs an interactive terminal (stdout is not a TTY).\\n');\n return 1;\n }\n\n const config = loadConfig();\n const client = new JiraClient(config);\n const me = await client.myself();\n\n // The alternate screen keeps the user's scrollback intact: everything jdb\n // draws goes to a separate buffer that the terminal discards on the way out.\n let restored = false;\n const restoreScreen = (): void => {\n if (restored) return;\n restored = true;\n process.stdout.write(ALT_SCREEN_OFF + CURSOR_SHOW);\n };\n\n process.stdout.write(ALT_SCREEN_ON);\n // Cover every way the process can end, including a crash or a signal.\n process.on('exit', restoreScreen);\n for (const signal of ['SIGINT', 'SIGTERM', 'SIGHUP'] as const) {\n process.once(signal, () => {\n restoreScreen();\n process.exit(signal === 'SIGINT' ? 130 : 143);\n });\n }\n\n try {\n const { waitUntilExit } = render(<App config={config} client={client} me={me} />, {\n exitOnCtrlC: true,\n });\n await waitUntilExit();\n } finally {\n restoreScreen();\n }\n\n return 0;\n}\n\nmain()\n .then((code) => {\n process.exitCode = code;\n })\n .catch((error: unknown) => {\n reportError(error);\n process.exitCode = 1;\n });\n", "import { Box, Text, useApp, useInput } from 'ink';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { ReactElement } from 'react';\n\nimport { IssueList } from './components/IssueList';\nimport { IssuePreview } from './components/IssuePreview';\nimport { PANEL_HEADER_ROWS } from './components/PanelFrame';\nimport { spinnerChar } from './components/PanelStatus';\nimport { chartWidthFor, WorkChartPanel } from './components/WorkChartPanel';\nimport { WorklogPanel } from './components/WorklogPanel';\nimport { loadDayMarks, type DayMarks } from './data/calendar';\nimport type { JiraClient } from './data/client';\nimport { fetchOpenIssues } from './data/issues';\nimport { fetchWorklogDays } from './data/worklogs';\nimport { useHomeEndKeys } from './hooks/useHomeEndKeys';\nimport { isMouseSequence, useMouse, type MouseEvent } from './hooks/useMouse';\nimport { useTerminalSize } from './hooks/useTerminalSize';\nimport { clampSplit, nudgeSplit, SPLIT_STEP } from './layout';\nimport { openUrl } from './openUrl';\nimport { buildIssueTree } from './tree';\nimport { JiraError } from './types';\nimport type { Config, CurrentUser, Issue, Loadable, WorklogDay, WorklogSummary } from './types';\n\ninterface AppProps {\n config: Config;\n client: JiraClient;\n me: CurrentUser;\n}\n\ntype Panel = 'worklog' | 'issues' | 'preview';\nconst PANEL_ORDER: Panel[] = ['worklog', 'issues', 'preview'];\n\nconst MIN_TOP_HEIGHT = 6;\nconst MIN_LEFT_WIDTH = 34;\nconst SPINNER_INTERVAL_MS = 90;\nconst NOTICE_TIMEOUT_MS = 3000;\n\nconst pending = <T,>(previous?: Loadable<T>): Loadable<T> => ({\n data: previous?.data ?? null,\n loading: true,\n error: null,\n});\n\nfunction toJiraError(cause: unknown): JiraError {\n if (cause instanceof JiraError) return cause;\n return new JiraError(cause instanceof Error ? cause.message : String(cause));\n}\n\nexport function App({ config, client, me }: AppProps): ReactElement {\n const { exit } = useApp();\n const { columns, rows } = useTerminalSize();\n\n const [worklogs, setWorklogs] = useState<Loadable<WorklogSummary>>(() => pending());\n const [issues, setIssues] = useState<Loadable<Issue[]>>(() => pending());\n const [focus, setFocus] = useState<Panel>('issues');\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [issueOffset, setIssueOffset] = useState(0);\n const [worklogOffset, setWorklogOffset] = useState(0);\n const [previewOffset, setPreviewOffset] = useState(0);\n const [spinnerFrame, setSpinnerFrame] = useState(0);\n const [reloadToken, setReloadToken] = useState(0);\n const [notice, setNotice] = useState<{ text: string; error: boolean } | null>(null);\n const [dayMarks, setDayMarks] = useState<DayMarks>(() => new Map());\n // \"t\" toggles between my own tickets and the full tree around them.\n const [fullTree, setFullTree] = useState(false);\n // Alt+\u2190/\u2192 offsets of the top and bottom splitters from their default positions.\n const [topShift, setTopShift] = useState(0);\n const [bottomShift, setBottomShift] = useState(0);\n\n // ---- layout -------------------------------------------------------------\n const topHeight = Math.max(MIN_TOP_HEIGHT, Math.floor((rows - 1) / 2));\n const bottomHeight = Math.max(MIN_TOP_HEIGHT, rows - 1 - topHeight);\n const defaultLeftWidth = Math.max(MIN_LEFT_WIDTH, Math.min(52, Math.floor(columns * 0.42)));\n const leftWidth = clampSplit(defaultLeftWidth + topShift, columns);\n const issueRows = Math.max(1, topHeight - 3);\n const worklogRows = issueRows;\n\n // The chart takes the width its bars need, but never more than half the screen;\n // the preview gets the rest.\n const dayCount = worklogs.data?.days.length ?? config.worklogDays;\n const defaultChartWidth = Math.min(\n Math.floor(columns / 2),\n Math.max(chartWidthFor(dayCount, 1), Math.min(chartWidthFor(dayCount, 2), columns - 40)),\n );\n const chartWidth = clampSplit(defaultChartWidth + bottomShift, columns);\n\n // ---- data ---------------------------------------------------------------\n useEffect(() => {\n let cancelled = false;\n\n setIssues(pending);\n fetchOpenIssues(client, config)\n .then((data) => {\n if (!cancelled) setIssues({ data, loading: false, error: null });\n })\n .catch((cause: unknown) => {\n if (!cancelled) setIssues({ data: null, loading: false, error: toJiraError(cause) });\n });\n\n setWorklogs(pending);\n fetchWorklogDays(client, config, me)\n .then((data) => {\n if (!cancelled) setWorklogs({ data, loading: false, error: null });\n })\n .catch((cause: unknown) => {\n if (!cancelled) setWorklogs({ data: null, loading: false, error: toJiraError(cause) });\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, config, me, reloadToken]);\n\n // The worklog window is known up front, so the calendar need not wait on Jira.\n // `loadDayMarks` swallows its own failures, hence no error state here.\n useEffect(() => {\n let cancelled = false;\n\n const today = new Date();\n const oldest = new Date(today);\n oldest.setDate(oldest.getDate() - config.worklogDays);\n const years = [...new Set([oldest.getFullYear(), today.getFullYear()])];\n\n void loadDayMarks(config, years).then((marks) => {\n if (!cancelled) setDayMarks(marks);\n });\n\n return () => {\n cancelled = true;\n };\n }, [config, reloadToken]);\n\n const loading = worklogs.loading || issues.loading;\n\n useEffect(() => {\n if (!loading) return;\n const timer = setInterval(() => {\n setSpinnerFrame((frame) => frame + 1);\n }, SPINNER_INTERVAL_MS);\n return () => {\n clearInterval(timer);\n };\n }, [loading]);\n\n const keepSelectedKey = useRef<string | null>(null);\n const issueRowsData = useMemo(\n () => buildIssueTree(issues.data ?? [], fullTree),\n [issues.data, fullTree],\n );\n const issueList = useMemo(() => issueRowsData.map((row) => row.issue), [issueRowsData]);\n const selected = issueList[selectedIndex] ?? null;\n\n // Keep the selection valid when a reload or a mode switch shortens the list,\n // and follow the remembered ticket when the tree mode changed under it.\n useEffect(() => {\n const wanted = keepSelectedKey.current;\n keepSelectedKey.current = null;\n const target = wanted === null ? -1 : issueList.findIndex((issue) => issue.key === wanted);\n setSelectedIndex((index) =>\n target >= 0 ? target : Math.min(index, Math.max(0, issueList.length - 1)),\n );\n }, [issueList]);\n\n // Days run oldest-first, so today sits at the bottom \u2014 start scrolled there.\n useEffect(() => {\n setWorklogOffset(Math.max(0, (worklogs.data?.days.length ?? 0) - worklogRows));\n }, [worklogs.data, worklogRows]);\n\n // Reading a new ticket always starts at the top of its description.\n useEffect(() => {\n setPreviewOffset(0);\n }, [selected?.key]);\n\n // ---- selection ----------------------------------------------------------\n const selectIndex = useCallback(\n (next: number) => {\n if (issueList.length === 0) return;\n const clamped = Math.max(0, Math.min(next, issueList.length - 1));\n setSelectedIndex(clamped);\n setIssueOffset((offset) => {\n if (clamped < offset) return clamped;\n if (clamped >= offset + issueRows) return clamped - issueRows + 1;\n return offset;\n });\n },\n [issueList.length, issueRows],\n );\n\n // Selection can also move without `selectIndex` (reload, tree toggle), so the\n // viewport is re-anchored here rather than at every call site.\n useEffect(() => {\n setIssueOffset((offset) => {\n if (selectedIndex < offset) return selectedIndex;\n if (selectedIndex >= offset + issueRows) return selectedIndex - issueRows + 1;\n return Math.min(offset, Math.max(0, issueList.length - issueRows));\n });\n }, [selectedIndex, issueRows, issueList.length]);\n\n const scrollPanel = useCallback(\n (panel: Panel, delta: number) => {\n switch (panel) {\n case 'issues':\n selectIndex(selectedIndex + delta);\n break;\n case 'worklog':\n setWorklogOffset((offset) =>\n Math.max(0, Math.min(offset + delta, (worklogs.data?.days.length ?? 0) - worklogRows)),\n );\n break;\n case 'preview':\n setPreviewOffset((offset) => Math.max(0, offset + delta));\n break;\n }\n },\n [selectIndex, selectedIndex, worklogs.data?.days.length, worklogRows],\n );\n\n /**\n * The selected issue's hours across the whole window, zero-filled.\n *\n * `byIssue` only holds days that have work, so the days axis comes from the\n * summary to keep every ticket's chart on the same 14-day scale.\n */\n const chartDays = useMemo<WorklogDay[]>(() => {\n const summary = worklogs.data;\n if (!summary || !selected) return [];\n\n const hoursByDate = new Map(\n (summary.byIssue[selected.key] ?? []).map((entry) => [entry.date, entry.hours]),\n );\n return summary.days.map((day) => ({\n date: day.date,\n hours: hoursByDate.get(day.date) ?? 0,\n issueKeys: [],\n }));\n }, [worklogs.data, selected]);\n\n // Notices are transient; clearing on a timer keeps the status bar honest.\n useEffect(() => {\n if (!notice) return;\n const timer = setTimeout(() => {\n setNotice(null);\n }, NOTICE_TIMEOUT_MS);\n return () => {\n clearTimeout(timer);\n };\n }, [notice]);\n\n const openSelected = useCallback(() => {\n if (!selected) return;\n openUrl(selected.url)\n .then(() => {\n setNotice({ text: `opened ${selected.key} in your browser`, error: false });\n })\n .catch((cause: unknown) => {\n setNotice({\n text: cause instanceof Error ? cause.message : String(cause),\n error: true,\n });\n });\n }, [selected]);\n\n // Home/End jump to the ends of whichever panel has focus.\n const goToStart = useCallback(() => {\n switch (focus) {\n case 'issues':\n selectIndex(0);\n break;\n case 'worklog':\n setWorklogOffset(0);\n break;\n case 'preview':\n setPreviewOffset(0);\n break;\n }\n }, [focus, selectIndex]);\n\n const goToEnd = useCallback(() => {\n switch (focus) {\n case 'issues':\n selectIndex(issueList.length - 1);\n break;\n case 'worklog':\n setWorklogOffset(Math.max(0, (worklogs.data?.days.length ?? 0) - worklogRows));\n break;\n case 'preview':\n // The description's total line count lives inside the preview, so there\n // is no \"last line\" to jump to from here.\n break;\n }\n }, [focus, issueList.length, selectIndex, worklogs.data?.days.length, worklogRows]);\n\n useHomeEndKeys(goToStart, goToEnd);\n\n // ---- mouse --------------------------------------------------------------\n const layout = useRef({ leftWidth, topHeight, issueRows, issueOffset });\n layout.current = { leftWidth, topHeight, issueRows, issueOffset };\n\n const onMouse = useCallback(\n (event: MouseEvent) => {\n const { leftWidth: left, topHeight: top, issueRows: visible, issueOffset: offset } =\n layout.current;\n\n const panel: Panel =\n event.row >= top ? 'preview' : event.column >= left ? 'issues' : 'worklog';\n\n if (event.type === 'wheel') {\n setFocus(panel);\n scrollPanel(panel, event.direction === 'up' ? -1 : 1);\n return;\n }\n\n setFocus(panel);\n if (panel !== 'issues') return;\n\n const row = event.row - PANEL_HEADER_ROWS;\n if (row >= 0 && row < visible) selectIndex(offset + row);\n },\n [scrollPanel, selectIndex],\n );\n\n useMouse(onMouse);\n\n // ---- keyboard -----------------------------------------------------------\n useInput((input, key) => {\n if (isMouseSequence(input)) return; // Ink also sees the raw mouse bytes.\n\n if (key.ctrl && input === 'c') {\n exit();\n return;\n }\n if (input === 'q' || key.escape) {\n exit();\n return;\n }\n if (input === 't') {\n // Keep the reading position on the same ticket across the mode switch.\n keepSelectedKey.current = selected?.key ?? null;\n setFullTree((current) => !current);\n return;\n }\n if (input === 'r') {\n setReloadToken((token) => token + 1);\n return;\n }\n if (key.return && focus === 'issues') {\n openSelected();\n return;\n }\n if (key.tab) {\n const step = key.shift ? -1 : 1;\n setFocus((current) => {\n const next = (PANEL_ORDER.indexOf(current) + step + PANEL_ORDER.length) % PANEL_ORDER.length;\n return PANEL_ORDER[next] ?? 'issues';\n });\n return;\n }\n\n // Alt+\u2190/\u2192 moves the splitter of the row holding the focused panel.\n if (key.meta && (key.leftArrow || key.rightArrow)) {\n const delta = key.leftArrow ? -SPLIT_STEP : SPLIT_STEP;\n if (focus === 'preview') {\n setBottomShift((shift) => nudgeSplit(shift, delta, defaultChartWidth, columns));\n } else {\n setTopShift((shift) => nudgeSplit(shift, delta, defaultLeftWidth, columns));\n }\n return;\n }\n\n // A held key arrives as one chunk (\"jjj\"), so count the repeats.\n if (key.downArrow) scrollPanel(focus, 1);\n else if (key.upArrow) scrollPanel(focus, -1);\n else if (key.pageDown) scrollPanel(focus, issueRows);\n else if (key.pageUp) scrollPanel(focus, -issueRows);\n else if (/^j+$/.test(input)) scrollPanel(focus, input.length);\n else if (/^k+$/.test(input)) scrollPanel(focus, -input.length);\n else if (input === 'g') scrollPanel(focus, -Number.MAX_SAFE_INTEGER);\n else if (input === 'G') selectIndex(issueList.length - 1);\n });\n\n const hint = useMemo(\n () =>\n focus === 'issues'\n ? '\u2191\u2193/jk move \u00B7 enter open \u00B7 t tree \u00B7 alt\u2190\u2192 resize \u00B7 tab panel \u00B7 r reload \u00B7 q quit'\n : '\u2191\u2193/jk scroll \u00B7 home/end top/bottom \u00B7 alt\u2190\u2192 resize \u00B7 tab panel \u00B7 r reload \u00B7 q quit',\n [focus],\n );\n\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexDirection=\"row\" height={topHeight} flexShrink={0}>\n <WorklogPanel\n state={worklogs}\n focused={focus === 'worklog'}\n height={topHeight}\n width={leftWidth}\n offset={worklogOffset}\n spinnerFrame={spinnerFrame}\n dayMarks={dayMarks}\n />\n <IssueList\n state={issues}\n rows={issueRowsData}\n fullTree={fullTree}\n focused={focus === 'issues'}\n height={topHeight}\n width={columns - leftWidth}\n selectedIndex={selectedIndex}\n offset={issueOffset}\n spinnerFrame={spinnerFrame}\n />\n </Box>\n\n <Box flexDirection=\"row\" height={bottomHeight} flexShrink={0}>\n <WorkChartPanel\n days={chartDays}\n issueKey={selected?.key ?? null}\n state={worklogs}\n focused={false}\n height={bottomHeight}\n width={chartWidth}\n spinnerFrame={spinnerFrame}\n dayMarks={dayMarks}\n />\n <IssuePreview\n issue={selected}\n worklog={(selected && worklogs.data?.byIssue[selected.key]) || []}\n worklogWindowDays={worklogs.data?.windowDays ?? config.worklogDays}\n focused={focus === 'preview'}\n height={bottomHeight}\n width={columns - chartWidth}\n offset={previewOffset}\n />\n </Box>\n\n <Box height={1} flexShrink={0} justifyContent=\"space-between\">\n <Text color=\"gray\" wrap=\"truncate-end\">\n {' '}\n {hint}\n </Text>\n {notice ? (\n <Text color={notice.error ? 'red' : 'green'} wrap=\"truncate-end\">\n {notice.error ? '\u2716' : '\u2713'} {notice.text}{' '}\n </Text>\n ) : loading ? (\n <Text color=\"cyan\" wrap=\"truncate-end\">\n {spinnerChar(spinnerFrame)} loading\u2026{' '}\n </Text>\n ) : (\n <Text color=\"gray\" wrap=\"truncate-end\">\n {me.displayName}{' '}\n </Text>\n )}\n </Box>\n </Box>\n );\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport { PANEL_CHROME_ROWS, PanelFrame } from './PanelFrame';\nimport { PanelError, Spinner, spinnerChar } from './PanelStatus';\nimport { ISSUE_TYPE_WIDTH, issueTypeMark } from '../issueType';\nimport { PRIORITY_WIDTH, priorityMark } from '../priority';\nimport { folderMark, treePrefix } from '../tree';\nimport type { Issue, IssueRow, Loadable } from '../types';\n\ninterface IssueListProps {\n state: Loadable<Issue[]>;\n rows: IssueRow[];\n fullTree: boolean;\n focused: boolean;\n height: number;\n width: number;\n selectedIndex: number;\n offset: number;\n spinnerFrame: number;\n}\n\n/** Status colouring keyed on Jira's status category, which is stable per site. */\nfunction statusColor(category: string): string {\n switch (category.toLowerCase()) {\n case 'in progress':\n return 'yellow';\n case 'done':\n return 'green';\n default:\n return 'blue';\n }\n}\n\n/** Truncate or pad `text` so a cell always covers exactly `width` columns. */\nfunction fit(text: string, width: number): string {\n if (width <= 0) return '';\n return text.length > width ? `${text.slice(0, Math.max(0, width - 1))}\u2026` : text.padEnd(width);\n}\n\nconst KEY_WIDTH = 10;\nconst STATUS_WIDTH = 14;\n\nexport function IssueList({\n state,\n rows,\n fullTree,\n focused,\n height,\n width,\n selectedIndex,\n offset,\n spinnerFrame,\n}: IssueListProps): ReactElement {\n const issues = state.data ?? [];\n const visibleRows = Math.max(1, height - PANEL_CHROME_ROWS);\n\n // One gutter width for the whole list keeps the key/status columns aligned,\n // and it is capped so deep trees cannot squeeze the summary off screen.\n // +2 for the folder marker column that follows the branch glyphs.\n const gutterWidth = Math.min(\n Math.max(0, Math.floor(width / 4)),\n rows.reduce((widest, row) => Math.max(widest, treePrefix(row).length + 2), 0),\n );\n const summaryWidth = Math.max(\n 8,\n width - 4 - 2 - gutterWidth - ISSUE_TYPE_WIDTH - PRIORITY_WIDTH - KEY_WIDTH - STATUS_WIDTH,\n );\n\n const label = fullTree ? 'my tickets \u00B7 full tree' : 'my open tickets';\n const base =\n rows.length > visibleRows\n ? `${label} (${selectedIndex + 1}/${rows.length})`\n : `${label} (${rows.length})`;\n // Reloading keeps the old rows on screen, so the spinner moves into the title.\n const title = state.loading && issues.length > 0 ? `${spinnerChar(spinnerFrame)} ${base}` : base;\n\n return (\n <PanelFrame title={title} focused={focused} height={height} flexGrow={1}>\n {state.loading && issues.length === 0 ? (\n <Spinner label=\"loading tickets\u2026\" frame={spinnerFrame} />\n ) : state.error ? (\n <PanelError error={state.error} />\n ) : issues.length === 0 ? (\n <Text color=\"gray\">No open tickets matched the query.</Text>\n ) : (\n <Box flexDirection=\"column\">\n {rows.slice(offset, offset + visibleRows).map((row, index) => {\n const issue = row.issue;\n const selected = offset + index === selectedIndex;\n // The highlight has to paint the padding too, so every cell is\n // padded to its column width instead of relying on Box layout.\n const background = selected ? (focused ? 'cyan' : 'blue') : undefined;\n const cellColor = (own: string): string => (selected ? 'black' : own);\n const issueType = issueTypeMark(issue.issueType);\n const priority = priorityMark(issue.priority);\n return (\n <Box key={issue.key}>\n <Text color={cellColor('cyan')} backgroundColor={background}>\n {selected ? '\u25B8 ' : ' '}\n </Text>\n {gutterWidth > 0 ? (\n <Text color={cellColor('gray')} backgroundColor={background}>\n {fit(`${treePrefix(row)}${folderMark(row)} `, gutterWidth)}\n </Text>\n ) : null}\n <Text\n color={issueType ? cellColor(issueType.color) : undefined}\n backgroundColor={background}\n >\n {fit(issueType ? issueType.glyph : '', ISSUE_TYPE_WIDTH)}\n </Text>\n <Text\n bold={priority?.bold}\n color={priority ? cellColor(priority.color) : undefined}\n backgroundColor={background}\n >\n {fit(priority ? priority.glyph : '', PRIORITY_WIDTH)}\n </Text>\n <Text\n bold={selected}\n color={cellColor(issue.mine ? 'white' : 'gray')}\n backgroundColor={background}\n >\n {fit(issue.key, KEY_WIDTH)}\n </Text>\n <Text\n color={cellColor(statusColor(issue.statusCategory))}\n backgroundColor={background}\n >\n {fit(issue.status, STATUS_WIDTH)}\n </Text>\n <Text\n color={cellColor('gray')}\n bold={selected}\n backgroundColor={background}\n >\n {fit(issue.summary, summaryWidth)}\n </Text>\n </Box>\n );\n })}\n </Box>\n )}\n </PanelFrame>\n );\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement, ReactNode } from 'react';\n\ninterface PanelFrameProps {\n title: string;\n focused: boolean;\n height: number;\n width?: number | string;\n flexGrow?: number;\n children: ReactNode;\n}\n\n/**\n * Bordered panel with a one-line header.\n *\n * The border and header occupy fixed rows, so callers can map a screen row to\n * their own content: content starts at the panel's top + {@link PANEL_HEADER_ROWS}.\n */\nexport const PANEL_HEADER_ROWS = 2; // top border + header line\nexport const PANEL_CHROME_ROWS = 3; // ...plus the bottom border\n\nexport function PanelFrame({\n title,\n focused,\n height,\n width,\n flexGrow,\n children,\n}: PanelFrameProps): ReactElement {\n return (\n <Box\n flexDirection=\"column\"\n height={height}\n width={width}\n flexGrow={flexGrow}\n // A panel given an explicit width must keep it: shrinking one column makes\n // fixed-width content (chart rows, aligned labels) wrap and fall apart.\n flexShrink={width === undefined ? 1 : 0}\n borderStyle=\"round\"\n borderColor={focused ? 'cyan' : 'gray'}\n paddingX={1}\n overflow=\"hidden\"\n >\n <Text bold color={focused ? 'cyan' : 'white'}>\n {title}\n </Text>\n {children}\n </Box>\n );\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport type { JiraError } from '../types';\n\n/** Spinner frames chosen to render on any terminal that supports Braille. */\nconst FRAMES = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F'];\n\n/** Current spinner glyph, for inlining into a panel title or status bar. */\nexport function spinnerChar(frame: number): string {\n return FRAMES[frame % FRAMES.length] ?? FRAMES[0] ?? '\u00B7';\n}\n\nexport function Spinner({ label, frame }: { label: string; frame: number }): ReactElement {\n return (\n <Text color=\"cyan\">\n {spinnerChar(frame)} <Text color=\"gray\">{label}</Text>\n </Text>\n );\n}\n\n/** Panel-local error: one panel failing must not blank the other. */\nexport function PanelError({ error }: { error: JiraError }): ReactElement {\n return (\n <Box flexDirection=\"column\">\n <Text color=\"red\" wrap=\"truncate-end\">\n \u2716 {error.message}\n </Text>\n {error.hints.slice(0, 3).map((hint, index) => (\n <Text key={index} color=\"gray\" wrap=\"truncate-end\">\n {hint}\n </Text>\n ))}\n <Text color=\"gray\">Press r to retry.</Text>\n </Box>\n );\n}\n", "export interface IssueTypeMark {\n glyph: string;\n color: string;\n}\n\n/** Column width for the issue-type cell: glyph + one space, like the priority cell. */\nexport const ISSUE_TYPE_WIDTH = 2;\n\n/**\n * Nerd Font icons per issue type. They sit in the Private Use Area, so each is a\n * single UTF-16 unit and one terminal column wide in a Nerd Font.\n */\nconst MARKS: { mark: IssueTypeMark; names: string[] }[] = [\n { mark: { glyph: '\uF46F', color: 'red' }, names: ['bug', 'defect'] },\n { mark: { glyph: '\uF14A', color: 'blue' }, names: ['task'] },\n { mark: { glyph: '\uF02E', color: 'green' }, names: ['story', 'user story'] },\n { mark: { glyph: '\uF0AE', color: 'cyan' }, names: ['sub-task', 'subtask', 'sub task'] },\n];\n\nconst BY_NAME = new Map<string, IssueTypeMark>(\n MARKS.flatMap(({ mark, names }) => names.map((name) => [name, mark] as const)),\n);\n\n/** Unknown issue types (Epic, custom ones) render as a blank cell. */\nexport function issueTypeMark(issueType: string): IssueTypeMark | null {\n return BY_NAME.get(issueType.trim().toLowerCase()) ?? null;\n}\n\n/** Exposed so tests can assert the single-column invariant over every glyph. */\nexport const ISSUE_TYPE_GLYPHS = MARKS.map(({ mark }) => mark.glyph);\n", "export interface PriorityMark {\n glyph: string;\n color: string;\n bold: boolean;\n}\n\n/** Column width for the priority cell: glyph + one space, like the caret cell. */\nexport const PRIORITY_WIDTH = 2;\n\n/**\n * Jira draws priority as a stack of arrows: up for urgent, flat for the middle,\n * down for the rest. Every glyph has to be one terminal column wide, because the\n * list pads cells with `padEnd` and would shear the grid otherwise.\n */\nconst MARKS: { mark: PriorityMark; names: string[] }[] = [\n { mark: { glyph: '\u203C', color: 'red', bold: true }, names: ['blocker', 'critical', 'urgent'] },\n { mark: { glyph: '\u21C8', color: 'red', bold: true }, names: ['highest', 'p1'] },\n { mark: { glyph: '\u2191', color: 'red', bold: false }, names: ['high', 'major', 'p2'] },\n { mark: { glyph: '=', color: 'yellow', bold: false }, names: ['medium', 'normal', 'p3'] },\n { mark: { glyph: '\u2193', color: 'blue', bold: false }, names: ['low', 'minor', 'p4'] },\n { mark: { glyph: '\u21CA', color: 'blue', bold: false }, names: ['lowest', 'trivial', 'p5'] },\n];\n\nconst BY_NAME = new Map<string, PriorityMark>(\n MARKS.flatMap(({ mark, names }) => names.map((name) => [name, mark] as const)),\n);\n\n/**\n * Priority names are configurable per Jira site, so match a known alias rather\n * than guessing. Unknown and unset priorities render as a blank cell.\n */\nexport function priorityMark(priority: string | null): PriorityMark | null {\n if (!priority) return null;\n return BY_NAME.get(priority.trim().toLowerCase()) ?? null;\n}\n\n/** Exposed so tests can assert the single-column invariant over every glyph. */\nexport const PRIORITY_GLYPHS = MARKS.map(({ mark }) => mark.glyph);\n", "import type { Issue, IssueRow } from './types';\n\n/**\n * Flatten issues into tree rows.\n *\n * In `full` mode every loaded ancestor gets its own row, so a subtask sits\n * under its story under its epic even when those levels are someone else's\n * work. Otherwise only the user's own issues are rendered, re-parented onto\n * their nearest own ancestor \u2014 an epic of mine with a foreign story in between\n * still adopts my subtask directly.\n *\n * Order follows the JQL result: each issue appears as soon as its branch does.\n */\nexport function buildIssueTree(issues: Issue[], full: boolean): IssueRow[] {\n const byKey = new Map(issues.map((issue) => [issue.key, issue]));\n const visible = full ? issues : issues.filter((issue) => issue.mine);\n const visibleKeys = new Set(visible.map((issue) => issue.key));\n\n /** Nearest ancestor that is itself rendered, or null for a root. */\n const parentOf = (issue: Issue): string | null => {\n let key = issue.parent?.key ?? null;\n const seen = new Set<string>([issue.key]);\n while (key && !seen.has(key)) {\n seen.add(key);\n if (visibleKeys.has(key)) return key;\n key = byKey.get(key)?.parent?.key ?? null;\n }\n return null;\n };\n\n const roots: Issue[] = [];\n const children = new Map<string, Issue[]>();\n\n for (const issue of visible) {\n const parent = parentOf(issue);\n if (parent === null) {\n roots.push(issue);\n continue;\n }\n const siblings = children.get(parent);\n if (siblings) siblings.push(issue);\n else children.set(parent, [issue]);\n }\n\n const rows: IssueRow[] = [];\n\n const walk = (issue: Issue, guides: boolean[], last: boolean): void => {\n const kids = children.get(issue.key) ?? [];\n rows.push({ issue, guides, last, hasChildren: kids.length > 0 });\n kids.forEach((kid, index) => {\n // One guide per ancestor edge: the column stays open while that ancestor\n // still has siblings queued below it.\n walk(kid, [...guides, !last], index === kids.length - 1);\n });\n };\n\n // Roots hang off a virtual trunk, so they carry branch marks of their own.\n roots.forEach((root, index) => {\n walk(root, [], index === roots.length - 1);\n });\n\n return rows;\n}\n\n/** The `\u2502 \u251C\u2500 ` gutter for a row, one level of indent per ancestor. */\nexport function treePrefix(row: IssueRow): string {\n const stem = row.guides.map((continues) => (continues ? '\u2502 ' : ' ')).join('');\n return `${stem}${row.last ? '\u2514\u2500 ' : '\u251C\u2500 '}`;\n}\n\n/** Folder marker: filled for a row with children, blank for a leaf. */\nexport function folderMark(row: IssueRow): string {\n return row.hasChildren ? '\u25BE' : ' ';\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport { PANEL_CHROME_ROWS, PanelFrame } from './PanelFrame';\nimport type { Issue, IssueWorklogEntry } from '../types';\n\ninterface IssuePreviewProps {\n issue: Issue | null;\n /** The current user's worklog entries for this issue, within the window. */\n worklog: IssueWorklogEntry[];\n /** How many days that worklog window covers, for labelling. */\n worklogWindowDays: number;\n focused: boolean;\n height: number;\n width: number;\n offset: number;\n}\n\nconst MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];\nconst FIELD_LABEL_WIDTH = 11;\n\nfunction formatUpdated(value: string | null): string {\n if (!value) return 'unknown';\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return value;\n return date.toLocaleString();\n}\n\n/** `28 Jul` from a `YYYY-MM-DD` key. */\nfunction formatDay(key: string): string {\n const [, month = '01', day = '01'] = key.split('-');\n return `${day} ${MONTHS[Number(month) - 1] ?? month}`;\n}\n\n/** `7.5h`, or `45m` for sub-hour amounts where decimals read badly. */\nfunction formatHours(hours: number): string {\n if (hours === 0) return '0h';\n return hours < 1 ? `${Math.round(hours * 60)}m` : `${hours.toFixed(1)}h`;\n}\n\n/**\n * Parent chain, most general first: project \u2192 parent \u2192 this issue.\n *\n * Jira's `parent` field only exposes one level, so a subtask shows its story but\n * not the story's epic.\n */\nfunction breadcrumbs(issue: Issue): string {\n const parts = [issue.projectName, issue.parent && `${issue.parent.key} ${issue.parent.summary}`]\n .filter((part): part is string => Boolean(part))\n .map((part) => part.trim());\n return [...parts, issue.key].join(' \u203A ');\n}\n\n/** Hard-wrap description text so we can window it by line for scrolling. */\nfunction wrapLines(text: string, width: number): string[] {\n const out: string[] = [];\n for (const line of text.split('\\n')) {\n if (line.length <= width) {\n out.push(line);\n continue;\n }\n let rest = line;\n while (rest.length > width) {\n const cut = rest.lastIndexOf(' ', width);\n const at = cut > width / 2 ? cut : width;\n out.push(rest.slice(0, at));\n rest = rest.slice(at).trimStart();\n }\n out.push(rest);\n }\n return out;\n}\n\nfunction Field({ label, value, color }: { label: string; value: string; color?: string }): ReactElement {\n return (\n <Box>\n <Box width={FIELD_LABEL_WIDTH} flexShrink={0}>\n <Text color=\"gray\">{label}</Text>\n </Box>\n <Text color={color} wrap=\"truncate-end\">\n {value}\n </Text>\n </Box>\n );\n}\n\nexport function IssuePreview({\n issue,\n worklog,\n worklogWindowDays,\n focused,\n height,\n width,\n offset,\n}: IssuePreviewProps): ReactElement {\n if (!issue) {\n return (\n <PanelFrame title=\"preview\" focused={focused} height={height} flexGrow={1}>\n <Text color=\"gray\">Select a ticket above to preview it.</Text>\n </PanelFrame>\n );\n }\n\n const loggedHours = worklog.reduce((sum, entry) => sum + entry.hours, 0);\n const loggedDates = worklog.map((entry) => `${formatDay(entry.date)} (${formatHours(entry.hours)})`);\n const myWork =\n worklog.length > 0\n ? `${formatHours(loggedHours)} \u00B7 ${loggedDates.join(', ')}`\n : `nothing in the last ${worklogWindowDays} days`;\n\n const total =\n issue.totalTimeSpentSeconds !== null && issue.totalTimeSpentSeconds > 0\n ? `${formatHours(issue.totalTimeSpentSeconds / 3600)} logged by everyone`\n : null;\n\n // Breadcrumbs + summary + type/status/sprint/assignee/reporter/priority/labels/\n // your work/updated. Keep in step with the fields rendered below.\n const fieldRows = 11 + (total ? 1 : 0);\n const lines = wrapLines(issue.description || '(no description)', Math.max(20, width - 4));\n\n // Fields are fixed-height; the description gets the rest, minus one row for the\n // \"more lines\" indicator whenever it will be shown. Overshooting here makes Ink\n // clip the panel, which silently eats the title row.\n const available = Math.max(1, height - PANEL_CHROME_ROWS - fieldRows);\n\n // Clamp so scrolling past the end keeps the last screenful visible instead of\n // sliding into blank space.\n const start = Math.min(offset, Math.max(0, lines.length - available));\n const bodyRows = lines.length - start > available ? Math.max(1, available - 1) : available;\n\n const visible = lines.slice(start, start + bodyRows);\n const more = lines.length - start - visible.length;\n\n return (\n <PanelFrame\n title={`${issue.key} \u00B7 ${issue.url}`}\n focused={focused}\n height={height}\n flexGrow={1}\n >\n <Text color=\"gray\" wrap=\"truncate-end\">\n {breadcrumbs(issue)}\n </Text>\n <Text bold wrap=\"truncate-end\">\n {issue.summary}\n </Text>\n <Field label=\"type\" value={issue.issueType || '\u2014'} />\n <Field label=\"status\" value={issue.status} />\n <Field label=\"sprint\" value={issue.sprints.length > 0 ? issue.sprints.join(', ') : 'None'} />\n <Field label=\"assignee\" value={issue.assignee ?? 'Unassigned'} />\n <Field label=\"reporter\" value={issue.reporter ?? '\u2014'} />\n <Field label=\"priority\" value={issue.priority ?? '\u2014'} />\n <Field label=\"labels\" value={issue.labels.length > 0 ? issue.labels.join(', ') : '\u2014'} />\n <Field\n label=\"your work\"\n value={myWork}\n color={worklog.length > 0 ? 'green' : undefined}\n />\n {total && <Field label=\"total work\" value={total} />}\n <Field label=\"updated\" value={formatUpdated(issue.updated)} />\n\n <Box flexDirection=\"column\">\n {visible.map((line, index) => (\n <Text key={start + index} color=\"white\" wrap=\"truncate-end\">\n {line || ' '}\n </Text>\n ))}\n {more > 0 && <Text color=\"gray\">\u2026 {more} more lines (\u2193 to scroll)</Text>}\n </Box>\n </PanelFrame>\n );\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport { PANEL_CHROME_ROWS, PanelFrame } from './PanelFrame';\nimport { PanelError, Spinner } from './PanelStatus';\nimport type { DayMarks } from '../data/calendar';\nimport { dayOfMonth, isToday, isWeekend, MONTHS } from '../dates';\nimport type { Loadable, WorklogDay, WorklogSummary } from '../types';\n\ninterface WorkChartPanelProps {\n /** One entry per day in the window, oldest first, already scoped to an issue. */\n days: WorklogDay[];\n /** The issue the bars belong to; `null` when nothing is selected. */\n issueKey: string | null;\n /** Loading/error state of the worklog fetch the days come from. */\n state: Loadable<WorklogSummary>;\n focused: boolean;\n height: number;\n width: number;\n spinnerFrame: number;\n /** Holidays and personal days off, so a bare column can be read as expected. */\n dayMarks: DayMarks;\n}\n\n/** One full row of bar equals this many hours; half a row equals half of it. */\nexport const HOURS_PER_BAR = 4;\n\n/** Rows reserved at the bottom for the day labels. */\nconst LABEL_ROWS = 1;\n\n/** A full working day is two rows, so never draw a shorter plot than that. */\nconst MIN_BAR_ROWS = 2;\n\nconst FULL_BLOCK = '\u2588';\nconst HALF_BLOCK = '\u2584';\n/** Anything logged but under half a bar, so a short day still reads as worked. */\nconst TRACE_BLOCK = '\u2581';\n\n/** Borders (2) + horizontal padding (2), plus one spare column of slack. */\nconst PANEL_SIDE_COLUMNS = 5;\n\n/** Marks a non-working day that has no bar, which would otherwise be blank. */\nconst REST_DOT = '\u00B7';\n\n/**\n * Width the chart needs for `dayCount` bars at the given bar width, including a\n * one-column gap between bars. A row that overflows by even one column wraps and\n * destroys the chart, so this has to be exact.\n */\nexport function chartWidthFor(dayCount: number, barWidth: number): number {\n const bars = dayCount * barWidth + Math.max(0, dayCount - 1);\n return bars + PANEL_SIDE_COLUMNS;\n}\n\n/**\n * Vertical bar chart of hours logged per day on one issue.\n *\n * The scale is fixed rather than fitted to the data, so a bar height means the\n * same thing between issues and between reloads: 2h is half a row, 4h one row,\n * 8h two rows.\n */\nexport function WorkChartPanel({\n days,\n issueKey,\n state,\n focused,\n height,\n width,\n spinnerFrame,\n dayMarks,\n}: WorkChartPanelProps): ReactElement {\n if (state.loading && days.length === 0) {\n return (\n <PanelFrame title=\"logged work\" focused={focused} height={height} width={width}>\n <Spinner label=\"loading\u2026\" frame={spinnerFrame} />\n </PanelFrame>\n );\n }\n if (state.error) {\n return (\n <PanelFrame title=\"logged work\" focused={focused} height={height} width={width}>\n <PanelError error={state.error} />\n </PanelFrame>\n );\n }\n if (!issueKey || days.length === 0) {\n return (\n <PanelFrame title=\"logged work\" focused={focused} height={height} width={width}>\n <Text color=\"gray\">Select a ticket to see your work on it.</Text>\n </PanelFrame>\n );\n }\n\n // Two columns per bar reads better, but fall back to one when space is tight.\n const barWidth = chartWidthFor(days.length, 2) <= width ? 2 : 1;\n\n const tallest = days.reduce((max, day) => Math.max(max, day.hours), 0);\n const roomForRows = Math.max(1, height - PANEL_CHROME_ROWS - LABEL_ROWS);\n const barRows = Math.min(roomForRows, Math.max(MIN_BAR_ROWS, Math.ceil(tallest / HOURS_PER_BAR)));\n\n // Drawn top-down, so the first row is the highest and the last is the baseline.\n const rows = Array.from({ length: barRows }, (_, index) => barRows - 1 - index);\n\n const total = days.reduce((sum, day) => sum + day.hours, 0);\n const clipped = tallest / HOURS_PER_BAR > barRows;\n const title =\n `${issueKey} \u00B7 ${total.toFixed(1)}h over ${days.length}d \u00B7 ${HOURS_PER_BAR}h = 1 bar` +\n (clipped ? ` \u00B7 peak ${tallest.toFixed(1)}h` : '');\n\n return (\n <PanelFrame title={title} focused={focused} height={height} width={width}>\n {/* Bars grow upward from a baseline pinned to the bottom of the panel. */}\n <Box flexDirection=\"column\" flexGrow={1} justifyContent=\"flex-end\">\n {/* An all-blank plot reads as broken, so say so instead. */}\n {total === 0 && <Text color=\"gray\">no time logged in this window</Text>}\n {total > 0 &&\n rows.map((rowFromBottom) => (\n <Text key={rowFromBottom}>\n {days.map((day, index) => {\n // Only days that are actually off for you get the rest treatment;\n // a holiday observed elsewhere is still a working day here.\n const rest = dayMarks.get(day.date)?.observed === true;\n // A rest day with no bar still gets a baseline dot, so it never\n // reads as an unexplained gap.\n const glyph =\n rest && day.hours === 0 && rowFromBottom === 0\n ? REST_DOT\n : glyphFor(day.hours, rowFromBottom);\n return (\n <Text key={day.date}>\n {index > 0 ? ' ' : ''}\n <Text color={barColor(day, rest)}>{glyph.repeat(barWidth)}</Text>\n </Text>\n );\n })}\n </Text>\n ))}\n\n {barWidth >= 2 ? (\n <Text>\n {days.map((day, index) => (\n <Text key={day.date}>\n {index > 0 ? ' ' : ''}\n <Text\n color={\n isToday(day.date)\n ? 'cyan'\n : dayMarks.get(day.date)?.observed\n ? 'magenta'\n : 'gray'\n }\n bold={isToday(day.date)}\n >\n {dayOfMonth(day.date)}\n </Text>\n </Text>\n ))}\n </Text>\n ) : (\n // One column per bar leaves no room for a label per day; a date range\n // reads better than a row of single digits.\n <Text color=\"gray\" wrap=\"truncate-end\">\n {rangeLabel(days)}\n </Text>\n )}\n </Box>\n </PanelFrame>\n );\n}\n\n/**\n * Which block character a bar shows on a given row.\n *\n * `rowFromBottom` 0 is the baseline. A row is a full block once the day's hours\n * reach its top and a half block once they reach its middle, so 2h renders as a\n * half block sitting on the baseline. Below that, any logged time still gets a\n * thin mark \u2014 an hour of work must not look the same as none.\n */\nfunction glyphFor(hours: number, rowFromBottom: number): string {\n const rowsFilled = hours / HOURS_PER_BAR - rowFromBottom;\n if (rowsFilled >= 1) return FULL_BLOCK;\n if (rowsFilled >= 0.5) return HALF_BLOCK;\n if (rowsFilled > 0) return TRACE_BLOCK;\n return ' ';\n}\n\n/** Rest days are tinted differently so an empty column reads as expected. */\nfunction barColor(day: WorklogDay, rest: boolean): string {\n if (rest) return 'magenta';\n if (day.hours === 0) return 'gray';\n return isWeekend(day.date) ? 'blue' : 'green';\n}\n\n/** `16 Jul \u2192 29 Jul`, for when per-bar labels do not fit. */\nfunction rangeLabel(days: WorklogDay[]): string {\n const format = (key: string): string => {\n const [, month = '01', day = '01'] = key.split('-');\n return `${day} ${MONTHS[Number(month) - 1] ?? month}`;\n };\n const first = days[0]?.date;\n const last = days[days.length - 1]?.date;\n return first && last ? `${format(first)} \u2192 ${format(last)}` : '';\n}\n", "export const WEEKDAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\nexport const MONTHS = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\n/** A `YYYY-MM-DD` key as a local date, so day boundaries match the user's clock. */\nexport function parseDate(key: string): Date {\n const [year = '1970', month = '01', day = '01'] = key.split('-');\n return new Date(Number(year), Number(month) - 1, Number(day));\n}\n\n/** `Mon 28 Jul` from a `YYYY-MM-DD` key. */\nexport function formatDate(key: string): string {\n const [, , day = '01'] = key.split('-');\n const date = parseDate(key);\n return `${WEEKDAYS[date.getDay()]} ${day} ${MONTHS[date.getMonth()]}`;\n}\n\nexport function isWeekend(key: string): boolean {\n const weekday = parseDate(key).getDay();\n return weekday === 0 || weekday === 6;\n}\n\nexport function isToday(key: string): boolean {\n return parseDate(key).toDateString() === new Date().toDateString();\n}\n\n/** Two-digit day of month, so labels line up under their two-column bars. */\nexport function dayOfMonth(key: string): string {\n const [, , day = '01'] = key.split('-');\n return day.padStart(2, '0');\n}\n", "import { Box, Text } from 'ink';\nimport type { ReactElement } from 'react';\n\nimport { PANEL_CHROME_ROWS, PanelFrame } from './PanelFrame';\nimport { PanelError, Spinner, spinnerChar } from './PanelStatus';\nimport { isNonWorkingDay, type DayMark, type DayMarks } from '../data/calendar';\nimport { formatDate, isWeekend } from '../dates';\nimport type { Loadable, WorklogSummary } from '../types';\n\ninterface WorklogPanelProps {\n state: Loadable<WorklogSummary>;\n focused: boolean;\n height: number;\n width: number;\n offset: number;\n spinnerFrame: number;\n /** Holidays and personal days off, so empty days can be told apart. */\n dayMarks: DayMarks;\n}\n\n/**\n * `Mon 28 Jul` (10) plus a marker glyph and a two-cell emoji badge, and one\n * column of gap before the hours.\n */\nconst DATE_WIDTH = 14;\n\n/** Colour hours by how close the day is to a full working day. */\nfunction hoursColor(hours: number, nonWorking: boolean): string {\n if (hours === 0) return nonWorking ? 'gray' : 'red';\n if (hours < 6) return 'yellow';\n return 'green';\n}\n\n/** A trailing glyph on the date keeps holidays legible without extra columns. */\nfunction markGlyph(mark: DayMark | undefined): string {\n if (!mark) return '';\n if (mark.kind === 'dayoff') return '\u00B7';\n // A holiday you do not observe is worth knowing about, but it is not your day off.\n return mark.observed ? '*' : '~';\n}\n\n/**\n * Statutory holidays get a badge next to the date; personal leave keeps its\n * plainer `\u00B7`, so the emoji stays a reliable \"public holiday\" signal.\n */\nfunction markBadge(mark: DayMark | undefined): string {\n return mark?.kind === 'holiday' ? '\uD83C\uDF89' : '';\n}\n\nexport function WorklogPanel({\n state,\n focused,\n height,\n width,\n offset,\n spinnerFrame,\n dayMarks,\n}: WorklogPanelProps): ReactElement {\n const days = state.data?.days ?? [];\n const visibleRows = Math.max(1, height - PANEL_CHROME_ROWS);\n const total = days.reduce((sum, day) => sum + day.hours, 0);\n // Panel width minus borders, padding, date column and hours column.\n const keysWidth = Math.max(6, width - 4 - DATE_WIDTH - 7);\n\n const base =\n days.length > 0\n ? `last ${days.length} days \u00B7 ${total.toFixed(1)}h total`\n : `last ${days.length || 14} days`;\n // Reloading keeps the old rows on screen, so the spinner moves into the title.\n const title = state.loading && days.length > 0 ? `${spinnerChar(spinnerFrame)} ${base}` : base;\n\n return (\n <PanelFrame title={title} focused={focused} height={height} width={width}>\n {state.loading && days.length === 0 ? (\n <Spinner label=\"loading worklogs\u2026\" frame={spinnerFrame} />\n ) : state.error ? (\n <PanelError error={state.error} />\n ) : (\n <Box flexDirection=\"column\">\n {days.slice(offset, offset + visibleRows).map((day) => {\n const mark = dayMarks.get(day.date);\n const weekend = isWeekend(day.date);\n const nonWorking = isNonWorkingDay(day.date, dayMarks);\n // With nothing logged, the reason the day is empty is the useful thing to show.\n const keys =\n day.issueKeys.length > 0 ? day.issueKeys.join(', ') : mark ? `(${mark.label})` : '\u2014';\n return (\n <Box key={day.date}>\n <Box width={DATE_WIDTH} flexShrink={0}>\n <Text\n color={mark ? 'magenta' : weekend ? 'gray' : 'white'}\n dimColor={mark !== undefined && !mark.observed}\n >\n {formatDate(day.date) + markGlyph(mark) + markBadge(mark)}\n </Text>\n </Box>\n <Box width={7} flexShrink={0}>\n <Text color={hoursColor(day.hours, nonWorking)}>\n {`${day.hours.toFixed(1)}h`.padStart(6)}\n </Text>\n </Box>\n <Box width={keysWidth} flexShrink={1}>\n <Text\n color={mark && day.issueKeys.length === 0 ? 'magenta' : 'gray'}\n dimColor={mark !== undefined && !mark.observed}\n wrap=\"truncate-end\"\n >\n {keys}\n </Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n )}\n </PanelFrame>\n );\n}\n", "import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { CONFIG_DIR } from './config';\nimport { isWeekend } from '../dates';\nimport type { Config } from '../types';\n\nexport const HOLIDAY_CACHE_PATH = join(CONFIG_DIR, 'holidays.json');\nexport const DAYS_OFF_PATH = join(CONFIG_DIR, 'days-off.json');\n\nconst NAGER_URL = 'https://date.nager.at/api/v3/PublicHolidays';\nconst REQUEST_TIMEOUT_MS = 20_000;\n\n/** Holiday tables get corrected after publication, so cached years expire. */\nconst CACHE_TTL_MS = 30 * 24 * 60 * 60 * 1000;\n\nexport type DayMarkKind = 'holiday' | 'dayoff';\n\nexport interface DayMark {\n kind: DayMarkKind;\n label: string;\n /**\n * Whether the day is actually off for *you*. Only reachable as `false` when\n * no region is configured: a holiday some regions observe \u2014 Canada's Civic\n * Holiday, say \u2014 is then still worth showing, since we cannot tell whether it\n * is yours, but it does not excuse an empty timesheet. Once `holidayRegion`\n * is set, holidays that miss it are dropped instead.\n */\n observed: boolean;\n}\n\n/** Non-working days keyed by local `YYYY-MM-DD` date. */\nexport type DayMarks = Map<string, DayMark>;\n\n/** A day nobody is expected to log hours on. */\nexport function isNonWorkingDay(key: string, marks: DayMarks): boolean {\n return isWeekend(key) || marks.get(key)?.observed === true;\n}\n\nexport interface CachedHoliday {\n date: string;\n name: string;\n /** Subdivisions observing it; absent or empty means the whole country does. */\n counties?: string[];\n}\n\ninterface CachedYear {\n fetchedAt: string;\n days: CachedHoliday[];\n}\n\n/**\n * Bumped when the cached shape changes so old files are discarded rather than\n * misread. v1 stored only nationwide holidays; v2 keeps regional ones too.\n */\nconst CACHE_VERSION = 2;\n\ninterface HolidayCache {\n version: number;\n country: string | null;\n years: Record<string, CachedYear>;\n}\n\nconst DATE_KEY = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/**\n * Statutory holidays plus personal days off across the given years.\n *\n * Never throws: a failed fetch falls back to whatever is cached, and a missing\n * cache to no holidays at all. A dashboard that starts without holiday colours\n * beats one that does not start.\n */\nexport async function loadDayMarks(config: Config, years: number[]): Promise<DayMarks> {\n return buildDayMarks(await loadHolidays(config, years), config.holidayRegion, readDaysOff());\n}\n\n/**\n * The marking rule on its own, free of network and disk so it can be tested\n * directly: which holidays survive, which count as days off, and how personal\n * leave overrides them.\n */\nexport function buildDayMarks(\n holidays: CachedHoliday[],\n region: string | null,\n daysOff: Array<[string, string]>,\n): DayMarks {\n const marks: DayMarks = new Map();\n\n // One date can carry several regional holidays \u2014 Canada's first Monday in\n // August is Civic Holiday, Saskatchewan Day and BC Day at once \u2014 so they are\n // merged rather than overwriting each other.\n const regional = new Map<string, { names: string[]; counties: string[] }>();\n\n for (const holiday of holidays) {\n const counties = holiday.counties ?? [];\n const observed = counties.length === 0 || (region !== null && counties.includes(region));\n\n if (observed) {\n // An observed holiday always wins the date over merely-regional ones.\n marks.set(holiday.date, { kind: 'holiday', label: holiday.name, observed: true });\n continue;\n }\n\n // Once you have named your region, another region's holiday is simply not\n // yours \u2014 showing it only makes a working day look like a day off. Without\n // a region there is no way to tell, so they are listed instead.\n if (region !== null) continue;\n\n const group = regional.get(holiday.date) ?? { names: [], counties: [] };\n if (!group.names.includes(holiday.name)) group.names.push(holiday.name);\n for (const county of counties) if (!group.counties.includes(county)) group.counties.push(county);\n regional.set(holiday.date, group);\n }\n\n for (const [date, { names, counties }] of regional) {\n if (marks.has(date)) continue;\n // Naming the regions explains why the day is flagged but still yours to work.\n marks.set(date, {\n kind: 'holiday',\n label: `${names.join(' / ')} \u00B7 ${counties.join(', ')}`,\n observed: false,\n });\n }\n\n // Personal leave wins over a holiday on the same date \u2014 it is the more\n // specific statement about the day.\n for (const [date, label] of daysOff) {\n marks.set(date, { kind: 'dayoff', label, observed: true });\n }\n\n return marks;\n}\n\nasync function loadHolidays(config: Config, years: number[]): Promise<CachedHoliday[]> {\n const country = config.holidayCountry;\n if (!country) return [];\n\n const cached = readCache();\n // A different country makes every cached year meaningless. The region is not\n // part of the key: every holiday is cached with the regions observing it, so\n // changing region only re-reads what is already on disk.\n const cache: HolidayCache =\n cached && cached.country === country\n ? cached\n : { version: CACHE_VERSION, country, years: {} };\n\n const stale = years.filter((year) => !isFresh(cache.years[String(year)]));\n const fetched = await Promise.all(\n stale.map(async (year) => [year, await fetchYear(country, year)] as const),\n );\n\n let changed = false;\n for (const [year, days] of fetched) {\n if (!days) continue; // Fetch failed; keep any stale entry rather than losing it.\n cache.years[String(year)] = { fetchedAt: new Date().toISOString(), days };\n changed = true;\n }\n if (changed || cached !== cache) writeCache(cache);\n\n return years.flatMap((year) => cache.years[String(year)]?.days ?? []);\n}\n\nfunction isFresh(entry: CachedYear | undefined): boolean {\n if (!entry) return false;\n const age = Date.now() - Date.parse(entry.fetchedAt);\n return Number.isFinite(age) && age >= 0 && age < CACHE_TTL_MS;\n}\n\ninterface NagerHoliday {\n date?: unknown;\n localName?: unknown;\n name?: unknown;\n global?: unknown;\n counties?: unknown;\n}\n\n/** `null` on any failure, so the caller can fall back to the cache. */\nasync function fetchYear(country: string, year: number): Promise<CachedHoliday[] | null> {\n try {\n const response = await fetch(`${NAGER_URL}/${year}/${country}`, {\n headers: { Accept: 'application/json' },\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n if (!response.ok) return null;\n\n const body: unknown = await response.json();\n if (!Array.isArray(body)) return null;\n\n const days: CachedHoliday[] = [];\n for (const entry of body as NagerHoliday[]) {\n const date = entry.date;\n if (typeof date !== 'string' || !DATE_KEY.test(date)) continue;\n // Regional holidays are kept, not dropped: which regions observe them is\n // decided at read time against the configured region.\n const counties =\n entry.global === true || !Array.isArray(entry.counties)\n ? []\n : entry.counties.filter((county): county is string => typeof county === 'string');\n const name = typeof entry.localName === 'string' ? entry.localName : entry.name;\n days.push({ date, name: typeof name === 'string' ? name : 'holiday', counties });\n }\n return days;\n } catch {\n return null;\n }\n}\n\nfunction readCache(): HolidayCache | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(HOLIDAY_CACHE_PATH, 'utf8'));\n } catch {\n return null;\n }\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n\n const { version, country, years } = parsed as Partial<HolidayCache>;\n if (version !== CACHE_VERSION) return null;\n if (typeof years !== 'object' || years === null) return null;\n\n return {\n version: CACHE_VERSION,\n country: typeof country === 'string' ? country : null,\n years,\n };\n}\n\nfunction writeCache(cache: HolidayCache): void {\n // Written via a temp file so a crash mid-write cannot leave truncated JSON\n // that every later run would have to throw away.\n const temp = `${HOLIDAY_CACHE_PATH}.tmp`;\n try {\n mkdirSync(CONFIG_DIR, { recursive: true });\n writeFileSync(temp, `${JSON.stringify(cache, null, 2)}\\n`, 'utf8');\n renameSync(temp, HOLIDAY_CACHE_PATH);\n } catch {\n // A read-only config dir costs colours, not the session.\n }\n}\n\n/**\n * Hand-maintained leave from `days-off.json`. Accepts a bare array or\n * `{ \"dates\": [...] }`, holding either date strings or `{ date, label }`.\n * Malformed entries are skipped rather than failing the whole file.\n */\nfunction readDaysOff(): Array<[string, string]> {\n let parsed: unknown;\n try {\n parsed = JSON.parse(readFileSync(DAYS_OFF_PATH, 'utf8'));\n } catch {\n return [];\n }\n\n const list = Array.isArray(parsed)\n ? parsed\n : typeof parsed === 'object' && parsed !== null && Array.isArray((parsed as { dates?: unknown }).dates)\n ? ((parsed as { dates: unknown[] }).dates)\n : [];\n\n const days: Array<[string, string]> = [];\n for (const entry of list) {\n if (typeof entry === 'string') {\n if (DATE_KEY.test(entry)) days.push([entry, 'day off']);\n continue;\n }\n if (typeof entry !== 'object' || entry === null) continue;\n const { date, label } = entry as { date?: unknown; label?: unknown };\n if (typeof date !== 'string' || !DATE_KEY.test(date)) continue;\n days.push([date, typeof label === 'string' && label.trim() ? label.trim() : 'day off']);\n }\n return days;\n}\n", "import { readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { JiraError, type Config } from '../types';\n\n/** Everything the dashboard persists \u2014 config, caches, hand-edited lists \u2014 lives here. */\nexport const CONFIG_DIR = join(\n process.env['XDG_CONFIG_HOME'] ?? join(homedir(), '.config'),\n 'jira-dashboard',\n);\n\nexport const CONFIG_PATH = join(CONFIG_DIR, 'config.json');\n\nconst TOKEN_URL = 'https://id.atlassian.com/manage-profile/security/api-tokens';\n\nexport const DEFAULT_ISSUES_JQL = [\n 'assignee = currentUser()',\n 'AND statusCategory != Done',\n 'AND status NOT IN (\"Cancelled\", \"Closed\", \"Backlog\")',\n 'ORDER BY updated DESC',\n].join(' ');\n\nconst DEFAULT_WORKLOG_DAYS = 14;\n\ninterface FileConfig {\n baseUrl?: string;\n email?: string;\n apiToken?: string;\n issuesJql?: string;\n worklogDays?: number;\n holidayCountry?: string;\n holidayRegion?: string;\n}\n\n/** Trim and upper-case a code, collapsing blank/absent values to `null`. */\nfunction normalizeCode(value: string | undefined): string | null {\n const trimmed = value?.trim().toUpperCase();\n return trimmed ? trimmed : null;\n}\n\nfunction readConfigFile(): FileConfig {\n let raw: string;\n try {\n raw = readFileSync(CONFIG_PATH, 'utf8');\n } catch {\n return {};\n }\n\n try {\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n throw new Error('expected a JSON object');\n }\n return parsed;\n } catch (cause) {\n throw new JiraError(`Config file at ${CONFIG_PATH} is not valid JSON.`, [\n cause instanceof Error ? cause.message : String(cause),\n 'Fix the file, or delete it and use environment variables instead.',\n ]);\n }\n}\n\n/** Strip a trailing slash so path joining stays predictable. */\nfunction normalizeBaseUrl(value: string): string {\n const trimmed = value.trim().replace(/\\/+$/, '');\n if (!/^https?:\\/\\//.test(trimmed)) {\n throw new JiraError(`JIRA_BASE_URL must start with https:// (got \"${trimmed}\")`, [\n 'Example: https://your-team.atlassian.net',\n ]);\n }\n return trimmed;\n}\n\n/**\n * Resolve credentials from the environment first, then the config file.\n * Throws a JiraError carrying setup instructions when anything is missing.\n */\nexport function loadConfig(): Config {\n const file = readConfigFile();\n\n const baseUrl = process.env['JIRA_BASE_URL'] ?? file.baseUrl;\n const email = process.env['JIRA_EMAIL'] ?? file.email;\n const apiToken = process.env['JIRA_API_TOKEN'] ?? file.apiToken;\n\n const missing: string[] = [];\n if (!baseUrl) missing.push('JIRA_BASE_URL');\n if (!email) missing.push('JIRA_EMAIL');\n if (!apiToken) missing.push('JIRA_API_TOKEN');\n\n if (missing.length > 0 || !baseUrl || !email || !apiToken) {\n throw new JiraError(`Missing Jira credentials: ${missing.join(', ')}`, [\n 'Set them in your shell:',\n ' export JIRA_BASE_URL=\"https://your-team.atlassian.net\"',\n ' export JIRA_EMAIL=\"you@example.com\"',\n ' export JIRA_API_TOKEN=\"...\"',\n '',\n `Or write them to ${CONFIG_PATH}:`,\n ' { \"baseUrl\": \"...\", \"email\": \"...\", \"apiToken\": \"...\" }',\n '',\n `Create an API token at ${TOKEN_URL}`,\n ]);\n }\n\n return {\n baseUrl: normalizeBaseUrl(baseUrl),\n email: email.trim(),\n apiToken: apiToken.trim(),\n issuesJql: file.issuesJql?.trim() || DEFAULT_ISSUES_JQL,\n worklogDays:\n typeof file.worklogDays === 'number' && file.worklogDays > 0\n ? Math.floor(file.worklogDays)\n : DEFAULT_WORKLOG_DAYS,\n holidayCountry: normalizeCode(process.env['JIRA_HOLIDAY_COUNTRY'] ?? file.holidayCountry),\n holidayRegion: normalizeCode(process.env['JIRA_HOLIDAY_REGION'] ?? file.holidayRegion),\n };\n}\n", "export interface Config {\n baseUrl: string;\n email: string;\n apiToken: string;\n /** Overridable JQL for the open-tickets panel. */\n issuesJql: string;\n /** How many days of worklog history the left panel covers. */\n worklogDays: number;\n /** ISO-3166 alpha-2 country whose statutory holidays count as non-working days. */\n holidayCountry: string | null;\n /** Optional Nager subdivision code (e.g. \"US-CA\") to narrow regional holidays. */\n holidayRegion: string | null;\n}\n\nexport interface Issue {\n key: string;\n summary: string;\n status: string;\n statusCategory: string;\n issueType: string;\n priority: string | null;\n assignee: string | null;\n reporter: string | null;\n labels: string[];\n updated: string | null;\n description: string;\n url: string;\n projectName: string | null;\n /** Immediate parent (epic for a story, story for a subtask), if any. */\n parent: { key: string; summary: string; issueType: string } | null;\n /** Total time logged on the issue by everyone, per Jira's aggregate. */\n totalTimeSpentSeconds: number | null;\n /** Sprint names the issue belongs to, active ones first; empty if none. */\n sprints: string[];\n /** True when the issue came from the user's own JQL, false for a pulled-in ancestor. */\n mine: boolean;\n}\n\n/** One rendered line of the issue tree. */\nexport interface IssueRow {\n issue: Issue;\n /**\n * Ancestor guides, outermost first: `true` where that ancestor still has\n * siblings below it (draw `\u2502`), `false` where it was the last child.\n */\n guides: boolean[];\n /** Whether this node is the last child of its parent, for `\u2514` vs `\u251C`. */\n last: boolean;\n /** Whether the row has rows nested under it, for the folder marker. */\n hasChildren: boolean;\n}\n\nexport interface WorklogDay {\n /** Local calendar date, `YYYY-MM-DD`. */\n date: string;\n hours: number;\n issueKeys: string[];\n}\n\n/** One day's worth of the current user's work on a single issue. */\nexport interface IssueWorklogEntry {\n date: string;\n hours: number;\n}\n\nexport interface WorklogSummary {\n /** One row per day in the window, oldest first. */\n days: WorklogDay[];\n /** Per-issue breakdown within the same window, keyed by issue key. */\n byIssue: Record<string, IssueWorklogEntry[]>;\n /** How many days the window covers, for labelling. */\n windowDays: number;\n}\n\nexport interface CurrentUser {\n accountId: string;\n displayName: string;\n}\n\n/** An error carrying user-facing remediation advice. */\nexport class JiraError extends Error {\n readonly hints: string[];\n\n constructor(message: string, hints: string[] = []) {\n super(message);\n this.name = 'JiraError';\n this.hints = hints;\n }\n}\n\n/** Async resource with independent loading/error state per panel. */\nexport interface Loadable<T> {\n data: T | null;\n loading: boolean;\n error: JiraError | null;\n}\n", "/**\n * Minimal Atlassian Document Format \u2192 plain text renderer.\n *\n * Jira returns rich descriptions as an ADF node tree. We only need something\n * readable in a terminal pane, so unknown node types simply recurse into their\n * children rather than failing.\n */\n\ninterface AdfNode {\n type?: string;\n text?: string;\n content?: AdfNode[];\n attrs?: Record<string, unknown>;\n marks?: { type?: string; attrs?: Record<string, unknown> }[];\n}\n\nexport function adfToText(doc: unknown): string {\n if (typeof doc === 'string') return doc;\n if (typeof doc !== 'object' || doc === null) return '';\n return renderNodes((doc as AdfNode).content ?? [], 0)\n .join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nfunction renderNodes(nodes: AdfNode[], depth: number): string[] {\n return nodes.flatMap((node) => renderNode(node, depth));\n}\n\nfunction renderNode(node: AdfNode, depth: number): string[] {\n const indent = ' '.repeat(depth);\n\n switch (node.type) {\n case 'text':\n return [applyMarks(node)];\n\n case 'hardBreak':\n return [''];\n\n case 'paragraph':\n return [indent + inline(node.content ?? []), ''];\n\n case 'heading': {\n const level = Number(node.attrs?.['level'] ?? 1);\n return [`${'#'.repeat(Math.min(level, 6))} ${inline(node.content ?? [])}`, ''];\n }\n\n case 'bulletList':\n return [...listItems(node, depth, () => '\u2022'), ''];\n\n case 'orderedList':\n return [...listItems(node, depth, (i) => `${i + 1}.`), ''];\n\n case 'codeBlock': {\n const lang = typeof node.attrs?.['language'] === 'string' ? node.attrs['language'] : '';\n const body = inline(node.content ?? []).split('\\n');\n return [`${indent}\\`\\`\\`${lang}`, ...body.map((l) => indent + l), `${indent}\\`\\`\\``, ''];\n }\n\n case 'blockquote':\n return [...renderNodes(node.content ?? [], depth).map((l) => `${indent}> ${l}`), ''];\n\n case 'rule':\n return ['\u2500\u2500\u2500', ''];\n\n case 'mediaSingle':\n case 'mediaGroup':\n return [`${indent}[attachment]`];\n\n case 'table':\n // Rendering real table borders is out of scope; flatten rows instead.\n return [...renderNodes(node.content ?? [], depth), ''];\n\n case 'tableRow':\n return [indent + (node.content ?? []).map((cell) => inline(cell.content ?? [])).join(' | ')];\n\n default:\n return node.content ? renderNodes(node.content, depth) : [];\n }\n}\n\nfunction listItems(node: AdfNode, depth: number, bullet: (index: number) => string): string[] {\n const indent = ' '.repeat(depth);\n return (node.content ?? []).flatMap((item, index) => {\n const lines = renderNodes(item.content ?? [], depth + 1).filter((l) => l.trim() !== '');\n const [first = '', ...rest] = lines;\n return [`${indent}${bullet(index)} ${first.trim()}`, ...rest];\n });\n}\n\n/** Flatten a run of inline nodes onto a single line. */\nfunction inline(nodes: AdfNode[]): string {\n return nodes\n .map((node) => {\n switch (node.type) {\n case 'text':\n return applyMarks(node);\n case 'hardBreak':\n return '\\n';\n case 'mention':\n return attr(node, 'text') || '@unknown';\n case 'emoji':\n return attr(node, 'text') || attr(node, 'shortName');\n case 'inlineCard':\n return attr(node, 'url');\n case 'date':\n return attr(node, 'timestamp');\n case 'status':\n return `[${attr(node, 'text')}]`;\n default:\n return node.content ? inline(node.content) : '';\n }\n })\n .join('');\n}\n\n/** Read a node attribute as a plain string; non-scalar attrs are dropped. */\nfunction attr(node: AdfNode, name: string): string {\n const value = node.attrs?.[name];\n return typeof value === 'string' || typeof value === 'number' ? String(value) : '';\n}\n\n/** Only links carry information that would be lost in plain text. */\nfunction applyMarks(node: AdfNode): string {\n const text = node.text ?? '';\n const link = node.marks?.find((m) => m.type === 'link');\n const href = link?.attrs?.['href'];\n return typeof href === 'string' && href !== text ? `${text} (${href})` : text;\n}\n", "import type { JiraClient } from './client';\n\n/** Jira's schema id for the Scrum board sprint field, stable across sites. */\nconst SPRINT_SCHEMA = 'com.pyxis.greenhopper.jira:gh-sprint';\n\ninterface FieldMeta {\n id?: string;\n schema?: { custom?: string };\n}\n\n/** `undefined` = not looked up yet, `null` = looked up and unavailable. */\nlet cached: string | null | undefined;\n\n/**\n * Resolve the site's sprint custom-field id (e.g. `customfield_10020`).\n *\n * The id differs per Jira site, so it has to be discovered rather than\n * hard-coded. Resolved once per process; a failure is not fatal \u2014 the preview\n * just shows \"None\" rather than breaking the whole ticket list.\n */\nexport async function resolveSprintFieldId(client: JiraClient): Promise<string | null> {\n if (cached !== undefined) return cached;\n\n try {\n const fields = await client.get<FieldMeta[]>('/rest/api/3/field');\n cached = fields.find((field) => field.schema?.custom === SPRINT_SCHEMA)?.id ?? null;\n } catch {\n cached = null;\n }\n\n return cached;\n}\n\ninterface RawSprint {\n name?: string;\n state?: string;\n}\n\n/**\n * Sprint names for display, active ones first.\n *\n * The field is an array because an issue can span sprints. Older Jira versions\n * return stringified Java objects instead of JSON, so both shapes are handled.\n */\nexport function sprintNames(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n\n const sprints = value\n .map((entry): RawSprint | null => {\n if (typeof entry === 'string') return parseLegacySprint(entry);\n if (typeof entry === 'object' && entry !== null) return entry as RawSprint;\n return null;\n })\n .filter((sprint): sprint is RawSprint => Boolean(sprint?.name));\n\n // Active first, then future, then closed \u2014 the order a person cares about.\n const rank = (state?: string): number => {\n switch (state?.toLowerCase()) {\n case 'active':\n return 0;\n case 'future':\n return 1;\n default:\n return 2;\n }\n };\n\n return sprints\n .sort((a, b) => rank(a.state) - rank(b.state))\n .map((sprint) =>\n sprint.state && sprint.state.toLowerCase() !== 'active'\n ? `${sprint.name ?? ''} (${sprint.state.toLowerCase()})`\n : (sprint.name ?? ''),\n );\n}\n\n/** `...Sprint@1a2b[id=5,name=Sprint 7,state=ACTIVE,...]` \u2192 `{ name, state }`. */\nfunction parseLegacySprint(raw: string): RawSprint | null {\n const name = /\\bname=([^,\\]]+)/.exec(raw)?.[1];\n const state = /\\bstate=([^,\\]]+)/.exec(raw)?.[1];\n return name ? { name, state } : null;\n}\n", "import type { JiraClient } from './client';\nimport { adfToText } from './adf';\nimport { resolveSprintFieldId, sprintNames } from './sprintField';\nimport type { Config, Issue } from '../types';\n\nconst MAX_ISSUES = 100;\n\nconst FIELDS = [\n 'summary',\n 'status',\n 'priority',\n 'assignee',\n 'reporter',\n 'labels',\n 'updated',\n 'description',\n 'issuetype',\n 'parent',\n 'project',\n 'aggregatetimespent',\n 'timespent',\n];\n\ninterface RawIssue {\n key: string;\n fields?: {\n summary?: string;\n status?: { name?: string; statusCategory?: { name?: string } };\n priority?: { name?: string } | null;\n assignee?: { displayName?: string } | null;\n reporter?: { displayName?: string } | null;\n labels?: string[];\n updated?: string;\n description?: unknown;\n issuetype?: { name?: string };\n project?: { name?: string; key?: string };\n parent?: {\n key?: string;\n fields?: { summary?: string; issuetype?: { name?: string } };\n } | null;\n aggregatetimespent?: number | null;\n timespent?: number | null;\n /** The sprint custom field, whose id varies per site. */\n [customField: string]: unknown;\n };\n}\n\nexport interface SearchResponse {\n issues?: RawIssue[];\n}\n\n/** How many parent hops to follow when filling in ancestors (epic \u2192 story \u2192 subtask). */\nconst MAX_ANCESTOR_ROUNDS = 4;\n\n/**\n * Fetch the open tickets shown in the top-right panel, plus every ancestor\n * needed to place them in a tree. Ancestors are marked `mine: false` so the\n * default view can hide them.\n */\nexport async function fetchOpenIssues(client: JiraClient, config: Config): Promise<Issue[]> {\n const sprintFieldId = await resolveSprintFieldId(client);\n\n const response = await client.post<SearchResponse>('/rest/api/3/search/jql', {\n jql: config.issuesJql,\n maxResults: MAX_ISSUES,\n fields: sprintFieldId ? [...FIELDS, sprintFieldId] : FIELDS,\n });\n\n const issues = (response.issues ?? []).map((raw) => toIssue(raw, client, sprintFieldId, true));\n const ancestors = await fetchAncestors(client, issues, sprintFieldId);\n\n return [...issues, ...ancestors];\n}\n\n/** Walk the parent links upward, fetching each level that is not already loaded. */\nasync function fetchAncestors(\n client: JiraClient,\n issues: Issue[],\n sprintFieldId: string | null,\n): Promise<Issue[]> {\n const known = new Set(issues.map((issue) => issue.key));\n const collected: Issue[] = [];\n let frontier = issues;\n\n for (let round = 0; round < MAX_ANCESTOR_ROUNDS; round += 1) {\n const missing = [\n ...new Set(\n frontier\n .map((issue) => issue.parent?.key)\n .filter((key): key is string => Boolean(key) && !known.has(key as string)),\n ),\n ];\n if (missing.length === 0) break;\n\n for (const key of missing) known.add(key);\n const fetched = await fetchByKeys(client, missing, sprintFieldId);\n if (fetched.length === 0) break;\n\n collected.push(...fetched);\n frontier = fetched;\n }\n\n return collected;\n}\n\nasync function fetchByKeys(\n client: JiraClient,\n keys: string[],\n sprintFieldId: string | null,\n): Promise<Issue[]> {\n const response = await client.post<SearchResponse>('/rest/api/3/search/jql', {\n jql: `key in (${keys.join(',')})`,\n maxResults: keys.length,\n fields: sprintFieldId ? [...FIELDS, sprintFieldId] : FIELDS,\n });\n\n return (response.issues ?? []).map((raw) => toIssue(raw, client, sprintFieldId, false));\n}\n\nfunction toIssue(\n raw: RawIssue,\n client: JiraClient,\n sprintFieldId: string | null,\n mine: boolean,\n): Issue {\n const fields = raw.fields ?? {};\n return {\n key: raw.key,\n summary: fields.summary ?? '(no summary)',\n status: fields.status?.name ?? 'Unknown',\n statusCategory: fields.status?.statusCategory?.name ?? '',\n issueType: fields.issuetype?.name ?? '',\n priority: fields.priority?.name ?? null,\n assignee: fields.assignee?.displayName ?? null,\n reporter: fields.reporter?.displayName ?? null,\n labels: fields.labels ?? [],\n updated: fields.updated ?? null,\n description: adfToText(fields.description),\n url: client.issueUrl(raw.key),\n projectName: fields.project?.name ?? fields.project?.key ?? null,\n parent: fields.parent?.key\n ? {\n key: fields.parent.key,\n summary: fields.parent.fields?.summary ?? '',\n issueType: fields.parent.fields?.issuetype?.name ?? '',\n }\n : null,\n totalTimeSpentSeconds: fields.aggregatetimespent ?? fields.timespent ?? null,\n sprints: sprintFieldId ? sprintNames(fields[sprintFieldId]) : [],\n mine,\n };\n}\n", "import type { JiraClient } from './client';\nimport type { Config, CurrentUser, IssueWorklogEntry, WorklogSummary } from '../types';\nimport type { SearchResponse } from './issues';\n\nconst WORKLOG_PAGE_SIZE = 1000;\nconst CONCURRENCY = 5;\n\ninterface RawWorklog {\n author?: { accountId?: string };\n started?: string;\n timeSpentSeconds?: number;\n}\n\ninterface WorklogResponse {\n worklogs?: RawWorklog[];\n}\n\n/** Local `YYYY-MM-DD` key \u2014 worklog days are calendar days in the user's timezone. */\nexport function localDateKey(date: Date): string {\n const month = String(date.getMonth() + 1).padStart(2, '0');\n const day = String(date.getDate()).padStart(2, '0');\n return `${date.getFullYear()}-${month}-${day}`;\n}\n\n/** Midnight local time, `daysAgo` days back from today. */\nfunction startOfDay(daysAgo: number): Date {\n const date = new Date();\n date.setHours(0, 0, 0, 0);\n date.setDate(date.getDate() - daysAgo);\n return date;\n}\n\n/**\n * Aggregate the current user's worklogs over the last `config.worklogDays` days\n * two ways: one row per day (oldest first, zero-hour days included so the panel\n * shows a complete window) and a per-issue breakdown for the preview panel.\n */\nexport async function fetchWorklogDays(\n client: JiraClient,\n config: Config,\n me: CurrentUser,\n): Promise<WorklogSummary> {\n const days = config.worklogDays;\n const windowStart = startOfDay(days - 1);\n\n const search = await client.post<SearchResponse>('/rest/api/3/search/jql', {\n jql: `worklogAuthor = currentUser() AND worklogDate >= -${days}d ORDER BY updated DESC`,\n maxResults: 200,\n fields: ['summary'],\n });\n\n const keys = (search.issues ?? []).map((issue) => issue.key);\n\n // Pre-seed every day in the window so gaps render as 0.0h.\n const buckets = new Map<string, { seconds: number; keys: Set<string> }>();\n for (let i = 0; i < days; i += 1) {\n buckets.set(localDateKey(startOfDay(i)), { seconds: 0, keys: new Set() });\n }\n\n // Seconds logged per issue per day: issue key \u2192 date \u2192 seconds.\n const perIssue = new Map<string, Map<string, number>>();\n\n const startedAfter = String(windowStart.getTime());\n await forEachLimited(keys, CONCURRENCY, async (key) => {\n const response = await client.get<WorklogResponse>(`/rest/api/3/issue/${key}/worklog`, {\n startedAfter,\n maxResults: String(WORKLOG_PAGE_SIZE),\n });\n\n for (const entry of response.worklogs ?? []) {\n if (entry.author?.accountId !== me.accountId || !entry.started) continue;\n\n const started = new Date(entry.started);\n if (Number.isNaN(started.getTime()) || started < windowStart) continue;\n\n const date = localDateKey(started);\n const bucket = buckets.get(date);\n if (!bucket) continue; // A worklog dated in the future.\n\n const seconds = entry.timeSpentSeconds ?? 0;\n bucket.seconds += seconds;\n bucket.keys.add(key);\n\n const issueDays = perIssue.get(key) ?? new Map<string, number>();\n issueDays.set(date, (issueDays.get(date) ?? 0) + seconds);\n perIssue.set(key, issueDays);\n }\n });\n\n const byIssue: Record<string, IssueWorklogEntry[]> = {};\n for (const [key, issueDays] of perIssue) {\n byIssue[key] = Array.from(issueDays.entries())\n .map(([date, seconds]) => ({ date, hours: seconds / 3600 }))\n .sort((a, b) => a.date.localeCompare(b.date));\n }\n\n return {\n days: Array.from(buckets.entries())\n .map(([date, { seconds, keys: issueKeys }]) => ({\n date,\n hours: seconds / 3600,\n issueKeys: Array.from(issueKeys).sort(compareIssueKeys),\n }))\n .sort((a, b) => a.date.localeCompare(b.date)),\n byIssue,\n windowDays: days,\n };\n}\n\n/** Sort by project then numeric part, so MR-2 precedes MR-10. */\nfunction compareIssueKeys(a: string, b: string): number {\n const [aProject = a, aNumber = '0'] = a.split('-');\n const [bProject = b, bNumber = '0'] = b.split('-');\n return aProject === bProject\n ? Number(aNumber) - Number(bNumber)\n : aProject.localeCompare(bProject);\n}\n\n/** Run `worker` over `items` with at most `limit` requests in flight. */\nasync function forEachLimited<T>(\n items: T[],\n limit: number,\n worker: (item: T) => Promise<void>,\n): Promise<void> {\n let cursor = 0;\n const runners = Array.from({ length: Math.min(limit, items.length) }, async () => {\n while (cursor < items.length) {\n const item = items[cursor];\n cursor += 1;\n if (item !== undefined) await worker(item);\n }\n });\n await Promise.all(runners);\n}\n", "import { useEffect, useRef } from 'react';\nimport { useStdin } from 'ink';\n\n/**\n * Built from a char code on purpose: a literal escape byte in source is\n * invisible and easy to lose to an editor or a copy-paste.\n */\nconst ESC = String.fromCharCode(27);\n\n/**\n * Home and End have no single encoding \u2014 xterm, VT and rxvt families each pick\n * a different one, and terminals vary in application vs normal cursor mode.\n */\nconst HOME_SEQUENCES = [`${ESC}[H`, `${ESC}[1~`, `${ESC}[7~`, `${ESC}OH`];\nconst END_SEQUENCES = [`${ESC}[F`, `${ESC}[4~`, `${ESC}[8~`, `${ESC}OF`];\n\n/**\n * Report Home and End key presses.\n *\n * Ink's `useInput` cannot help here: it parses all of these into an empty input\n * with no key flags set, making Home and End indistinguishable. Reading stdin\n * directly is the only way to tell them apart. Ink still sees the same bytes and\n * emits its own (harmless) empty events.\n */\nexport function useHomeEndKeys(onHome: () => void, onEnd: () => void): void {\n const { stdin, isRawModeSupported } = useStdin();\n\n // Handlers change on nearly every render; a ref keeps the listener stable.\n const handlers = useRef({ onHome, onEnd });\n handlers.current = { onHome, onEnd };\n\n useEffect(() => {\n if (!isRawModeSupported) return;\n\n const onData = (data: Buffer | string): void => {\n const text = typeof data === 'string' ? data : data.toString('utf8');\n if (HOME_SEQUENCES.some((sequence) => text.includes(sequence))) {\n handlers.current.onHome();\n } else if (END_SEQUENCES.some((sequence) => text.includes(sequence))) {\n handlers.current.onEnd();\n }\n };\n\n stdin.on('data', onData);\n return () => {\n stdin.off('data', onData);\n };\n }, [stdin, isRawModeSupported]);\n}\n", "import { useEffect } from 'react';\nimport { useStdin, useStdout } from 'ink';\n\n/**\n * Built from a char code on purpose: a literal escape byte in source is\n * invisible and easy to lose to an editor or a copy-paste.\n */\nconst ESC = String.fromCharCode(27);\n\n/** SGR mouse reporting: button presses + wheel, with unbounded coordinates. */\nconst MOUSE_ON = `${ESC}[?1000h${ESC}[?1006h`;\nconst MOUSE_OFF = `${ESC}[?1006l${ESC}[?1000l`;\n\n/** `ESC [ < button ; col ; row (M|m)` \u2014 M is press, m is release. */\nconst SGR_MOUSE = new RegExp(`${ESC}\\\\[<(\\\\d+);(\\\\d+);(\\\\d+)([Mm])`, 'g');\nconst SGR_PREFIX = `${ESC}[<`;\n\nexport type MouseEvent =\n | { type: 'press'; column: number; row: number }\n | { type: 'wheel'; direction: 'up' | 'down'; column: number; row: number };\n\n/**\n * True when `input` is (or contains) an SGR mouse report. Keyboard handlers\n * should ignore these, since Ink's key parser sees the same bytes.\n */\nexport function isMouseSequence(input: string): boolean {\n return input.includes(SGR_PREFIX);\n}\n\n/**\n * Enable SGR mouse reporting for the lifetime of the component and report\n * parsed events. Terminal state is restored on unmount and on abrupt exits.\n */\nexport function useMouse(onEvent: (event: MouseEvent) => void): void {\n const { stdin, isRawModeSupported } = useStdin();\n const { stdout } = useStdout();\n\n useEffect(() => {\n if (!isRawModeSupported) return;\n\n stdout.write(MOUSE_ON);\n const disable = (): void => {\n stdout.write(MOUSE_OFF);\n };\n // Ink's own cleanup does not know about mouse mode, so cover hard exits.\n process.on('exit', disable);\n\n const onData = (data: Buffer | string): void => {\n const text = typeof data === 'string' ? data : data.toString('utf8');\n SGR_MOUSE.lastIndex = 0;\n\n for (let match = SGR_MOUSE.exec(text); match; match = SGR_MOUSE.exec(text)) {\n const [, rawButton = '0', rawColumn = '1', rawRow = '1', kind] = match;\n if (kind !== 'M') continue; // Ignore releases.\n\n const button = Number(rawButton);\n const column = Number(rawColumn) - 1;\n const row = Number(rawRow) - 1;\n\n // Bit 6 (64) marks wheel events; the low bit gives the direction.\n if (button & 64) {\n onEvent({ type: 'wheel', direction: (button & 1) === 0 ? 'up' : 'down', column, row });\n } else if ((button & 3) === 0) {\n onEvent({ type: 'press', column, row });\n }\n }\n };\n\n stdin.on('data', onData);\n return () => {\n stdin.off('data', onData);\n process.off('exit', disable);\n disable();\n };\n }, [stdin, stdout, isRawModeSupported, onEvent]);\n}\n", "import { useEffect, useState } from 'react';\nimport { useStdout } from 'ink';\n\nexport interface TerminalSize {\n columns: number;\n rows: number;\n}\n\n/** Track the terminal dimensions so the layout fills the window on resize. */\nexport function useTerminalSize(): TerminalSize {\n const { stdout } = useStdout();\n const [size, setSize] = useState<TerminalSize>(() => ({\n columns: stdout.columns || 80,\n rows: stdout.rows || 24,\n }));\n\n useEffect(() => {\n const onResize = (): void => {\n setSize({ columns: stdout.columns || 80, rows: stdout.rows || 24 });\n };\n\n stdout.on('resize', onResize);\n onResize();\n return () => {\n stdout.off('resize', onResize);\n };\n }, [stdout]);\n\n return size;\n}\n", "/** Columns a splitter moves per Alt+\u2190/\u2192 press. */\nexport const SPLIT_STEP = 2;\n\n/** Neither side of a splitter is shrunk below this by manual resizing. */\nexport const MIN_PANE_WIDTH = 20;\n\n/**\n * Clamp a left-pane width so both panes keep `MIN_PANE_WIDTH`.\n *\n * A terminal too narrow to honour both minimums is simply split in half.\n */\nexport function clampSplit(width: number, total: number): number {\n if (total < 2 * MIN_PANE_WIDTH) return Math.max(0, Math.floor(total / 2));\n return Math.max(MIN_PANE_WIDTH, Math.min(width, total - MIN_PANE_WIDTH));\n}\n\n/**\n * Nudge a splitter stored as a shift from its default position (`base`).\n *\n * The shift is re-based on the clamped width, so presses past a limit don't pile\n * up an invisible debt that has to be unwound before the splitter moves back.\n * Keeping a shift rather than an absolute width lets the adjustment follow the\n * default when the terminal is resized.\n */\nexport function nudgeSplit(shift: number, delta: number, base: number, total: number): number {\n return clampSplit(base + shift + delta, total) - base;\n}\n", "import { spawn } from 'node:child_process';\n\n/** Platform command that hands a URL to the user's default browser. */\nfunction opener(): { command: string; args: string[] } {\n switch (process.platform) {\n case 'darwin':\n return { command: 'open', args: [] };\n case 'win32':\n // `start` is a cmd builtin; the empty string is the (ignored) window title.\n return { command: 'cmd', args: ['/c', 'start', ''] };\n default:\n return { command: 'xdg-open', args: [] };\n }\n}\n\n/**\n * Open `url` in the default browser.\n *\n * The child is detached and its streams are discarded, so a browser writing to\n * stderr cannot corrupt the TUI. Resolves once the process has been spawned;\n * rejects if the opener is missing or exits non-zero straight away.\n */\nexport async function openUrl(url: string): Promise<void> {\n const { command, args } = opener();\n\n return new Promise((resolve, reject) => {\n // Arguments are passed as an array, so the URL is never shell-interpreted.\n const child = spawn(command, [...args, url], {\n detached: true,\n stdio: 'ignore',\n });\n\n child.once('error', (cause: NodeJS.ErrnoException) => {\n reject(\n new Error(\n cause.code === 'ENOENT'\n ? `${command} is not installed, so the browser could not be opened.`\n : `${command} failed: ${cause.message}`,\n ),\n );\n });\n\n child.once('spawn', () => {\n child.unref(); // Let jdb exit without waiting for the browser.\n resolve();\n });\n });\n}\n", "import { JiraError, type Config, type CurrentUser } from '../types';\n\nconst REQUEST_TIMEOUT_MS = 20_000;\n\n/** Thin authenticated wrapper around the Jira Cloud REST API v3. */\nexport class JiraClient {\n private readonly baseUrl: string;\n private readonly authHeader: string;\n\n constructor(config: Config) {\n this.baseUrl = config.baseUrl;\n this.authHeader = `Basic ${Buffer.from(`${config.email}:${config.apiToken}`).toString('base64')}`;\n }\n\n /** Browser URL for an issue, for display in the preview panel. */\n issueUrl(key: string): string {\n return `${this.baseUrl}/browse/${key}`;\n }\n\n async get<T>(path: string, params?: Record<string, string>): Promise<T> {\n const url = new URL(`${this.baseUrl}${path}`);\n for (const [key, value] of Object.entries(params ?? {})) {\n url.searchParams.set(key, value);\n }\n return this.request<T>('GET', url.toString());\n }\n\n async post<T>(path: string, body: unknown): Promise<T> {\n return this.request<T>('POST', `${this.baseUrl}${path}`, body);\n }\n\n private async request<T>(method: string, url: string, body?: unknown): Promise<T> {\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers: {\n Authorization: this.authHeader,\n Accept: 'application/json',\n ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),\n },\n ...(body === undefined ? {} : { body: JSON.stringify(body) }),\n signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),\n });\n } catch (cause) {\n throw asNetworkError(cause, this.baseUrl);\n }\n\n if (!response.ok) {\n throw await asHttpError(response, url);\n }\n\n return (await response.json()) as T;\n }\n\n /** Identify the caller; doubles as the credential check at startup. */\n async myself(): Promise<CurrentUser> {\n const me = await this.get<{ accountId: string; displayName: string }>('/rest/api/3/myself');\n return { accountId: me.accountId, displayName: me.displayName };\n }\n}\n\nfunction asNetworkError(cause: unknown, baseUrl: string): JiraError {\n const name = cause instanceof Error ? cause.name : '';\n if (name === 'TimeoutError' || name === 'AbortError') {\n return new JiraError('Jira did not respond within 20s.', [\n 'Check your network connection or VPN, then press r to retry.',\n ]);\n }\n return new JiraError(`Could not reach ${baseUrl}.`, [\n cause instanceof Error ? cause.message : String(cause),\n 'Verify JIRA_BASE_URL points at your Atlassian site.',\n ]);\n}\n\nasync function asHttpError(response: Response, url: string): Promise<JiraError> {\n const detail = await readErrorDetail(response);\n\n switch (response.status) {\n case 401:\n return new JiraError('Jira rejected your credentials (401).', [\n 'JIRA_EMAIL must be your Atlassian account email and JIRA_API_TOKEN an API token',\n '(not your password). Tokens: https://id.atlassian.com/manage-profile/security/api-tokens',\n ]);\n case 403:\n return new JiraError('Jira denied access (403).', [\n detail || 'Your account may lack permission for this project.',\n ]);\n case 400:\n return new JiraError('Jira rejected the request (400).', [\n detail || 'The JQL query may be invalid for this site.',\n 'Adjust \"issuesJql\" in your config file.',\n ]);\n case 429:\n return new JiraError('Jira rate-limited this client (429).', [\n 'Wait a moment, then press r to retry.',\n ]);\n default:\n return new JiraError(`Jira returned ${response.status} for ${new URL(url).pathname}.`, [\n detail || response.statusText,\n ]);\n }\n}\n\n/** Jira reports failures as `errorMessages` and/or a field-keyed `errors` map. */\nasync function readErrorDetail(response: Response): Promise<string> {\n try {\n const body: unknown = await response.json();\n if (typeof body !== 'object' || body === null) return '';\n const { errorMessages, errors } = body as {\n errorMessages?: unknown;\n errors?: Record<string, unknown>;\n };\n\n const parts: string[] = [];\n if (Array.isArray(errorMessages)) parts.push(...errorMessages.map(String));\n if (errors && typeof errors === 'object') {\n parts.push(...Object.entries(errors).map(([k, v]) => `${k}: ${String(v)}`));\n }\n return parts.join('; ');\n } catch {\n return '';\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["Box", "Text", "useEffect", "useRef", "useState", "Box", "Text", "Box", "Text", "jsx", "jsxs", "MARKS", "BY_NAME", "jsx", "jsxs", "Text", "Box", "Box", "Text", "jsx", "jsxs", "Box", "Text", "Box", "Text", "MONTHS", "jsx", "jsxs", "Text", "Box", "MONTHS", "Box", "Text", "readFileSync", "join", "join", "cached", "readFileSync", "version", "jsx", "jsxs", "Box", "Text", "useEffect", "useStdin", "ESC", "useEffect", "useStdout", "jsx", "jsxs", "useState", "useEffect", "useRef", "Box", "Text", "REQUEST_TIMEOUT_MS", "jsx", "require", "ESC"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jira-dashboard",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Full-screen terminal dashboard for Jira: 14-day worklog summary and your open tickets",
5
5
  "type": "module",
6
6
  "bin": {