jira-dashboard 0.1.4 → 0.1.6
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 +12 -6
- package/dist/cli.js +42 -6
- package/dist/cli.js.map +3 -3
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ 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 a Jira-style priority arrow: `⇈` highest, `↑` high,
|
|
26
|
+
`=` medium, `↓` low, `⇊` lowest, `‼` critical/blocker. Priorities the site names differently are
|
|
27
|
+
left blank.
|
|
26
28
|
- **Bottom left** — bar chart of the hours *you* logged on the **selected** ticket, day by day over
|
|
27
29
|
the same 14 days. The scale is fixed so bars are comparable between tickets and between runs: 2h is
|
|
28
30
|
half a row, 4h one row, 8h two rows; anything logged below 2h gets a thin mark so it stays visible.
|
|
@@ -75,14 +77,18 @@ year is refreshed after 30 days. Both keys can be overridden with `JIRA_HOLIDAY_
|
|
|
75
77
|
regardless.
|
|
76
78
|
|
|
77
79
|
Many holidays are provincial or state-level rather than nationwide — Canada's Civic Holiday on
|
|
78
|
-
the first Monday of August, for instance.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
the first Monday of August, for instance. Set `holidayRegion` (`"CA-ON"`, `"US-CA"`, …) and the
|
|
81
|
+
ones covering your region become full days off, while the rest are hidden: a Yukon holiday is
|
|
82
|
+
not your day off in Ontario.
|
|
83
|
+
|
|
84
|
+
Leave `holidayRegion` unset and there is no way to tell which are yours, so they are **shown
|
|
85
|
+
rather than skipped** — dimmed and marked `~` instead of `*`, listing the regions that observe
|
|
86
|
+
them; those do not excuse an empty timesheet:
|
|
82
87
|
|
|
83
88
|
```
|
|
84
89
|
Mon 03 Aug~🎉 0.0h (Civic Holiday / British Columbia Day … · CA-ON, …) # region unset
|
|
85
90
|
Mon 03 Aug*🎉 0.0h (Civic Holiday) # holidayRegion CA-ON
|
|
91
|
+
Mon 17 Aug 7.5h JD-14, JD-22 # CA-YT only: hidden
|
|
86
92
|
```
|
|
87
93
|
|
|
88
94
|
Personal leave goes in `~/.config/jira-dashboard/days-off.json`, which you maintain by hand:
|
|
@@ -92,7 +98,7 @@ Personal leave goes in `~/.config/jira-dashboard/days-off.json`, which you maint
|
|
|
92
98
|
```
|
|
93
99
|
|
|
94
100
|
Both kinds of day show up in magenta in the two left panels — holidays marked `*🎉`, days off `·`,
|
|
95
|
-
holidays observed only in other regions dimmed and marked `~🎉` — and an empty day off is never
|
|
101
|
+
holidays observed only in other regions dimmed and marked `~🎉` when no region is set — and an empty day off is never
|
|
96
102
|
coloured red, so what is left in red is what still needs hours.
|
|
97
103
|
|
|
98
104
|
## Keys
|
package/dist/cli.js
CHANGED
|
@@ -69,6 +69,25 @@ function PanelError({ error }) {
|
|
|
69
69
|
] });
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
// src/priority.ts
|
|
73
|
+
var PRIORITY_WIDTH = 2;
|
|
74
|
+
var MARKS = [
|
|
75
|
+
{ mark: { glyph: "\u203C", color: "red", bold: true }, names: ["blocker", "critical", "urgent"] },
|
|
76
|
+
{ mark: { glyph: "\u21C8", color: "red", bold: true }, names: ["highest", "p1"] },
|
|
77
|
+
{ mark: { glyph: "\u2191", color: "red", bold: false }, names: ["high", "major", "p2"] },
|
|
78
|
+
{ mark: { glyph: "=", color: "yellow", bold: false }, names: ["medium", "normal", "p3"] },
|
|
79
|
+
{ mark: { glyph: "\u2193", color: "blue", bold: false }, names: ["low", "minor", "p4"] },
|
|
80
|
+
{ mark: { glyph: "\u21CA", color: "blue", bold: false }, names: ["lowest", "trivial", "p5"] }
|
|
81
|
+
];
|
|
82
|
+
var BY_NAME = new Map(
|
|
83
|
+
MARKS.flatMap(({ mark, names }) => names.map((name) => [name, mark]))
|
|
84
|
+
);
|
|
85
|
+
function priorityMark(priority) {
|
|
86
|
+
if (!priority) return null;
|
|
87
|
+
return BY_NAME.get(priority.trim().toLowerCase()) ?? null;
|
|
88
|
+
}
|
|
89
|
+
var PRIORITY_GLYPHS = MARKS.map(({ mark }) => mark.glyph);
|
|
90
|
+
|
|
72
91
|
// src/tree.ts
|
|
73
92
|
function buildIssueTree(issues, full) {
|
|
74
93
|
const byKey = new Map(issues.map((issue) => [issue.key, issue]));
|
|
@@ -152,7 +171,10 @@ function IssueList({
|
|
|
152
171
|
Math.max(0, Math.floor(width / 4)),
|
|
153
172
|
rows.reduce((widest, row) => Math.max(widest, treePrefix(row).length + 2), 0)
|
|
154
173
|
);
|
|
155
|
-
const summaryWidth = Math.max(
|
|
174
|
+
const summaryWidth = Math.max(
|
|
175
|
+
8,
|
|
176
|
+
width - 4 - 2 - gutterWidth - PRIORITY_WIDTH - KEY_WIDTH - STATUS_WIDTH
|
|
177
|
+
);
|
|
156
178
|
const label = fullTree ? "my tickets \xB7 full tree" : "my open tickets";
|
|
157
179
|
const base = rows.length > visibleRows ? `${label} (${selectedIndex + 1}/${rows.length})` : `${label} (${rows.length})`;
|
|
158
180
|
const title = state.loading && issues.length > 0 ? `${spinnerChar(spinnerFrame)} ${base}` : base;
|
|
@@ -161,9 +183,19 @@ function IssueList({
|
|
|
161
183
|
const selected = offset + index === selectedIndex;
|
|
162
184
|
const background = selected ? focused ? "cyan" : "blue" : void 0;
|
|
163
185
|
const cellColor = (own) => selected ? "black" : own;
|
|
186
|
+
const priority = priorityMark(issue.priority);
|
|
164
187
|
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
165
188
|
/* @__PURE__ */ jsx3(Text3, { color: cellColor("cyan"), backgroundColor: background, children: selected ? "\u25B8 " : " " }),
|
|
166
189
|
gutterWidth > 0 ? /* @__PURE__ */ jsx3(Text3, { color: cellColor("gray"), backgroundColor: background, children: fit(`${treePrefix(row)}${folderMark(row)} `, gutterWidth) }) : null,
|
|
190
|
+
/* @__PURE__ */ jsx3(
|
|
191
|
+
Text3,
|
|
192
|
+
{
|
|
193
|
+
bold: priority?.bold,
|
|
194
|
+
color: priority ? cellColor(priority.color) : void 0,
|
|
195
|
+
backgroundColor: background,
|
|
196
|
+
children: fit(priority ? priority.glyph : "", PRIORITY_WIDTH)
|
|
197
|
+
}
|
|
198
|
+
),
|
|
167
199
|
/* @__PURE__ */ jsx3(
|
|
168
200
|
Text3,
|
|
169
201
|
{
|
|
@@ -548,15 +580,19 @@ function isNonWorkingDay(key, marks) {
|
|
|
548
580
|
var CACHE_VERSION = 2;
|
|
549
581
|
var DATE_KEY = /^\d{4}-\d{2}-\d{2}$/;
|
|
550
582
|
async function loadDayMarks(config, years) {
|
|
583
|
+
return buildDayMarks(await loadHolidays(config, years), config.holidayRegion, readDaysOff());
|
|
584
|
+
}
|
|
585
|
+
function buildDayMarks(holidays, region, daysOff) {
|
|
551
586
|
const marks = /* @__PURE__ */ new Map();
|
|
552
587
|
const regional = /* @__PURE__ */ new Map();
|
|
553
|
-
for (const holiday of
|
|
588
|
+
for (const holiday of holidays) {
|
|
554
589
|
const counties = holiday.counties ?? [];
|
|
555
|
-
const observed = counties.length === 0 ||
|
|
590
|
+
const observed = counties.length === 0 || region !== null && counties.includes(region);
|
|
556
591
|
if (observed) {
|
|
557
592
|
marks.set(holiday.date, { kind: "holiday", label: holiday.name, observed: true });
|
|
558
593
|
continue;
|
|
559
594
|
}
|
|
595
|
+
if (region !== null) continue;
|
|
560
596
|
const group = regional.get(holiday.date) ?? { names: [], counties: [] };
|
|
561
597
|
if (!group.names.includes(holiday.name)) group.names.push(holiday.name);
|
|
562
598
|
for (const county of counties) if (!group.counties.includes(county)) group.counties.push(county);
|
|
@@ -570,7 +606,7 @@ async function loadDayMarks(config, years) {
|
|
|
570
606
|
observed: false
|
|
571
607
|
});
|
|
572
608
|
}
|
|
573
|
-
for (const [date, label] of
|
|
609
|
+
for (const [date, label] of daysOff) {
|
|
574
610
|
marks.set(date, { kind: "dayoff", label, observed: true });
|
|
575
611
|
}
|
|
576
612
|
return marks;
|
|
@@ -1614,8 +1650,8 @@ var HELP = `
|
|
|
1614
1650
|
JIRA_EMAIL your Atlassian account email
|
|
1615
1651
|
JIRA_API_TOKEN https://id.atlassian.com/manage-profile/security/api-tokens
|
|
1616
1652
|
JIRA_HOLIDAY_COUNTRY ISO country code for statutory holidays, e.g. UA
|
|
1617
|
-
JIRA_HOLIDAY_REGION your subdivision, e.g. CA-ON \u2014 regional holidays
|
|
1618
|
-
|
|
1653
|
+
JIRA_HOLIDAY_REGION your subdivision, e.g. CA-ON \u2014 regional holidays then
|
|
1654
|
+
count as days off, and other regions' are hidden
|
|
1619
1655
|
|
|
1620
1656
|
Or ${CONFIG_PATH}:
|
|
1621
1657
|
{
|
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 are\n shown either way, but only count as days off with it\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*. A holiday only some regions\n * observe \u2014 Canada's Civic Holiday, say \u2014 is still worth showing, but it does\n * not excuse an empty timesheet unless it covers your configured region.\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\ninterface 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 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 await loadHolidays(config, years)) {\n const counties = holiday.counties ?? [];\n const observed =\n counties.length === 0 ||\n (config.holidayRegion !== null && counties.includes(config.holidayRegion));\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 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 readDaysOff()) {\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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",
|
|
3
|
+
"sources": ["../src/cli.tsx", "../src/app.tsx", "../src/components/IssueList.tsx", "../src/components/PanelFrame.tsx", "../src/components/PanelStatus.tsx", "../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/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 { 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 - 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 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 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 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", "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
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"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jira-dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Full-screen terminal dashboard for Jira: 14-day worklog summary and your open tickets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"dev": "tsx src/cli.tsx",
|
|
18
|
+
"test": "jest",
|
|
18
19
|
"build": "esbuild src/cli.tsx --bundle --outfile=dist/cli.js --platform=node --format=esm --target=node20 --packages=external --sourcemap && chmod +x dist/cli.js",
|
|
19
|
-
"publish-check": "npm run lint && npm run typecheck && npm run build",
|
|
20
|
+
"publish-check": "npm run lint && npm run typecheck && npm test && npm run build",
|
|
20
21
|
"prepublishOnly": "npm run publish-check",
|
|
21
22
|
"typecheck": "tsc --noEmit",
|
|
22
23
|
"lint": "eslint .",
|
|
@@ -46,13 +47,17 @@
|
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@eslint/js": "^9.17.0",
|
|
50
|
+
"@jest/globals": "^30.4.1",
|
|
51
|
+
"@types/jest": "^30.0.0",
|
|
49
52
|
"@types/node": "^22.10.2",
|
|
50
53
|
"@types/react": "^18.3.18",
|
|
51
54
|
"esbuild": "^0.28.1",
|
|
52
55
|
"eslint": "^9.17.0",
|
|
53
56
|
"eslint-plugin-react": "^7.37.3",
|
|
54
57
|
"eslint-plugin-react-hooks": "^5.1.0",
|
|
58
|
+
"jest": "^30.4.2",
|
|
55
59
|
"lint-staged": "^15.3.0",
|
|
60
|
+
"ts-jest": "^29.4.12",
|
|
56
61
|
"tsx": "^4.19.2",
|
|
57
62
|
"typescript": "^5.7.2",
|
|
58
63
|
"typescript-eslint": "^8.19.0"
|