@kud/gh-ink 0.74.0 → 0.75.1
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 +25 -0
- package/dist/index.d.ts +140 -4
- package/dist/index.js +190 -12
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -61,6 +61,31 @@ codepoints; sibling host verbs should match them rather than invent neighbours:
|
|
|
61
61
|
| Unsubscribe / Mute / Remove reviewer | U+F478 / U+F466 / U+F468 |
|
|
62
62
|
| Close PR / and delete / issue | U+F4DC / U+F48E / U+F41D |
|
|
63
63
|
|
|
64
|
+
## Inbox focus slot and tab icons
|
|
65
|
+
|
|
66
|
+
`App` draws one standing row directly above the tab strip for whatever the
|
|
67
|
+
reader should look at next. The fetch result carries it as
|
|
68
|
+
`focus?: FocusSlot | null`: absent means the host has no focus feature and no
|
|
69
|
+
row is drawn; `null` means nothing to point at and draws the dim empty line.
|
|
70
|
+
It rides the fetch (and the cache beside the rows) because it goes stale the
|
|
71
|
+
same way they do.
|
|
72
|
+
|
|
73
|
+
| Prop / field | What |
|
|
74
|
+
| ------------------ | --------------------------------------------------------------------------------------------------- |
|
|
75
|
+
| `focusLabel` | What to call the row (default `focus`). Passing this or `focusEmpty` reserves the row from first paint. |
|
|
76
|
+
| `focusEmpty` | The empty sentence, drawn dim after the label when focus comes back `null`. |
|
|
77
|
+
| `FocusSlot` | `label`, `ref` (a ticket key, or a PR-style `#427`), `title`, `reason`, optional `marker` (the host's priority glyph), `mark`/`markColor`/`refColor` overrides, and `target: { tab, url }`. |
|
|
78
|
+
| `g` | Jumps to `focus.target`: switches to that tab and lands the cursor on that row, opening a collapsed tail it is folded into. Listed in the `?` legend only while a focus is on screen. |
|
|
79
|
+
|
|
80
|
+
A row whose url matches the target draws the mark in a fixed gutter between
|
|
81
|
+
the tree glyph and the marker cell; every other row holds two blanks there, so
|
|
82
|
+
the mark moves nothing sideways. Without a focus the rows draw what they
|
|
83
|
+
always drew.
|
|
84
|
+
|
|
85
|
+
Tabs take an optional `Section.icon`, passed through to ink-ui's
|
|
86
|
+
`TabItem.icon`: one glyph naming the tab beside its label, which is also what
|
|
87
|
+
an inactive tab folds down to when the strip does not fit its width.
|
|
88
|
+
|
|
64
89
|
## Development
|
|
65
90
|
|
|
66
91
|
```sh
|
package/dist/index.d.ts
CHANGED
|
@@ -179,6 +179,101 @@ declare const SidePanel: ({ sidebar, liveLabel, width, height, focused, cursor,
|
|
|
179
179
|
cursor?: number;
|
|
180
180
|
}) => React.JSX.Element;
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* The one thing the reader should look at next, in the host's words.
|
|
184
|
+
*
|
|
185
|
+
* Data, not vocabulary: `label` is the host's word for the row ("focus"), but
|
|
186
|
+
* everything else arrived over the wire — which ticket, what it says, where it
|
|
187
|
+
* lives — and goes stale the same way the rows do. That is why it rides the
|
|
188
|
+
* fetch result (and the cache beside the rows) rather than arriving as props.
|
|
189
|
+
*
|
|
190
|
+
* `undefined` (the field absent) means the host has no focus feature and draws
|
|
191
|
+
* no row at all; `null` means there is one and it currently has nothing to
|
|
192
|
+
* point at, which draws the dim empty line. The distinction matters because a
|
|
193
|
+
* host that never wired the feature must keep byte-identical output, while a
|
|
194
|
+
* host whose feature came back empty still owes the reader one line saying so.
|
|
195
|
+
*/
|
|
196
|
+
type FocusSlot = {
|
|
197
|
+
/** The mark in front of the row, accent by default. */
|
|
198
|
+
mark?: string;
|
|
199
|
+
/** The mark's colour. Defaults to the accent. */
|
|
200
|
+
markColor?: string;
|
|
201
|
+
/** What the row is a slot OF, in the host's words: `focus`, say. */
|
|
202
|
+
label: string;
|
|
203
|
+
/** The thing to look at: a ticket key, or a PR-style `#427`. */
|
|
204
|
+
ref: string;
|
|
205
|
+
/** The ref's colour. A `#` ref takes the accent, anything else the ticket. */
|
|
206
|
+
refColor?: string;
|
|
207
|
+
/** The row's own title, bold. Truncated to keep the line to one row. */
|
|
208
|
+
title: string;
|
|
209
|
+
/** Why this is the focus, dim, three spaces after the title. */
|
|
210
|
+
reason: string;
|
|
211
|
+
/** The host's priority glyph for the row, drawn in the default foreground. */
|
|
212
|
+
marker?: string;
|
|
213
|
+
/** Where `g` goes: the tab id and the row url. */
|
|
214
|
+
target: {
|
|
215
|
+
tab: string;
|
|
216
|
+
url: string;
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
declare const FOCUS_MARK = "\uDB81\uDCFE";
|
|
220
|
+
declare const FOCUS_JUMP_KEY = "g";
|
|
221
|
+
declare const DEFAULT_FOCUS_LABEL = "focus";
|
|
222
|
+
declare const DEFAULT_FOCUS_EMPTY = "nothing to focus on";
|
|
223
|
+
/**
|
|
224
|
+
* What one row draws in the focus gutter, resolved by the list. `undefined`
|
|
225
|
+
* means no focus is on screen and the row draws nothing — the branch is
|
|
226
|
+
* absent, not blank, so output without the feature is byte-identical. `null`
|
|
227
|
+
* holds two blank columns on a row the focus is not pointing at, so the one
|
|
228
|
+
* row that carries the mark does not shift its neighbours.
|
|
229
|
+
*/
|
|
230
|
+
type FocusGutter = {
|
|
231
|
+
mark: string;
|
|
232
|
+
color?: string;
|
|
233
|
+
} | null;
|
|
234
|
+
/**
|
|
235
|
+
* The gutter cell for a row url: the mark when this is the row `g` would land
|
|
236
|
+
* on, a blank holder on every other row while a focus is on screen, nothing at
|
|
237
|
+
* all without one. One function so the task rows and the PR rows cannot
|
|
238
|
+
* disagree about which row is marked.
|
|
239
|
+
*/
|
|
240
|
+
declare const focusCellFor: (focus: FocusSlot | null | undefined, url: string | undefined) => FocusGutter | undefined;
|
|
241
|
+
/**
|
|
242
|
+
* The ref's colour, for the one decision the frame cannot show. A `#427` is a
|
|
243
|
+
* pull request and takes the accent — the same orange the PR rows number
|
|
244
|
+
* themselves in — while a ticket key takes the ticket yellow. An explicit
|
|
245
|
+
* `refColor` wins over both, for a host whose refs follow another vocabulary.
|
|
246
|
+
*
|
|
247
|
+
* Exported pure because the test frame carries no escape codes: a colour that
|
|
248
|
+
* lived only in a tint would be a colour nobody can test for.
|
|
249
|
+
*/
|
|
250
|
+
declare const focusRefColor: (focus: Pick<FocusSlot, "ref" | "refColor">) => string;
|
|
251
|
+
/**
|
|
252
|
+
* One standing single-line row, the same height across loading / ready / empty
|
|
253
|
+
* so the tab strip below it never jumps — the same contract `StatusStripLine`
|
|
254
|
+
* keeps, and drawn in the same place, directly above the tabs.
|
|
255
|
+
*
|
|
256
|
+
* `undefined` while the first answer is out; `null` when the host answered
|
|
257
|
+
* with nothing to point at. Either prop (`label`/`empty`) reserves the row;
|
|
258
|
+
* with neither and no focus value the caller draws nothing at all.
|
|
259
|
+
*/
|
|
260
|
+
declare const FocusSlotLine: ({ focus, label, empty, width, jumpKey, }: {
|
|
261
|
+
/** `undefined` while the first answer is out; `null` when it came back empty. */
|
|
262
|
+
focus: FocusSlot | null | undefined;
|
|
263
|
+
/** What to call the row. The host's word; defaults to `focus`. */
|
|
264
|
+
label?: string;
|
|
265
|
+
/** The empty sentence, drawn dim after the label when focus is null. */
|
|
266
|
+
empty?: string;
|
|
267
|
+
/** Columns the row may use. */
|
|
268
|
+
width: number;
|
|
269
|
+
/**
|
|
270
|
+
* The key that jumps to the focus row, drawn as a quiet hint after the
|
|
271
|
+
* reason. Ready rows only: loading and empty have no row to jump to, so
|
|
272
|
+
* they never draw it even when it is set.
|
|
273
|
+
*/
|
|
274
|
+
jumpKey?: string;
|
|
275
|
+
}) => React.JSX.Element;
|
|
276
|
+
|
|
182
277
|
/**
|
|
183
278
|
* How this row is moving through the list right now.
|
|
184
279
|
*
|
|
@@ -270,6 +365,15 @@ type PrRowProps = {
|
|
|
270
365
|
* to the title rather than standing empty.
|
|
271
366
|
*/
|
|
272
367
|
gutter?: boolean;
|
|
368
|
+
/**
|
|
369
|
+
* The focus gutter, resolved by the list — see `focusCellFor`. `undefined`
|
|
370
|
+
* draws nothing at all (no focus feature, byte-identical to before); `null`
|
|
371
|
+
* holds two blank columns so the marked row does not shift its neighbours;
|
|
372
|
+
* a mark draws the focus glyph there in its colour. A fixed cell either way,
|
|
373
|
+
* for the same reason the cursor gutter is one: a mark that moved the title
|
|
374
|
+
* of exactly the row being watched would defeat its own purpose.
|
|
375
|
+
*/
|
|
376
|
+
focusCell?: FocusGutter;
|
|
273
377
|
};
|
|
274
378
|
/**
|
|
275
379
|
* The trailing block, in reading order: whose, how contested, how big, how
|
|
@@ -313,7 +417,9 @@ declare const trailingColumnsOf: (items: readonly GHItem[]) => TrailingColumns;
|
|
|
313
417
|
* three, and a six-digit number would have run straight into its title.
|
|
314
418
|
*/
|
|
315
419
|
declare const numberColumnsOf: (items: readonly GHItem[]) => number;
|
|
316
|
-
type LayoutInput = Pick<PrRowProps, "item" | "login" | "cols" | "prefix" | "uniformLabels" | "announcements" | "columns" | "shed" | "numberCols" | "gutter"
|
|
420
|
+
type LayoutInput = Pick<PrRowProps, "item" | "login" | "cols" | "prefix" | "uniformLabels" | "announcements" | "columns" | "shed" | "numberCols" | "gutter"> & {
|
|
421
|
+
focusGutter?: boolean;
|
|
422
|
+
};
|
|
317
423
|
/**
|
|
318
424
|
* Which trailing columns the section has to give up, so that every row gives up
|
|
319
425
|
* the same ones. Each row is laid out alone and the drops are unioned: if any
|
|
@@ -339,7 +445,7 @@ declare const sectionShedOf: (rows: readonly Pick<LayoutInput, "item" | "prefix"
|
|
|
339
445
|
* arrival, merge and departure belongs to whoever owns the list, and reaches
|
|
340
446
|
* the row as `icon`, `motion` and `announcements` after the decisions are made.
|
|
341
447
|
*/
|
|
342
|
-
declare const PrRow: ({ item, active, login, cols, selectionBg, icon: iconOverride, prefix, uniformLabels, motion, announcements, columns, shed, numberCols, gutter, }: PrRowProps) => React.JSX.Element;
|
|
448
|
+
declare const PrRow: ({ item, active, login, cols, selectionBg, icon: iconOverride, prefix, uniformLabels, motion, announcements, columns, shed, numberCols, gutter, focusCell, }: PrRowProps) => React.JSX.Element;
|
|
343
449
|
|
|
344
450
|
/**
|
|
345
451
|
* One state per thing, from a closed set the strip knows how to draw. The host
|
|
@@ -517,6 +623,16 @@ type CachedCockpit = {
|
|
|
517
623
|
* refresh — `i` did nothing and the footer did not even offer it.
|
|
518
624
|
*/
|
|
519
625
|
sidebar?: Rails;
|
|
626
|
+
/**
|
|
627
|
+
* The focus slot, when the host supplied one. Cached beside the rows because
|
|
628
|
+
* it is painted beside them: a launch that trusts the cache never refetches,
|
|
629
|
+
* so a focus left out of the file is a slot that stays on `loading…` until
|
|
630
|
+
* the next refresh — or never fills in at all.
|
|
631
|
+
*
|
|
632
|
+
* Absent reads as `undefined`: no focus feature, no row. `null` is a real
|
|
633
|
+
* answer ("nothing to focus on") and round-trips as one.
|
|
634
|
+
*/
|
|
635
|
+
focus?: FocusSlot | null;
|
|
520
636
|
};
|
|
521
637
|
declare const readCache: (key: string) => CachedCockpit | null;
|
|
522
638
|
declare const writeCache: (key: string, data: {
|
|
@@ -524,6 +640,7 @@ declare const writeCache: (key: string, data: {
|
|
|
524
640
|
login: string;
|
|
525
641
|
budget?: InboxBudget;
|
|
526
642
|
sidebar?: Rails;
|
|
643
|
+
focus?: FocusSlot | null;
|
|
527
644
|
}) => void;
|
|
528
645
|
|
|
529
646
|
/**
|
|
@@ -880,7 +997,7 @@ declare const ActionMenu: ({ item, actions, cursor, cols, }: {
|
|
|
880
997
|
cols?: number;
|
|
881
998
|
}) => React.JSX.Element;
|
|
882
999
|
declare const signatureOf: (sections: Section[]) => string;
|
|
883
|
-
declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraSetupHint, jiraTransitions, jiraTransitionsFor, tabForStatus, hasCiStatus, ciJob, ciFetcher, ciPollMs, stripFetcher, stripLabel, stripPollMs, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, onSettled, }: {
|
|
1000
|
+
declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jiraKeyRe, jiraSetupHint, jiraTransitions, jiraTransitionsFor, tabForStatus, hasCiStatus, ciJob, ciFetcher, ciPollMs, stripFetcher, stripLabel, stripPollMs, focusLabel, focusEmpty, watchPath, watchDebounceMs, watchJitterMs, extensions, tabHelp, emptyHint, liveLabel, onSettled, }: {
|
|
884
1001
|
fetcher: () => Promise<{
|
|
885
1002
|
sections: Section[];
|
|
886
1003
|
login: string;
|
|
@@ -901,6 +1018,15 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
|
|
|
901
1018
|
* data that goes stale the same way the rows do.
|
|
902
1019
|
*/
|
|
903
1020
|
sidebar?: Rails;
|
|
1021
|
+
/**
|
|
1022
|
+
* What the reader should look at next, in the host's words — see
|
|
1023
|
+
* `FocusSlot`. Part of the fetch result for the same reason the rail is:
|
|
1024
|
+
* it goes stale with the rows, and the cache paints it beside them.
|
|
1025
|
+
*
|
|
1026
|
+
* Absent means the host has no focus feature and no row is drawn; `null`
|
|
1027
|
+
* means nothing to point at and draws the dim empty line.
|
|
1028
|
+
*/
|
|
1029
|
+
focus?: FocusSlot | null;
|
|
904
1030
|
}>;
|
|
905
1031
|
cacheKey?: string;
|
|
906
1032
|
/**
|
|
@@ -943,6 +1069,16 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
|
|
|
943
1069
|
/** What to call the row before its first answer arrives with a title. */
|
|
944
1070
|
stripLabel?: string;
|
|
945
1071
|
stripPollMs?: number;
|
|
1072
|
+
/**
|
|
1073
|
+
* What to call the focus row. Passing this (or `focusEmpty`) reserves the
|
|
1074
|
+
* row from the first paint — `loading…` until the fetch lands — while a
|
|
1075
|
+
* host that never wires the feature passes neither and draws nothing.
|
|
1076
|
+
* Vocabulary, not data: it never goes stale, so it is a prop rather than
|
|
1077
|
+
* part of the fetch result.
|
|
1078
|
+
*/
|
|
1079
|
+
focusLabel?: string;
|
|
1080
|
+
/** The empty sentence, drawn dim after the label when focus comes back null. */
|
|
1081
|
+
focusEmpty?: string;
|
|
946
1082
|
/**
|
|
947
1083
|
* A file something else touches when it has changed GitHub on your behalf —
|
|
948
1084
|
* a Claude session closing an issue, a script merging a PR. Touching it makes
|
|
@@ -995,4 +1131,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
|
|
|
995
1131
|
*/
|
|
996
1132
|
declare const parsePatterns: (value: string) => string[];
|
|
997
1133
|
|
|
998
|
-
export { type Action, type ActionGroup, ActionMenu, App, COLS, type ChromeHints, type ChromeSpec, type ChromeState, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraAvailableTransition, type JiraTransition, type JiraTransitionsFor, type LiveLabel, LookupMiss, MIN_SIDEBAR_COLS, type MenuGap, type MenuRow, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, type SettledResult, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TabForStatus, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
|
|
1134
|
+
export { type Action, type ActionGroup, ActionMenu, App, COLS, type ChromeHints, type ChromeSpec, type ChromeState, CiStatusLine, type CiStatusState, type Command, CommentsPanel, type CommentsPanelProps, DEFAULT_FOCUS_EMPTY, DEFAULT_FOCUS_LABEL, type DetailContext, type ExplainSection, type ExtensionTarget, FOCUS_JUMP_KEY, FOCUS_MARK, type FocusGutter, type FocusSlot, FocusSlotLine, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraAvailableTransition, type JiraTransition, type JiraTransitionsFor, type LiveLabel, LookupMiss, MIN_SIDEBAR_COLS, type MenuGap, type MenuRow, PrRow, type PrRowProps, type Rails, type RepoFilter, type RowAnnouncement, type RowMotion, SIDEBAR_COLS, type SettledResult, SidePanel, type Sidebar, type SidebarRow, type StatusStrip, StatusStripLine, type StripItem, type StripState, type StripStatusState, type TabForStatus, type TrailingColumns, type TrailingShed, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, focusCellFor, focusRefColor, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
|
package/dist/index.js
CHANGED
|
@@ -876,7 +876,8 @@ var layoutOf = ({
|
|
|
876
876
|
columns,
|
|
877
877
|
shed = {},
|
|
878
878
|
numberCols,
|
|
879
|
-
gutter = true
|
|
879
|
+
gutter = true,
|
|
880
|
+
focusGutter = false
|
|
880
881
|
}) => {
|
|
881
882
|
const numCols = numberCols ?? numberColumnsOf([item]);
|
|
882
883
|
const numStr = `#${item.number}`.padEnd(numCols);
|
|
@@ -923,7 +924,7 @@ var layoutOf = ({
|
|
|
923
924
|
);
|
|
924
925
|
const pills = announcements.reduce((w, a) => w + 2 + a.label.length, 0);
|
|
925
926
|
const cell = labelCell();
|
|
926
|
-
return (gutter ? 2 : 0) + prefix.length + 2 + 2 + numCols + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + block + pills + pillCaps + 4;
|
|
927
|
+
return (gutter ? 2 : 0) + prefix.length + (focusGutter ? 2 : 0) + 2 + 2 + numCols + (cell ? 2 + cell.length + 1 : 0) + (givingUp.repo ? 0 : repoLabel.length) + block + pills + pillCaps + 4;
|
|
927
928
|
};
|
|
928
929
|
const MIN_TITLE = 24;
|
|
929
930
|
for (const give of [
|
|
@@ -969,7 +970,8 @@ var PrRow = ({
|
|
|
969
970
|
columns,
|
|
970
971
|
shed,
|
|
971
972
|
numberCols,
|
|
972
|
-
gutter = true
|
|
973
|
+
gutter = true,
|
|
974
|
+
focusCell
|
|
973
975
|
}) => {
|
|
974
976
|
const backdropped = useBackdropped();
|
|
975
977
|
const { glyph: healthIcon, color: healthColor2 } = displayFor(item.health);
|
|
@@ -1002,7 +1004,8 @@ var PrRow = ({
|
|
|
1002
1004
|
columns,
|
|
1003
1005
|
shed,
|
|
1004
1006
|
numberCols,
|
|
1005
|
-
gutter
|
|
1007
|
+
gutter,
|
|
1008
|
+
focusGutter: focusCell !== void 0
|
|
1006
1009
|
});
|
|
1007
1010
|
return (
|
|
1008
1011
|
// The wash paints every cell of the fixed-width row — an Ink Box's
|
|
@@ -1010,6 +1013,7 @@ var PrRow = ({
|
|
|
1010
1013
|
/* @__PURE__ */ jsxs(Box, { width: cols, backgroundColor: selectionBg, children: [
|
|
1011
1014
|
gutter ? /* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }) : null,
|
|
1012
1015
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
|
|
1016
|
+
focusCell !== void 0 ? focusCell ? /* @__PURE__ */ jsx(Text3, { color: focusCell.color, bold: true, children: `${focusCell.mark} ` }) : /* @__PURE__ */ jsx(Text3, { children: " " }) : null,
|
|
1013
1017
|
/* @__PURE__ */ jsx(Text3, { color, bold: true, children: icon + " " }),
|
|
1014
1018
|
/* @__PURE__ */ jsx(Text3, { color: turnColor, bold: turnIcon === "\u2190", children: turnIcon + " " }),
|
|
1015
1019
|
/* @__PURE__ */ jsx(Text3, { color: colors.accent, children: numStr }),
|
|
@@ -1372,7 +1376,7 @@ var lookupRows = (claim, lookup, query, start, openUrl) => {
|
|
|
1372
1376
|
|
|
1373
1377
|
// src/inbox/cache.ts
|
|
1374
1378
|
var cacheTtlMs = () => inboxConfig().cacheTtlMs;
|
|
1375
|
-
var CACHE_VERSION =
|
|
1379
|
+
var CACHE_VERSION = 6;
|
|
1376
1380
|
var cacheDir = () => join(
|
|
1377
1381
|
process.env.XDG_CACHE_HOME || join(homedir(), ".cache"),
|
|
1378
1382
|
inboxConfig().cacheNamespace
|
|
@@ -1383,11 +1387,14 @@ var readCache = (key) => {
|
|
|
1383
1387
|
const raw = JSON.parse(readFileSync(cacheFile(key), "utf8"));
|
|
1384
1388
|
if (raw?.version !== CACHE_VERSION) return null;
|
|
1385
1389
|
if (!Array.isArray(raw?.sections)) return null;
|
|
1390
|
+
const focusOk = raw.focus === void 0 || raw.focus === null || typeof raw.focus?.target?.tab === "string" && typeof raw.focus?.target?.url === "string";
|
|
1391
|
+
if (!focusOk) return null;
|
|
1386
1392
|
return {
|
|
1387
1393
|
sections: raw.sections,
|
|
1388
1394
|
login: raw.login ?? "",
|
|
1389
1395
|
at: raw.at ?? 0,
|
|
1390
1396
|
budget: raw.budget,
|
|
1397
|
+
...raw.focus !== void 0 ? { focus: raw.focus } : {},
|
|
1391
1398
|
...railsOf(raw.sidebar).every((s) => Array.isArray(s?.rows)) && raw.sidebar !== void 0 ? { sidebar: raw.sidebar } : {}
|
|
1392
1399
|
};
|
|
1393
1400
|
} catch {
|
|
@@ -1686,6 +1693,73 @@ var failureNotice = (failed) => {
|
|
|
1686
1693
|
const more = failed.length > 1 ? ` +${failed.length - 1} more` : "";
|
|
1687
1694
|
return `\u2717 Couldn't ${newest.action} ${newest.label}: ${newest.failure?.reason ?? "no reason reported"}${more} w restore`;
|
|
1688
1695
|
};
|
|
1696
|
+
var FOCUS_MARK = "\u{F04FE}";
|
|
1697
|
+
var FOCUS_JUMP_KEY = "g";
|
|
1698
|
+
var DEFAULT_FOCUS_LABEL = "focus";
|
|
1699
|
+
var DEFAULT_FOCUS_EMPTY = "nothing to focus on";
|
|
1700
|
+
var focusCellFor = (focus, url) => {
|
|
1701
|
+
if (focus === void 0) return void 0;
|
|
1702
|
+
if (!focus || url !== focus.target.url) return null;
|
|
1703
|
+
return { mark: focus.mark ?? FOCUS_MARK, color: focus.markColor ?? colors.accent };
|
|
1704
|
+
};
|
|
1705
|
+
var focusRefColor = (focus) => focus.refColor ?? (focus.ref.startsWith("#") ? colors.accent : colors.ticket);
|
|
1706
|
+
var cells = (s) => [...s].length;
|
|
1707
|
+
var titleBudgetOf = (focus, label, width, jumpKey) => {
|
|
1708
|
+
const hint = jumpKey ? jumpKey : void 0;
|
|
1709
|
+
const mark = focus.mark ?? FOCUS_MARK;
|
|
1710
|
+
const head = 2 + cells(mark) + 1 + cells(label) + 2 + (focus.marker ? cells(focus.marker) + 1 : 0) + cells(focus.ref) + 2;
|
|
1711
|
+
const hintCells = hint ? 3 + cells(hint) + 5 : 0;
|
|
1712
|
+
const tail = 3 + cells(focus.reason);
|
|
1713
|
+
const titleMax = width - head - tail - hintCells;
|
|
1714
|
+
if (titleMax >= 1)
|
|
1715
|
+
return { title: truncate(focus.title, titleMax), reason: focus.reason, hint };
|
|
1716
|
+
const reasonMax = width - head - 3 - 1 - hintCells;
|
|
1717
|
+
if (hint === void 0 || reasonMax >= 1)
|
|
1718
|
+
return {
|
|
1719
|
+
title: "",
|
|
1720
|
+
reason: truncate(focus.reason, Math.max(0, width - head - 3 - 1 - hintCells)),
|
|
1721
|
+
hint
|
|
1722
|
+
};
|
|
1723
|
+
return {
|
|
1724
|
+
title: "",
|
|
1725
|
+
reason: truncate(focus.reason, Math.max(0, width - head - 3 - 1)),
|
|
1726
|
+
hint: void 0
|
|
1727
|
+
};
|
|
1728
|
+
};
|
|
1729
|
+
var FocusSlotLine = ({
|
|
1730
|
+
focus,
|
|
1731
|
+
label = DEFAULT_FOCUS_LABEL,
|
|
1732
|
+
empty = DEFAULT_FOCUS_EMPTY,
|
|
1733
|
+
width,
|
|
1734
|
+
jumpKey
|
|
1735
|
+
}) => {
|
|
1736
|
+
if (focus === void 0)
|
|
1737
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
1738
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
|
|
1739
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} loading\u2026` })
|
|
1740
|
+
] });
|
|
1741
|
+
if (focus === null)
|
|
1742
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
1743
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${FOCUS_MARK} ` }),
|
|
1744
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} ${empty}` })
|
|
1745
|
+
] });
|
|
1746
|
+
const mark = focus.mark ?? FOCUS_MARK;
|
|
1747
|
+
const { title, reason, hint } = titleBudgetOf(focus, label, width, jumpKey);
|
|
1748
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
1749
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1750
|
+
/* @__PURE__ */ jsx(Text, { color: focus.markColor ?? colors.accent, bold: true, children: `${mark} ` }),
|
|
1751
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} ` }),
|
|
1752
|
+
focus.marker ? /* @__PURE__ */ jsx(Text, { children: `${focus.marker} ` }) : null,
|
|
1753
|
+
/* @__PURE__ */ jsx(Text, { color: focusRefColor(focus), bold: true, children: `${focus.ref} ` }),
|
|
1754
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: title }),
|
|
1755
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${reason}` }),
|
|
1756
|
+
hint ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1757
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
1758
|
+
/* @__PURE__ */ jsx(Text, { color: colors.accent, bold: true, children: hint }),
|
|
1759
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " jump" })
|
|
1760
|
+
] }) : null
|
|
1761
|
+
] });
|
|
1762
|
+
};
|
|
1689
1763
|
var quietly = $({ quiet: true });
|
|
1690
1764
|
var isMenuGap = (row) => "gap" in row;
|
|
1691
1765
|
var rowLabelOf = (item) => item.kind === "task" ? item.ticket ?? item.key : item.kind === "pr" || item.kind === "issue" ? `#${item.number}` : "";
|
|
@@ -2911,7 +2985,8 @@ var ItemRow = ({
|
|
|
2911
2985
|
uniformLabels = [],
|
|
2912
2986
|
columns,
|
|
2913
2987
|
shed,
|
|
2914
|
-
numberCols
|
|
2988
|
+
numberCols,
|
|
2989
|
+
focus
|
|
2915
2990
|
}) => {
|
|
2916
2991
|
const backdropped = useBackdropped();
|
|
2917
2992
|
const selBg = selectionBackground(active, backdropped);
|
|
@@ -2955,12 +3030,16 @@ var ItemRow = ({
|
|
|
2955
3030
|
const transitIcon = pending ? PENDING_GLYPH : !transient ? " " : isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : "\u25C9";
|
|
2956
3031
|
const titleMax = Math.max(
|
|
2957
3032
|
1,
|
|
2958
|
-
cols - item.key.length - note.length - (item.pill ? pillWidth(item.pill) + 1 : 0) - (farewellLabel ? pillWidth(farewellLabel) + 2 : 0) - prefix.length - (markerCols > 0 ? markerCols + 1 : 0) -
|
|
3033
|
+
cols - item.key.length - note.length - (item.pill ? pillWidth(item.pill) + 1 : 0) - (farewellLabel ? pillWidth(farewellLabel) + 2 : 0) - prefix.length - (markerCols > 0 ? markerCols + 1 : 0) - // The focus gutter, priced only when it is drawn: two columns on every
|
|
3034
|
+
// url-bearing row while a focus is on screen, none at all without one.
|
|
3035
|
+
(focus !== void 0 ? 2 : 0) - 12
|
|
2959
3036
|
);
|
|
3037
|
+
const focusCell = focusCellFor(focus, item.url);
|
|
2960
3038
|
return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, width: cols, backgroundColor: selBg, children: [
|
|
2961
3039
|
/* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
|
|
2962
3040
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
|
|
2963
3041
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
|
|
3042
|
+
focusCell !== void 0 ? focusCell ? /* @__PURE__ */ jsx(Text3, { color: focusCell.color, bold: true, children: `${focusCell.mark} ` }) : /* @__PURE__ */ jsx(Text3, { children: " " }) : null,
|
|
2964
3043
|
pending ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: transitIcon + " " }) : /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
|
|
2965
3044
|
markerCols > 0 ? /* @__PURE__ */ jsx(Text3, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
|
|
2966
3045
|
/* @__PURE__ */ jsx(Text3, { ...taskKeyStyle(), children: item.key + " " }),
|
|
@@ -3019,7 +3098,11 @@ var ItemRow = ({
|
|
|
3019
3098
|
numberCols,
|
|
3020
3099
|
icon,
|
|
3021
3100
|
motion,
|
|
3022
|
-
announcements
|
|
3101
|
+
announcements,
|
|
3102
|
+
focusCell: focusCellFor(
|
|
3103
|
+
focus,
|
|
3104
|
+
"url" in item ? item.url : void 0
|
|
3105
|
+
)
|
|
3023
3106
|
}
|
|
3024
3107
|
);
|
|
3025
3108
|
};
|
|
@@ -3223,6 +3306,7 @@ var LEGEND_FOOTNOTE = "Each item lands in the FIRST tab that claims it, so count
|
|
|
3223
3306
|
var HelpModal = ({
|
|
3224
3307
|
extensions,
|
|
3225
3308
|
hasJira,
|
|
3309
|
+
hasFocus,
|
|
3226
3310
|
tabHelp,
|
|
3227
3311
|
maxRows,
|
|
3228
3312
|
scroll = 0,
|
|
@@ -3257,6 +3341,7 @@ var HelpModal = ({
|
|
|
3257
3341
|
["f", "filter by repo"],
|
|
3258
3342
|
["r", "refresh"],
|
|
3259
3343
|
["w", "restore a change that failed"],
|
|
3344
|
+
...hasFocus ? [[FOCUS_JUMP_KEY, "jump to focus"]] : [],
|
|
3260
3345
|
...extensionLegend(extensions),
|
|
3261
3346
|
["?", "this help"],
|
|
3262
3347
|
["q", "quit"]
|
|
@@ -3452,6 +3537,9 @@ var BrowseScreen = ({
|
|
|
3452
3537
|
ciJob,
|
|
3453
3538
|
stripState,
|
|
3454
3539
|
stripLabel,
|
|
3540
|
+
focus,
|
|
3541
|
+
focusLabel,
|
|
3542
|
+
focusEmpty,
|
|
3455
3543
|
tabHelp,
|
|
3456
3544
|
origin,
|
|
3457
3545
|
budget,
|
|
@@ -3503,12 +3591,13 @@ var BrowseScreen = ({
|
|
|
3503
3591
|
const filterActive = search != null || repoFilter.size > 0;
|
|
3504
3592
|
const reserveCiRow = ciStatusState != null;
|
|
3505
3593
|
const reserveStripRow = stripState != null;
|
|
3594
|
+
const reserveFocusRow = focusLabel !== void 0 || focusEmpty !== void 0 || focus !== void 0;
|
|
3506
3595
|
const ciStatus = ciStatusState?.kind === "ready" ? ciStatusState.status : null;
|
|
3507
3596
|
const listHeight = Math.max(
|
|
3508
3597
|
5,
|
|
3509
3598
|
// -10 rather than -8: the extra 2 are the frame's top and bottom border
|
|
3510
3599
|
// rows, so the tree never grows taller than the terminal inside the frame.
|
|
3511
|
-
rows - 10 - (filterActive ? 2 : 0) - (reserveCiRow ? 2 : 0) - (reserveStripRow ? 2 : 0)
|
|
3600
|
+
rows - 10 - (filterActive ? 2 : 0) - (reserveCiRow ? 2 : 0) - (reserveStripRow ? 2 : 0) - (reserveFocusRow ? 2 : 0)
|
|
3512
3601
|
);
|
|
3513
3602
|
useEffect(() => {
|
|
3514
3603
|
setLocalSections(applyWork(sections));
|
|
@@ -3654,7 +3743,10 @@ var BrowseScreen = ({
|
|
|
3654
3743
|
cols: rowCols,
|
|
3655
3744
|
uniformLabels,
|
|
3656
3745
|
columns: trailingColumns,
|
|
3657
|
-
numberCols
|
|
3746
|
+
numberCols,
|
|
3747
|
+
// Presence only: every row holds the same two gutter columns while a
|
|
3748
|
+
// focus is on screen, so the shed ladder prices the cost once for all.
|
|
3749
|
+
focusGutter: focus !== void 0
|
|
3658
3750
|
}
|
|
3659
3751
|
);
|
|
3660
3752
|
const [sparkFrame, setSparkFrame] = useState(0);
|
|
@@ -3894,6 +3986,56 @@ var BrowseScreen = ({
|
|
|
3894
3986
|
onRefresh?.();
|
|
3895
3987
|
return;
|
|
3896
3988
|
}
|
|
3989
|
+
if (input === FOCUS_JUMP_KEY && focus) {
|
|
3990
|
+
const tabId = focus.target.tab;
|
|
3991
|
+
const at = localSections.findIndex((s) => s.id === tabId);
|
|
3992
|
+
if (at === -1) return;
|
|
3993
|
+
const drawn = (s) => {
|
|
3994
|
+
const one = search != null ? filterBySearch([s], search, login) : [s];
|
|
3995
|
+
const two = repoFilter.size > 0 ? filterByRepos(one, repoFilter, login) : one;
|
|
3996
|
+
return filterActive ? two[0]?.items ?? [] : s.items;
|
|
3997
|
+
};
|
|
3998
|
+
const urlOf = (i) => "url" in i ? i.url : void 0;
|
|
3999
|
+
let items = localSections[at].items;
|
|
4000
|
+
if (!items.some((i) => urlOf(i) === focus.target.url)) {
|
|
4001
|
+
const tail = items.find(
|
|
4002
|
+
(i) => i.kind === "show-more" && i.hidden.some((h) => h.url === focus.target.url)
|
|
4003
|
+
);
|
|
4004
|
+
if (!tail) {
|
|
4005
|
+
setTabIdx(at);
|
|
4006
|
+
return;
|
|
4007
|
+
}
|
|
4008
|
+
items = [
|
|
4009
|
+
...items.slice(0, items.indexOf(tail)),
|
|
4010
|
+
...tail.hidden,
|
|
4011
|
+
{
|
|
4012
|
+
kind: "show-less",
|
|
4013
|
+
toHide: tail.hidden,
|
|
4014
|
+
// The depth of the row it replaces, never a constant — a collapsed
|
|
4015
|
+
// row under a story sits deeper than 1, and pinning it breaks the
|
|
4016
|
+
// `treeUrls` walk exactly as the comment there warns.
|
|
4017
|
+
depth: depthOf(tail)
|
|
4018
|
+
},
|
|
4019
|
+
...items.slice(items.indexOf(tail) + 1)
|
|
4020
|
+
];
|
|
4021
|
+
const expanded = items;
|
|
4022
|
+
setLocalSections(
|
|
4023
|
+
(prev) => prev.map((s) => s.id === tabId ? { ...s, items: expanded } : s)
|
|
4024
|
+
);
|
|
4025
|
+
prevItems.current = { ...prevItems.current, [tabId]: expanded };
|
|
4026
|
+
}
|
|
4027
|
+
setTabIdx(at);
|
|
4028
|
+
const shown = drawn({ ...localSections[at], items });
|
|
4029
|
+
const dit = shown.findIndex((i) => urlOf(i) === focus.target.url);
|
|
4030
|
+
if (dit === -1) return;
|
|
4031
|
+
setCursors((p) => ({ ...p, [tabId]: dit }));
|
|
4032
|
+
setViewStarts((p) => {
|
|
4033
|
+
let vs = Math.min(p[tabId] ?? 0, withHeaders(shown, dit));
|
|
4034
|
+
while (dit >= vs + windowCount(shown, vs, listHeight)) vs++;
|
|
4035
|
+
return { ...p, [tabId]: vs };
|
|
4036
|
+
});
|
|
4037
|
+
return;
|
|
4038
|
+
}
|
|
3897
4039
|
const ext = extensionFor(input, extensions);
|
|
3898
4040
|
if (ext) {
|
|
3899
4041
|
onOpenExt?.(ext.id, extensionTargetFor(activeItem));
|
|
@@ -4309,6 +4451,7 @@ var BrowseScreen = ({
|
|
|
4309
4451
|
{
|
|
4310
4452
|
extensions,
|
|
4311
4453
|
hasJira: !!jiraBase,
|
|
4454
|
+
hasFocus: focus != null,
|
|
4312
4455
|
tabHelp,
|
|
4313
4456
|
maxRows: listHeight,
|
|
4314
4457
|
scroll: helpScroll,
|
|
@@ -4365,6 +4508,16 @@ var BrowseScreen = ({
|
|
|
4365
4508
|
width: COLS
|
|
4366
4509
|
}
|
|
4367
4510
|
) : null,
|
|
4511
|
+
reserveFocusRow ? /* @__PURE__ */ jsx(
|
|
4512
|
+
FocusSlotLine,
|
|
4513
|
+
{
|
|
4514
|
+
focus,
|
|
4515
|
+
label: focusLabel,
|
|
4516
|
+
empty: focusEmpty,
|
|
4517
|
+
width: COLS,
|
|
4518
|
+
jumpKey: FOCUS_JUMP_KEY
|
|
4519
|
+
}
|
|
4520
|
+
) : null,
|
|
4368
4521
|
skippedForBudget ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
4369
4522
|
/* @__PURE__ */ jsx(Text3, { bold: true, color: colors.accent, children: " \u26A1 auto-refresh paused to save API budget" }),
|
|
4370
4523
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: " r refreshes anyway" })
|
|
@@ -4373,9 +4526,11 @@ var BrowseScreen = ({
|
|
|
4373
4526
|
Tabs,
|
|
4374
4527
|
{
|
|
4375
4528
|
active: section.id,
|
|
4529
|
+
width: COLS,
|
|
4376
4530
|
items: localSections.map((s) => ({
|
|
4377
4531
|
value: s.id,
|
|
4378
4532
|
label: s.label,
|
|
4533
|
+
icon: s.icon,
|
|
4379
4534
|
count: countOf(s.id),
|
|
4380
4535
|
/*
|
|
4381
4536
|
* The denominator, and only where it can still be true.
|
|
@@ -4461,6 +4616,7 @@ var BrowseScreen = ({
|
|
|
4461
4616
|
columns: trailingColumns,
|
|
4462
4617
|
shed: trailingShed,
|
|
4463
4618
|
numberCols,
|
|
4619
|
+
focus,
|
|
4464
4620
|
gap: i > 0 && gapsAbove(section.items, viewStart + i)
|
|
4465
4621
|
},
|
|
4466
4622
|
`${viewStart + i}:${rowKey(item)}`
|
|
@@ -4556,6 +4712,8 @@ var App = ({
|
|
|
4556
4712
|
stripFetcher,
|
|
4557
4713
|
stripLabel,
|
|
4558
4714
|
stripPollMs = 3e5,
|
|
4715
|
+
focusLabel,
|
|
4716
|
+
focusEmpty,
|
|
4559
4717
|
watchPath,
|
|
4560
4718
|
watchDebounceMs = 400,
|
|
4561
4719
|
// Zero by default, deliberately. A package that adds randomness to its own
|
|
@@ -4610,6 +4768,7 @@ var App = ({
|
|
|
4610
4768
|
return sameCiStatusState(prev, next) ? prev : next;
|
|
4611
4769
|
});
|
|
4612
4770
|
const hasStrip = !!stripFetcher;
|
|
4771
|
+
const hasFocusSlot = focusLabel !== void 0 || focusEmpty !== void 0;
|
|
4613
4772
|
const [stripState, setStripState] = useState({
|
|
4614
4773
|
kind: "loading"
|
|
4615
4774
|
});
|
|
@@ -4738,6 +4897,7 @@ var App = ({
|
|
|
4738
4897
|
)
|
|
4739
4898
|
};
|
|
4740
4899
|
if (fresh.budget) setBudget(fresh.budget);
|
|
4900
|
+
if (fresh.focus !== void 0) setFocus(fresh.focus);
|
|
4741
4901
|
if (fresh.sidebar !== void 0) setSidebar(fresh.sidebar);
|
|
4742
4902
|
if (cacheKey) writeCache(cacheKey, fresh);
|
|
4743
4903
|
setRefreshing(false);
|
|
@@ -4890,6 +5050,7 @@ var App = ({
|
|
|
4890
5050
|
displayedSections.current = cached.sections;
|
|
4891
5051
|
setFetchedAt(cached.at);
|
|
4892
5052
|
if (cached.sidebar) setSidebar(cached.sidebar);
|
|
5053
|
+
if (cached.focus !== void 0) setFocus(cached.focus);
|
|
4893
5054
|
setState({
|
|
4894
5055
|
phase: "browse",
|
|
4895
5056
|
sections: cached.sections,
|
|
@@ -4952,6 +5113,7 @@ var App = ({
|
|
|
4952
5113
|
if (shared && shared.at > firedAt) {
|
|
4953
5114
|
receive({ sections: shared.sections, login: shared.login });
|
|
4954
5115
|
if (shared.sidebar) setSidebar(shared.sidebar);
|
|
5116
|
+
if (shared.focus !== void 0) setFocus(shared.focus);
|
|
4955
5117
|
setFetchedAt(shared.at);
|
|
4956
5118
|
return;
|
|
4957
5119
|
}
|
|
@@ -4972,6 +5134,7 @@ var App = ({
|
|
|
4972
5134
|
);
|
|
4973
5135
|
const [appliedSummary, setAppliedSummary] = useState("");
|
|
4974
5136
|
const [sidebar, setSidebar] = useState(void 0);
|
|
5137
|
+
const [focus, setFocus] = useState(void 0);
|
|
4975
5138
|
const [skippedForBudget, setSkippedForBudget] = useState(false);
|
|
4976
5139
|
const [mergedUrls, setMergedUrls] = useState([]);
|
|
4977
5140
|
const [leavingUrls, setLeavingUrls] = useState([]);
|
|
@@ -5024,11 +5187,23 @@ var App = ({
|
|
|
5024
5187
|
width: COLS
|
|
5025
5188
|
}
|
|
5026
5189
|
) : null,
|
|
5190
|
+
hasFocusSlot ? /* @__PURE__ */ jsx(
|
|
5191
|
+
FocusSlotLine,
|
|
5192
|
+
{
|
|
5193
|
+
focus: void 0,
|
|
5194
|
+
label: focusLabel,
|
|
5195
|
+
empty: focusEmpty,
|
|
5196
|
+
width: COLS
|
|
5197
|
+
}
|
|
5198
|
+
) : null,
|
|
5027
5199
|
/* @__PURE__ */ jsx(
|
|
5028
5200
|
Box,
|
|
5029
5201
|
{
|
|
5030
5202
|
flexDirection: "column",
|
|
5031
|
-
minHeight: Math.max(
|
|
5203
|
+
minHeight: Math.max(
|
|
5204
|
+
5,
|
|
5205
|
+
rows - 6 - (hasCiStatus ? 2 : 0) - (hasStrip ? 2 : 0) - (hasFocusSlot ? 2 : 0)
|
|
5206
|
+
),
|
|
5032
5207
|
children: state.phase === "loading" ? /* @__PURE__ */ jsx(LoadingScreen, { label: `Fetching ${title}\u2026` }) : /* @__PURE__ */ jsx(
|
|
5033
5208
|
NoRowsScreen,
|
|
5034
5209
|
{
|
|
@@ -5136,6 +5311,9 @@ var App = ({
|
|
|
5136
5311
|
ciJob,
|
|
5137
5312
|
stripState: hasStrip ? stripState : void 0,
|
|
5138
5313
|
stripLabel,
|
|
5314
|
+
focus,
|
|
5315
|
+
focusLabel,
|
|
5316
|
+
focusEmpty,
|
|
5139
5317
|
tabHelp,
|
|
5140
5318
|
onOpenPr: (item) => {
|
|
5141
5319
|
setChromeSpec({});
|
|
@@ -5184,4 +5362,4 @@ var matchesFilter = (repo, filter) => {
|
|
|
5184
5362
|
};
|
|
5185
5363
|
var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
5186
5364
|
|
|
5187
|
-
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, LookupMiss, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
|
|
5365
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, DEFAULT_FOCUS_EMPTY, DEFAULT_FOCUS_LABEL, FOCUS_JUMP_KEY, FOCUS_MARK, FocusSlotLine, HealthPanel, LookupMiss, MIN_SIDEBAR_COLS, PrRow, SIDEBAR_COLS, SidePanel, StatusStripLine, ageLabel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, focusCellFor, focusRefColor, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, isMenuGap, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, menuRowsOf, menuTone, moveCursor, numberColumnsOf, openInTab, parsePatterns, railCapacity, railHeights, railRowsOf, railWidth, railsOf, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, sectionShedOf, signatureOf, stripGlyph, stripLayout, stripSignature, toCiStatusState, trailingColumnsOf, truncate, useActionMenu, useChrome, windowCount, withHeaders, withoutItem, workCount, writeCache };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.75.1",
|
|
4
4
|
"description": "Ink components for rendering GitHub PR review comments and health — controlled, presentation-only, built on @kud/ink-ui and fed by @kud/gh.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,17 +44,17 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"@kud/ink-ui": ">=0.
|
|
47
|
+
"@kud/ink-ui": ">=0.39.0 <1",
|
|
48
48
|
"ink": ">=7",
|
|
49
49
|
"react": ">=19"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@kud/gh": "0.21.0",
|
|
53
|
-
"@kud/gh-workflow": "0.
|
|
53
|
+
"@kud/gh-workflow": "0.17.0",
|
|
54
54
|
"zx": "8.8.5"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@kud/ink-ui": "0.
|
|
57
|
+
"@kud/ink-ui": "0.39.0",
|
|
58
58
|
"@types/node": "22.20.1",
|
|
59
59
|
"@types/react": "19.2.17",
|
|
60
60
|
"ink": "7.1.0",
|