@kud/gh-ink 0.73.0 → 0.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/index.d.ts +182 -4
- package/dist/index.js +320 -129
- 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,94 @@ 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 DEFAULT_FOCUS_LABEL = "focus";
|
|
221
|
+
declare const DEFAULT_FOCUS_EMPTY = "nothing to focus on";
|
|
222
|
+
/**
|
|
223
|
+
* What one row draws in the focus gutter, resolved by the list. `undefined`
|
|
224
|
+
* means no focus is on screen and the row draws nothing — the branch is
|
|
225
|
+
* absent, not blank, so output without the feature is byte-identical. `null`
|
|
226
|
+
* holds two blank columns on a row the focus is not pointing at, so the one
|
|
227
|
+
* row that carries the mark does not shift its neighbours.
|
|
228
|
+
*/
|
|
229
|
+
type FocusGutter = {
|
|
230
|
+
mark: string;
|
|
231
|
+
color?: string;
|
|
232
|
+
} | null;
|
|
233
|
+
/**
|
|
234
|
+
* The gutter cell for a row url: the mark when this is the row `g` would land
|
|
235
|
+
* on, a blank holder on every other row while a focus is on screen, nothing at
|
|
236
|
+
* all without one. One function so the task rows and the PR rows cannot
|
|
237
|
+
* disagree about which row is marked.
|
|
238
|
+
*/
|
|
239
|
+
declare const focusCellFor: (focus: FocusSlot | null | undefined, url: string | undefined) => FocusGutter | undefined;
|
|
240
|
+
/**
|
|
241
|
+
* The ref's colour, for the one decision the frame cannot show. A `#427` is a
|
|
242
|
+
* pull request and takes the accent — the same orange the PR rows number
|
|
243
|
+
* themselves in — while a ticket key takes the ticket yellow. An explicit
|
|
244
|
+
* `refColor` wins over both, for a host whose refs follow another vocabulary.
|
|
245
|
+
*
|
|
246
|
+
* Exported pure because the test frame carries no escape codes: a colour that
|
|
247
|
+
* lived only in a tint would be a colour nobody can test for.
|
|
248
|
+
*/
|
|
249
|
+
declare const focusRefColor: (focus: Pick<FocusSlot, "ref" | "refColor">) => string;
|
|
250
|
+
/**
|
|
251
|
+
* One standing single-line row, the same height across loading / ready / empty
|
|
252
|
+
* so the tab strip below it never jumps — the same contract `StatusStripLine`
|
|
253
|
+
* keeps, and drawn in the same place, directly above the tabs.
|
|
254
|
+
*
|
|
255
|
+
* `undefined` while the first answer is out; `null` when the host answered
|
|
256
|
+
* with nothing to point at. Either prop (`label`/`empty`) reserves the row;
|
|
257
|
+
* with neither and no focus value the caller draws nothing at all.
|
|
258
|
+
*/
|
|
259
|
+
declare const FocusSlotLine: ({ focus, label, empty, width, }: {
|
|
260
|
+
/** `undefined` while the first answer is out; `null` when it came back empty. */
|
|
261
|
+
focus: FocusSlot | null | undefined;
|
|
262
|
+
/** What to call the row. The host's word; defaults to `focus`. */
|
|
263
|
+
label?: string;
|
|
264
|
+
/** The empty sentence, drawn dim after the label when focus is null. */
|
|
265
|
+
empty?: string;
|
|
266
|
+
/** Columns the row may use. */
|
|
267
|
+
width: number;
|
|
268
|
+
}) => React.JSX.Element;
|
|
269
|
+
|
|
182
270
|
/**
|
|
183
271
|
* How this row is moving through the list right now.
|
|
184
272
|
*
|
|
@@ -270,6 +358,15 @@ type PrRowProps = {
|
|
|
270
358
|
* to the title rather than standing empty.
|
|
271
359
|
*/
|
|
272
360
|
gutter?: boolean;
|
|
361
|
+
/**
|
|
362
|
+
* The focus gutter, resolved by the list — see `focusCellFor`. `undefined`
|
|
363
|
+
* draws nothing at all (no focus feature, byte-identical to before); `null`
|
|
364
|
+
* holds two blank columns so the marked row does not shift its neighbours;
|
|
365
|
+
* a mark draws the focus glyph there in its colour. A fixed cell either way,
|
|
366
|
+
* for the same reason the cursor gutter is one: a mark that moved the title
|
|
367
|
+
* of exactly the row being watched would defeat its own purpose.
|
|
368
|
+
*/
|
|
369
|
+
focusCell?: FocusGutter;
|
|
273
370
|
};
|
|
274
371
|
/**
|
|
275
372
|
* The trailing block, in reading order: whose, how contested, how big, how
|
|
@@ -313,7 +410,9 @@ declare const trailingColumnsOf: (items: readonly GHItem[]) => TrailingColumns;
|
|
|
313
410
|
* three, and a six-digit number would have run straight into its title.
|
|
314
411
|
*/
|
|
315
412
|
declare const numberColumnsOf: (items: readonly GHItem[]) => number;
|
|
316
|
-
type LayoutInput = Pick<PrRowProps, "item" | "login" | "cols" | "prefix" | "uniformLabels" | "announcements" | "columns" | "shed" | "numberCols" | "gutter"
|
|
413
|
+
type LayoutInput = Pick<PrRowProps, "item" | "login" | "cols" | "prefix" | "uniformLabels" | "announcements" | "columns" | "shed" | "numberCols" | "gutter"> & {
|
|
414
|
+
focusGutter?: boolean;
|
|
415
|
+
};
|
|
317
416
|
/**
|
|
318
417
|
* Which trailing columns the section has to give up, so that every row gives up
|
|
319
418
|
* the same ones. Each row is laid out alone and the drops are unioned: if any
|
|
@@ -339,7 +438,7 @@ declare const sectionShedOf: (rows: readonly Pick<LayoutInput, "item" | "prefix"
|
|
|
339
438
|
* arrival, merge and departure belongs to whoever owns the list, and reaches
|
|
340
439
|
* the row as `icon`, `motion` and `announcements` after the decisions are made.
|
|
341
440
|
*/
|
|
342
|
-
declare const PrRow: ({ item, active, login, cols, selectionBg, icon: iconOverride, prefix, uniformLabels, motion, announcements, columns, shed, numberCols, gutter, }: PrRowProps) => React.JSX.Element;
|
|
441
|
+
declare const PrRow: ({ item, active, login, cols, selectionBg, icon: iconOverride, prefix, uniformLabels, motion, announcements, columns, shed, numberCols, gutter, focusCell, }: PrRowProps) => React.JSX.Element;
|
|
343
442
|
|
|
344
443
|
/**
|
|
345
444
|
* One state per thing, from a closed set the strip knows how to draw. The host
|
|
@@ -517,6 +616,16 @@ type CachedCockpit = {
|
|
|
517
616
|
* refresh — `i` did nothing and the footer did not even offer it.
|
|
518
617
|
*/
|
|
519
618
|
sidebar?: Rails;
|
|
619
|
+
/**
|
|
620
|
+
* The focus slot, when the host supplied one. Cached beside the rows because
|
|
621
|
+
* it is painted beside them: a launch that trusts the cache never refetches,
|
|
622
|
+
* so a focus left out of the file is a slot that stays on `loading…` until
|
|
623
|
+
* the next refresh — or never fills in at all.
|
|
624
|
+
*
|
|
625
|
+
* Absent reads as `undefined`: no focus feature, no row. `null` is a real
|
|
626
|
+
* answer ("nothing to focus on") and round-trips as one.
|
|
627
|
+
*/
|
|
628
|
+
focus?: FocusSlot | null;
|
|
520
629
|
};
|
|
521
630
|
declare const readCache: (key: string) => CachedCockpit | null;
|
|
522
631
|
declare const writeCache: (key: string, data: {
|
|
@@ -524,8 +633,30 @@ declare const writeCache: (key: string, data: {
|
|
|
524
633
|
login: string;
|
|
525
634
|
budget?: InboxBudget;
|
|
526
635
|
sidebar?: Rails;
|
|
636
|
+
focus?: FocusSlot | null;
|
|
527
637
|
}) => void;
|
|
528
638
|
|
|
639
|
+
/**
|
|
640
|
+
* What an action did to a row, said before the server has agreed to it.
|
|
641
|
+
*
|
|
642
|
+
* Three shapes cover every action the inbox takes on a row: it leaves the list
|
|
643
|
+
* (close, done), it changes tab (a Jira move), or it changes in place (labels,
|
|
644
|
+
* assignee, read). A patch names the END STATE rather than the step that gets
|
|
645
|
+
* there, which is what lets it be reapplied on top of every fetch: "SHOP-1234
|
|
646
|
+
* belongs in review, wearing In Review" is as true on the third fetch as on the
|
|
647
|
+
* first, where "move it one tab right" would walk it off the board.
|
|
648
|
+
*/
|
|
649
|
+
type Patch = {
|
|
650
|
+
kind: "remove";
|
|
651
|
+
} | {
|
|
652
|
+
kind: "move";
|
|
653
|
+
toSection: string;
|
|
654
|
+
fields?: Record<string, unknown>;
|
|
655
|
+
} | {
|
|
656
|
+
kind: "mutate";
|
|
657
|
+
fields: Record<string, unknown>;
|
|
658
|
+
};
|
|
659
|
+
|
|
529
660
|
type DetailContext = {
|
|
530
661
|
item: GHItem;
|
|
531
662
|
kind: "pr" | "issue";
|
|
@@ -629,6 +760,25 @@ type TabForStatus = (to: {
|
|
|
629
760
|
name: string;
|
|
630
761
|
category?: string;
|
|
631
762
|
}) => string | null;
|
|
763
|
+
/**
|
|
764
|
+
* What a move hands back when the screen took it optimistically: the request's
|
|
765
|
+
* two possible answers, reported so the pending patch can stop showing `◌` or
|
|
766
|
+
* be marked failed. Absent when the move was not taken optimistically, and the
|
|
767
|
+
* caller falls back to its own flashes.
|
|
768
|
+
*/
|
|
769
|
+
type OptimisticHandle = {
|
|
770
|
+
settled: () => void;
|
|
771
|
+
failed: (err: unknown) => void;
|
|
772
|
+
};
|
|
773
|
+
/**
|
|
774
|
+
* A row action said ahead of the server: the end state it leaves the row in,
|
|
775
|
+
* and the verb the failure line names it by — `close` reads back as
|
|
776
|
+
* "Couldn't close #412".
|
|
777
|
+
*/
|
|
778
|
+
type OptimisticChange = {
|
|
779
|
+
patch: Patch;
|
|
780
|
+
action: string;
|
|
781
|
+
};
|
|
632
782
|
/**
|
|
633
783
|
* Resolve the live transitions for a ticket, called lazily when its move
|
|
634
784
|
* submenu opens — never at board fetch, where one call per visible ticket
|
|
@@ -766,6 +916,15 @@ declare const buildActions: (item: AnyItem, login: string, showFlash: (msg: stri
|
|
|
766
916
|
* the settlement signal.
|
|
767
917
|
*/
|
|
768
918
|
onSettled?: (item: GHItem, result: SettledResult) => void;
|
|
919
|
+
/**
|
|
920
|
+
* Take the row's action ahead of the server — see `OptimisticChange`. A
|
|
921
|
+
* handle back means the row is already where the action puts it, so the
|
|
922
|
+
* request's answer goes to the handle and a failure goes ONLY there: the
|
|
923
|
+
* failed patch has its own sticky line with `w` beside it, and a flash
|
|
924
|
+
* naming the same failure would be a second voice saying it less well.
|
|
925
|
+
* Absent, every action keeps the pessimistic shape it had.
|
|
926
|
+
*/
|
|
927
|
+
optimistic?: (item: AnyItem, change: OptimisticChange) => OptimisticHandle | undefined;
|
|
769
928
|
/**
|
|
770
929
|
* Open the ticket's move submenu through the live-transitions hook. Passed
|
|
771
930
|
* by the browse screen (which owns the menu the rows open into) only when
|
|
@@ -831,7 +990,7 @@ declare const ActionMenu: ({ item, actions, cursor, cols, }: {
|
|
|
831
990
|
cols?: number;
|
|
832
991
|
}) => React.JSX.Element;
|
|
833
992
|
declare const signatureOf: (sections: Section[]) => string;
|
|
834
|
-
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, }: {
|
|
993
|
+
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, }: {
|
|
835
994
|
fetcher: () => Promise<{
|
|
836
995
|
sections: Section[];
|
|
837
996
|
login: string;
|
|
@@ -852,6 +1011,15 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
|
|
|
852
1011
|
* data that goes stale the same way the rows do.
|
|
853
1012
|
*/
|
|
854
1013
|
sidebar?: Rails;
|
|
1014
|
+
/**
|
|
1015
|
+
* What the reader should look at next, in the host's words — see
|
|
1016
|
+
* `FocusSlot`. Part of the fetch result for the same reason the rail is:
|
|
1017
|
+
* it goes stale with the rows, and the cache paints it beside them.
|
|
1018
|
+
*
|
|
1019
|
+
* Absent means the host has no focus feature and no row is drawn; `null`
|
|
1020
|
+
* means nothing to point at and draws the dim empty line.
|
|
1021
|
+
*/
|
|
1022
|
+
focus?: FocusSlot | null;
|
|
855
1023
|
}>;
|
|
856
1024
|
cacheKey?: string;
|
|
857
1025
|
/**
|
|
@@ -894,6 +1062,16 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
|
|
|
894
1062
|
/** What to call the row before its first answer arrives with a title. */
|
|
895
1063
|
stripLabel?: string;
|
|
896
1064
|
stripPollMs?: number;
|
|
1065
|
+
/**
|
|
1066
|
+
* What to call the focus row. Passing this (or `focusEmpty`) reserves the
|
|
1067
|
+
* row from the first paint — `loading…` until the fetch lands — while a
|
|
1068
|
+
* host that never wires the feature passes neither and draws nothing.
|
|
1069
|
+
* Vocabulary, not data: it never goes stale, so it is a prop rather than
|
|
1070
|
+
* part of the fetch result.
|
|
1071
|
+
*/
|
|
1072
|
+
focusLabel?: string;
|
|
1073
|
+
/** The empty sentence, drawn dim after the label when focus comes back null. */
|
|
1074
|
+
focusEmpty?: string;
|
|
897
1075
|
/**
|
|
898
1076
|
* A file something else touches when it has changed GitHub on your behalf —
|
|
899
1077
|
* a Claude session closing an issue, a script merging a PR. Touching it makes
|
|
@@ -946,4 +1124,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
|
|
|
946
1124
|
*/
|
|
947
1125
|
declare const parsePatterns: (value: string) => string[];
|
|
948
1126
|
|
|
949
|
-
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 };
|
|
1127
|
+
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_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 }),
|
|
@@ -1054,9 +1058,9 @@ var PrRow = ({
|
|
|
1054
1058
|
justifyContent: "flex-end",
|
|
1055
1059
|
flexShrink: 0,
|
|
1056
1060
|
children: sizeParts ? /* @__PURE__ */ jsxs(Text3, { children: [
|
|
1057
|
-
/* @__PURE__ */ jsx(Text3, { color: colors.
|
|
1061
|
+
/* @__PURE__ */ jsx(Text3, { color: colors.added, children: sizeParts.added }),
|
|
1058
1062
|
" ",
|
|
1059
|
-
/* @__PURE__ */ jsx(Text3, { color: colors.
|
|
1063
|
+
/* @__PURE__ */ jsx(Text3, { color: colors.removed, children: sizeParts.removed })
|
|
1060
1064
|
] }) : null
|
|
1061
1065
|
}
|
|
1062
1066
|
) : null,
|
|
@@ -1335,20 +1339,12 @@ var lookupRows = (claim, lookup, query, start, openUrl) => {
|
|
|
1335
1339
|
if (lookup?.phase === "failed")
|
|
1336
1340
|
return {
|
|
1337
1341
|
exclusive: true,
|
|
1338
|
-
rows: [
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
claim.label,
|
|
1345
|
-
" \xB7 ",
|
|
1346
|
-
lookup.message
|
|
1347
|
-
] }),
|
|
1348
|
-
run: () => start(query, claim),
|
|
1349
|
-
keepOpen: true
|
|
1350
|
-
}
|
|
1351
|
-
]
|
|
1342
|
+
rows: [],
|
|
1343
|
+
message: {
|
|
1344
|
+
text: `couldn't look up ${claim.label} \xB7 ${lookup.message} \xB7 \u21B5 retry`,
|
|
1345
|
+
tone: "error",
|
|
1346
|
+
onSubmit: () => start(query, claim)
|
|
1347
|
+
}
|
|
1352
1348
|
};
|
|
1353
1349
|
const rows = [
|
|
1354
1350
|
{
|
|
@@ -1380,7 +1376,7 @@ var lookupRows = (claim, lookup, query, start, openUrl) => {
|
|
|
1380
1376
|
|
|
1381
1377
|
// src/inbox/cache.ts
|
|
1382
1378
|
var cacheTtlMs = () => inboxConfig().cacheTtlMs;
|
|
1383
|
-
var CACHE_VERSION =
|
|
1379
|
+
var CACHE_VERSION = 6;
|
|
1384
1380
|
var cacheDir = () => join(
|
|
1385
1381
|
process.env.XDG_CACHE_HOME || join(homedir(), ".cache"),
|
|
1386
1382
|
inboxConfig().cacheNamespace
|
|
@@ -1391,11 +1387,14 @@ var readCache = (key) => {
|
|
|
1391
1387
|
const raw = JSON.parse(readFileSync(cacheFile(key), "utf8"));
|
|
1392
1388
|
if (raw?.version !== CACHE_VERSION) return null;
|
|
1393
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;
|
|
1394
1392
|
return {
|
|
1395
1393
|
sections: raw.sections,
|
|
1396
1394
|
login: raw.login ?? "",
|
|
1397
1395
|
at: raw.at ?? 0,
|
|
1398
1396
|
budget: raw.budget,
|
|
1397
|
+
...raw.focus !== void 0 ? { focus: raw.focus } : {},
|
|
1399
1398
|
...railsOf(raw.sidebar).every((s) => Array.isArray(s?.rows)) && raw.sidebar !== void 0 ? { sidebar: raw.sidebar } : {}
|
|
1400
1399
|
};
|
|
1401
1400
|
} catch {
|
|
@@ -1694,8 +1693,59 @@ var failureNotice = (failed) => {
|
|
|
1694
1693
|
const more = failed.length > 1 ? ` +${failed.length - 1} more` : "";
|
|
1695
1694
|
return `\u2717 Couldn't ${newest.action} ${newest.label}: ${newest.failure?.reason ?? "no reason reported"}${more} w restore`;
|
|
1696
1695
|
};
|
|
1696
|
+
var FOCUS_MARK = "\u{F04FE}";
|
|
1697
|
+
var DEFAULT_FOCUS_LABEL = "focus";
|
|
1698
|
+
var DEFAULT_FOCUS_EMPTY = "nothing to focus on";
|
|
1699
|
+
var focusCellFor = (focus, url) => {
|
|
1700
|
+
if (focus === void 0) return void 0;
|
|
1701
|
+
if (!focus || url !== focus.target.url) return null;
|
|
1702
|
+
return { mark: focus.mark ?? FOCUS_MARK, color: focus.markColor ?? colors.accent };
|
|
1703
|
+
};
|
|
1704
|
+
var focusRefColor = (focus) => focus.refColor ?? (focus.ref.startsWith("#") ? colors.accent : colors.ticket);
|
|
1705
|
+
var cells = (s) => [...s].length;
|
|
1706
|
+
var titleBudgetOf = (focus, label, width) => {
|
|
1707
|
+
const mark = focus.mark ?? FOCUS_MARK;
|
|
1708
|
+
const head = 2 + cells(mark) + 1 + cells(label) + 2 + (focus.marker ? cells(focus.marker) + 1 : 0) + cells(focus.ref) + 2;
|
|
1709
|
+
const tail = 3 + cells(focus.reason);
|
|
1710
|
+
const titleMax = width - head - tail;
|
|
1711
|
+
if (titleMax >= 1)
|
|
1712
|
+
return { title: truncate(focus.title, titleMax), reason: focus.reason };
|
|
1713
|
+
return {
|
|
1714
|
+
title: "",
|
|
1715
|
+
reason: truncate(focus.reason, Math.max(0, width - head - 3 - 1))
|
|
1716
|
+
};
|
|
1717
|
+
};
|
|
1718
|
+
var FocusSlotLine = ({
|
|
1719
|
+
focus,
|
|
1720
|
+
label = DEFAULT_FOCUS_LABEL,
|
|
1721
|
+
empty = DEFAULT_FOCUS_EMPTY,
|
|
1722
|
+
width
|
|
1723
|
+
}) => {
|
|
1724
|
+
if (focus === void 0)
|
|
1725
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
1726
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: " \xB7 " }),
|
|
1727
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} loading\u2026` })
|
|
1728
|
+
] });
|
|
1729
|
+
if (focus === null)
|
|
1730
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
1731
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${FOCUS_MARK} ` }),
|
|
1732
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} ${empty}` })
|
|
1733
|
+
] });
|
|
1734
|
+
const mark = focus.mark ?? FOCUS_MARK;
|
|
1735
|
+
const { title, reason } = titleBudgetOf(focus, label, width);
|
|
1736
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
1737
|
+
/* @__PURE__ */ jsx(Text, { children: " " }),
|
|
1738
|
+
/* @__PURE__ */ jsx(Text, { color: focus.markColor ?? colors.accent, bold: true, children: `${mark} ` }),
|
|
1739
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: `${label} ` }),
|
|
1740
|
+
focus.marker ? /* @__PURE__ */ jsx(Text, { children: `${focus.marker} ` }) : null,
|
|
1741
|
+
/* @__PURE__ */ jsx(Text, { color: focusRefColor(focus), bold: true, children: `${focus.ref} ` }),
|
|
1742
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: title }),
|
|
1743
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: ` ${reason}` })
|
|
1744
|
+
] });
|
|
1745
|
+
};
|
|
1697
1746
|
var quietly = $({ quiet: true });
|
|
1698
1747
|
var isMenuGap = (row) => "gap" in row;
|
|
1748
|
+
var rowLabelOf = (item) => item.kind === "task" ? item.ticket ?? item.key : item.kind === "pr" || item.kind === "issue" ? `#${item.number}` : "";
|
|
1699
1749
|
var jiraMovesCache = /* @__PURE__ */ new Map();
|
|
1700
1750
|
var cachedJiraMoves = (ticket, status) => {
|
|
1701
1751
|
const entry = jiraMovesCache.get(ticket);
|
|
@@ -2297,6 +2347,19 @@ var workCount = (s) => {
|
|
|
2297
2347
|
}
|
|
2298
2348
|
return n;
|
|
2299
2349
|
};
|
|
2350
|
+
var countedSections = (sections, departing) => {
|
|
2351
|
+
if (!departing?.size) return sections;
|
|
2352
|
+
const sent = new Set(sections.map((s) => s.id));
|
|
2353
|
+
return sections.map((s) => {
|
|
2354
|
+
const items = s.items.filter((item) => {
|
|
2355
|
+
const key = keyOf(item);
|
|
2356
|
+
if (!key || !departing.has(key)) return true;
|
|
2357
|
+
const to = departing.get(key);
|
|
2358
|
+
return to != null && (to === s.id || !sent.has(to));
|
|
2359
|
+
});
|
|
2360
|
+
return items.length === s.items.length ? s : { ...s, items };
|
|
2361
|
+
});
|
|
2362
|
+
};
|
|
2300
2363
|
var drillCmd = (item) => {
|
|
2301
2364
|
if (item.kind === "task")
|
|
2302
2365
|
return item.ticket ? `jira issue view ${item.ticket}` : null;
|
|
@@ -2360,6 +2423,7 @@ var openInJira = (jiraBase, key, showFlash) => {
|
|
|
2360
2423
|
});
|
|
2361
2424
|
showFlash(`\u2197 Opened ${key} in Jira`);
|
|
2362
2425
|
};
|
|
2426
|
+
var REMOVE_ME_AS_REVIEWER = "Remove me as reviewer";
|
|
2363
2427
|
var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions, onRefresh, onRemove, onOpenView, ext) => {
|
|
2364
2428
|
if (item.kind === "repo-header" || item.kind === "subgroup-header" || item.kind === "show-more" || item.kind === "show-less")
|
|
2365
2429
|
return [];
|
|
@@ -2464,6 +2528,32 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2464
2528
|
}
|
|
2465
2529
|
const actions = drillAction ? [drillAction, open, copyUrl] : [open, copyUrl];
|
|
2466
2530
|
const closing = [];
|
|
2531
|
+
const removeAhead = (opts) => {
|
|
2532
|
+
const row = item;
|
|
2533
|
+
const held = ext?.optimistic?.(row, {
|
|
2534
|
+
patch: { kind: "remove" },
|
|
2535
|
+
action: opts.action
|
|
2536
|
+
});
|
|
2537
|
+
onRemove?.(row);
|
|
2538
|
+
showFlash(opts.taking);
|
|
2539
|
+
void opts.request().then(
|
|
2540
|
+
() => {
|
|
2541
|
+
held?.settled();
|
|
2542
|
+
if (opts.done) showFlash(opts.done);
|
|
2543
|
+
if (held || opts.refreshes) ext?.onActed?.();
|
|
2544
|
+
if (opts.settles) ext?.onSettled?.(row, { ok: true });
|
|
2545
|
+
},
|
|
2546
|
+
(e) => {
|
|
2547
|
+
if (held) held.failed(e);
|
|
2548
|
+
else {
|
|
2549
|
+
showFlash(opts.refused(e));
|
|
2550
|
+
onRefresh?.();
|
|
2551
|
+
}
|
|
2552
|
+
if (opts.settles)
|
|
2553
|
+
ext?.onSettled?.(row, { ok: false, reason: firstLine(e) });
|
|
2554
|
+
}
|
|
2555
|
+
);
|
|
2556
|
+
};
|
|
2467
2557
|
actions.push({
|
|
2468
2558
|
label: "Copy repo name",
|
|
2469
2559
|
hint: "r",
|
|
@@ -2486,7 +2576,7 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2486
2576
|
});
|
|
2487
2577
|
if (item.kind === "pr" && item.standing === "queued" && login) {
|
|
2488
2578
|
actions.push({
|
|
2489
|
-
label:
|
|
2579
|
+
label: REMOVE_ME_AS_REVIEWER,
|
|
2490
2580
|
hint: "x",
|
|
2491
2581
|
// Quiet, beside Unsubscribe: both are "less of this PR for me" — no more
|
|
2492
2582
|
// review requests, no more notifications. Guarded by the caller's own
|
|
@@ -2494,25 +2584,17 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2494
2584
|
// review request there is no such row, and none is invented here.
|
|
2495
2585
|
group: "quiet",
|
|
2496
2586
|
icon: "\uF468",
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
onRefresh?.();
|
|
2509
|
-
ext?.onSettled?.(item, {
|
|
2510
|
-
ok: false,
|
|
2511
|
-
reason: firstLine(e)
|
|
2512
|
-
});
|
|
2513
|
-
}
|
|
2514
|
-
);
|
|
2515
|
-
}
|
|
2587
|
+
// Optimistic, the same shape Close uses. A row that lingers for a round
|
|
2588
|
+
// trip reads as a broken keypress.
|
|
2589
|
+
run: () => removeAhead({
|
|
2590
|
+
action: "remove you from",
|
|
2591
|
+
request: () => quietly`gh pr edit ${item.number} --repo ${item.repo} --remove-reviewer ${login}`,
|
|
2592
|
+
taking: `\u22EF Removing you from #${item.number}\u2026`,
|
|
2593
|
+
done: `\u2713 Removed you as reviewer on #${item.number}`,
|
|
2594
|
+
refused: (e) => `\u2717 #${item.number}: ${explainGhAction(e)}`,
|
|
2595
|
+
settles: true,
|
|
2596
|
+
refreshes: true
|
|
2597
|
+
})
|
|
2516
2598
|
});
|
|
2517
2599
|
}
|
|
2518
2600
|
if (item.kind === "pr" && item.branch) {
|
|
@@ -2584,23 +2666,13 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2584
2666
|
label: `Close #${item.number}`,
|
|
2585
2667
|
hint: "",
|
|
2586
2668
|
group: "confirm",
|
|
2587
|
-
run: () => {
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
(e) => {
|
|
2595
|
-
showFlash(`\u2717 Close failed \u2014 restoring #${item.number}`);
|
|
2596
|
-
onRefresh?.();
|
|
2597
|
-
ext?.onSettled?.(item, {
|
|
2598
|
-
ok: false,
|
|
2599
|
-
reason: firstLine(e)
|
|
2600
|
-
});
|
|
2601
|
-
}
|
|
2602
|
-
);
|
|
2603
|
-
}
|
|
2669
|
+
run: () => removeAhead({
|
|
2670
|
+
action: "close",
|
|
2671
|
+
request: () => quietly`gh issue close ${item.number} --repo ${item.repo}`,
|
|
2672
|
+
taking: `\u2713 Closed #${item.number}`,
|
|
2673
|
+
refused: () => `\u2717 Close failed \u2014 restoring #${item.number}`,
|
|
2674
|
+
settles: true
|
|
2675
|
+
})
|
|
2604
2676
|
},
|
|
2605
2677
|
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2606
2678
|
} }
|
|
@@ -2651,23 +2723,13 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2651
2723
|
label: `Close #${item.number}`,
|
|
2652
2724
|
hint: "",
|
|
2653
2725
|
group: "confirm",
|
|
2654
|
-
run: () => {
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
(e) => {
|
|
2662
|
-
showFlash(`\u2717 Close failed \u2014 restoring #${item.number}`);
|
|
2663
|
-
onRefresh?.();
|
|
2664
|
-
ext?.onSettled?.(item, {
|
|
2665
|
-
ok: false,
|
|
2666
|
-
reason: firstLine(e)
|
|
2667
|
-
});
|
|
2668
|
-
}
|
|
2669
|
-
);
|
|
2670
|
-
}
|
|
2726
|
+
run: () => removeAhead({
|
|
2727
|
+
action: "close",
|
|
2728
|
+
request: () => quietly`gh pr close ${item.number} --repo ${item.repo}`,
|
|
2729
|
+
taking: `\u2713 Closed #${item.number}`,
|
|
2730
|
+
refused: () => `\u2717 Close failed \u2014 restoring #${item.number}`,
|
|
2731
|
+
settles: true
|
|
2732
|
+
})
|
|
2671
2733
|
},
|
|
2672
2734
|
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2673
2735
|
} }
|
|
@@ -2688,16 +2750,23 @@ var buildActions = (item, login, showFlash, jiraBase, jiraKeyRe, jiraTransitions
|
|
|
2688
2750
|
label: `Close #${item.number} and delete ${item.branch}`,
|
|
2689
2751
|
hint: "",
|
|
2690
2752
|
group: "confirm",
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2753
|
+
// The patch covers the close and nothing else. A branch that will
|
|
2754
|
+
// not delete leaves a PR that IS closed, so reporting "Couldn't
|
|
2755
|
+
// close #412" and offering `w` would restore a row the server no
|
|
2756
|
+
// longer has; the branch gets its own flash instead.
|
|
2757
|
+
run: () => removeAhead({
|
|
2758
|
+
action: "close",
|
|
2759
|
+
request: async () => {
|
|
2760
|
+
await quietly`gh pr close ${item.number} --repo ${item.repo}`;
|
|
2761
|
+
void quietly`gh api -X DELETE ${`repos/${item.repo}/git/refs/heads/${item.branch}`}`.catch(
|
|
2762
|
+
() => showFlash(
|
|
2763
|
+
`\u2717 Closed #${item.number}, but couldn't delete ${item.branch}`
|
|
2764
|
+
)
|
|
2765
|
+
);
|
|
2766
|
+
},
|
|
2767
|
+
taking: `\u2713 Closed #${item.number} and deleted ${item.branch}`,
|
|
2768
|
+
refused: () => `\u2717 Close + delete failed \u2014 restoring`
|
|
2769
|
+
})
|
|
2701
2770
|
},
|
|
2702
2771
|
{ label: "Cancel", hint: "", group: "confirm", run: () => {
|
|
2703
2772
|
} }
|
|
@@ -2899,7 +2968,8 @@ var ItemRow = ({
|
|
|
2899
2968
|
uniformLabels = [],
|
|
2900
2969
|
columns,
|
|
2901
2970
|
shed,
|
|
2902
|
-
numberCols
|
|
2971
|
+
numberCols,
|
|
2972
|
+
focus
|
|
2903
2973
|
}) => {
|
|
2904
2974
|
const backdropped = useBackdropped();
|
|
2905
2975
|
const selBg = selectionBackground(active, backdropped);
|
|
@@ -2943,12 +3013,16 @@ var ItemRow = ({
|
|
|
2943
3013
|
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";
|
|
2944
3014
|
const titleMax = Math.max(
|
|
2945
3015
|
1,
|
|
2946
|
-
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) -
|
|
3016
|
+
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
|
|
3017
|
+
// url-bearing row while a focus is on screen, none at all without one.
|
|
3018
|
+
(focus !== void 0 ? 2 : 0) - 12
|
|
2947
3019
|
);
|
|
3020
|
+
const focusCell = focusCellFor(focus, item.url);
|
|
2948
3021
|
return /* @__PURE__ */ jsxs(Box, { marginTop: gap ? 1 : 0, width: cols, backgroundColor: selBg, children: [
|
|
2949
3022
|
/* @__PURE__ */ jsx(Text3, { color: colors.info, children: active ? "\u276F " : " " }),
|
|
2950
3023
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: prefix }),
|
|
2951
3024
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: parent ? "\u252C " : " " }),
|
|
3025
|
+
focusCell !== void 0 ? focusCell ? /* @__PURE__ */ jsx(Text3, { color: focusCell.color, bold: true, children: `${focusCell.mark} ` }) : /* @__PURE__ */ jsx(Text3, { children: " " }) : null,
|
|
2952
3026
|
pending ? /* @__PURE__ */ jsx(Text3, { dimColor: true, children: transitIcon + " " }) : /* @__PURE__ */ jsx(Text3, { bold: true, color: transient ? TRANSIT_COLOUR[transient] : void 0, children: transitIcon + " " }),
|
|
2953
3027
|
markerCols > 0 ? /* @__PURE__ */ jsx(Text3, { color: item.markerColor, children: (item.marker ?? "").padEnd(markerCols) + " " }) : null,
|
|
2954
3028
|
/* @__PURE__ */ jsx(Text3, { ...taskKeyStyle(), children: item.key + " " }),
|
|
@@ -2983,7 +3057,7 @@ var ItemRow = ({
|
|
|
2983
3057
|
const icon = merged ? {
|
|
2984
3058
|
glyph: MERGED_FRAMES[sparkFrame % MERGED_FRAMES.length],
|
|
2985
3059
|
color: MERGED_COLOUR
|
|
2986
|
-
} : transient ? {
|
|
3060
|
+
} : pending ? { glyph: PENDING_GLYPH, color: colors.muted } : transient ? {
|
|
2987
3061
|
glyph: isDeparture(transient) ? TRANSIT_OUT_FRAMES[rampAt(TRANSIT_OUT_FRAMES.length)] : isArrival(transient) ? TRANSIT_IN_FRAMES[rampAt(TRANSIT_IN_FRAMES.length)] : displayFor(item.health).glyph,
|
|
2988
3062
|
color: TRANSIT_COLOUR[transient]
|
|
2989
3063
|
} : void 0;
|
|
@@ -3007,7 +3081,11 @@ var ItemRow = ({
|
|
|
3007
3081
|
numberCols,
|
|
3008
3082
|
icon,
|
|
3009
3083
|
motion,
|
|
3010
|
-
announcements
|
|
3084
|
+
announcements,
|
|
3085
|
+
focusCell: focusCellFor(
|
|
3086
|
+
focus,
|
|
3087
|
+
"url" in item ? item.url : void 0
|
|
3088
|
+
)
|
|
3011
3089
|
}
|
|
3012
3090
|
);
|
|
3013
3091
|
};
|
|
@@ -3043,17 +3121,18 @@ var useActionMenu = () => {
|
|
|
3043
3121
|
};
|
|
3044
3122
|
const handleKey = (key) => {
|
|
3045
3123
|
if (!actions) return false;
|
|
3124
|
+
const live = (n) => n >= 0 && n < actions.length && !isMenuGap(actions[n]);
|
|
3046
3125
|
if (key.upArrow)
|
|
3047
3126
|
setCursor((c) => {
|
|
3048
3127
|
let n = c - 1;
|
|
3049
3128
|
while (n > 0 && isMenuGap(actions[n])) n -= 1;
|
|
3050
|
-
return
|
|
3129
|
+
return live(n) ? n : c;
|
|
3051
3130
|
});
|
|
3052
3131
|
if (key.downArrow)
|
|
3053
3132
|
setCursor((c) => {
|
|
3054
3133
|
let n = c + 1;
|
|
3055
3134
|
while (n < actions.length - 1 && isMenuGap(actions[n])) n += 1;
|
|
3056
|
-
return
|
|
3135
|
+
return live(n) ? n : c;
|
|
3057
3136
|
});
|
|
3058
3137
|
if (key.return) {
|
|
3059
3138
|
const row = actions[cursor];
|
|
@@ -3210,6 +3289,7 @@ var LEGEND_FOOTNOTE = "Each item lands in the FIRST tab that claims it, so count
|
|
|
3210
3289
|
var HelpModal = ({
|
|
3211
3290
|
extensions,
|
|
3212
3291
|
hasJira,
|
|
3292
|
+
hasFocus,
|
|
3213
3293
|
tabHelp,
|
|
3214
3294
|
maxRows,
|
|
3215
3295
|
scroll = 0,
|
|
@@ -3244,6 +3324,7 @@ var HelpModal = ({
|
|
|
3244
3324
|
["f", "filter by repo"],
|
|
3245
3325
|
["r", "refresh"],
|
|
3246
3326
|
["w", "restore a change that failed"],
|
|
3327
|
+
...hasFocus ? [["g", "jump to focus"]] : [],
|
|
3247
3328
|
...extensionLegend(extensions),
|
|
3248
3329
|
["?", "this help"],
|
|
3249
3330
|
["q", "quit"]
|
|
@@ -3439,6 +3520,9 @@ var BrowseScreen = ({
|
|
|
3439
3520
|
ciJob,
|
|
3440
3521
|
stripState,
|
|
3441
3522
|
stripLabel,
|
|
3523
|
+
focus,
|
|
3524
|
+
focusLabel,
|
|
3525
|
+
focusEmpty,
|
|
3442
3526
|
tabHelp,
|
|
3443
3527
|
origin,
|
|
3444
3528
|
budget,
|
|
@@ -3453,6 +3537,8 @@ var BrowseScreen = ({
|
|
|
3453
3537
|
sidebar,
|
|
3454
3538
|
liveLabel,
|
|
3455
3539
|
optimisticMove,
|
|
3540
|
+
optimistic,
|
|
3541
|
+
departing,
|
|
3456
3542
|
pendingKeys,
|
|
3457
3543
|
failure,
|
|
3458
3544
|
onRestore
|
|
@@ -3488,12 +3574,13 @@ var BrowseScreen = ({
|
|
|
3488
3574
|
const filterActive = search != null || repoFilter.size > 0;
|
|
3489
3575
|
const reserveCiRow = ciStatusState != null;
|
|
3490
3576
|
const reserveStripRow = stripState != null;
|
|
3577
|
+
const reserveFocusRow = focusLabel !== void 0 || focusEmpty !== void 0 || focus !== void 0;
|
|
3491
3578
|
const ciStatus = ciStatusState?.kind === "ready" ? ciStatusState.status : null;
|
|
3492
3579
|
const listHeight = Math.max(
|
|
3493
3580
|
5,
|
|
3494
3581
|
// -10 rather than -8: the extra 2 are the frame's top and bottom border
|
|
3495
3582
|
// rows, so the tree never grows taller than the terminal inside the frame.
|
|
3496
|
-
rows - 10 - (filterActive ? 2 : 0) - (reserveCiRow ? 2 : 0) - (reserveStripRow ? 2 : 0)
|
|
3583
|
+
rows - 10 - (filterActive ? 2 : 0) - (reserveCiRow ? 2 : 0) - (reserveStripRow ? 2 : 0) - (reserveFocusRow ? 2 : 0)
|
|
3497
3584
|
);
|
|
3498
3585
|
useEffect(() => {
|
|
3499
3586
|
setLocalSections(applyWork(sections));
|
|
@@ -3578,6 +3665,14 @@ var BrowseScreen = ({
|
|
|
3578
3665
|
const timer = setTimeout(() => setDismissedSeq(seq), FAILURE_NOTICE_MS);
|
|
3579
3666
|
return () => clearTimeout(timer);
|
|
3580
3667
|
}, [failure?.seq]);
|
|
3668
|
+
const counted = useMemo(
|
|
3669
|
+
() => countedSections(localSections, departing),
|
|
3670
|
+
[localSections, departing]
|
|
3671
|
+
);
|
|
3672
|
+
const countOf = (id) => {
|
|
3673
|
+
const s = counted.find((c) => c.id === id);
|
|
3674
|
+
return s ? workCount(s) : 0;
|
|
3675
|
+
};
|
|
3581
3676
|
const markedTabs = useMemo(() => {
|
|
3582
3677
|
const out = /* @__PURE__ */ new Set();
|
|
3583
3678
|
if (!transients?.size) return out;
|
|
@@ -3631,7 +3726,10 @@ var BrowseScreen = ({
|
|
|
3631
3726
|
cols: rowCols,
|
|
3632
3727
|
uniformLabels,
|
|
3633
3728
|
columns: trailingColumns,
|
|
3634
|
-
numberCols
|
|
3729
|
+
numberCols,
|
|
3730
|
+
// Presence only: every row holds the same two gutter columns while a
|
|
3731
|
+
// focus is on screen, so the shed ladder prices the cost once for all.
|
|
3732
|
+
focusGutter: focus !== void 0
|
|
3635
3733
|
}
|
|
3636
3734
|
);
|
|
3637
3735
|
const [sparkFrame, setSparkFrame] = useState(0);
|
|
@@ -3713,31 +3811,34 @@ var BrowseScreen = ({
|
|
|
3713
3811
|
optimistic: optimisticMove ? (to) => optimisticMove(task, to) : void 0
|
|
3714
3812
|
});
|
|
3715
3813
|
};
|
|
3814
|
+
const actionsFor = (item, flash2) => buildActions(
|
|
3815
|
+
item,
|
|
3816
|
+
login,
|
|
3817
|
+
flash2,
|
|
3818
|
+
jiraBase,
|
|
3819
|
+
jiraKeyRe,
|
|
3820
|
+
jiraTransitions,
|
|
3821
|
+
onRefresh,
|
|
3822
|
+
dismissItem,
|
|
3823
|
+
openDrillView,
|
|
3824
|
+
{
|
|
3825
|
+
extensions,
|
|
3826
|
+
onOpenExt,
|
|
3827
|
+
onActed,
|
|
3828
|
+
onSettled,
|
|
3829
|
+
optimistic,
|
|
3830
|
+
onOpenMove: jiraTransitionsFor ? openMoveFor : void 0
|
|
3831
|
+
}
|
|
3832
|
+
);
|
|
3716
3833
|
const openMenu = () => {
|
|
3717
3834
|
if (!activeItem || activeItem.kind === "repo-header" || activeItem.kind === "subgroup-header")
|
|
3718
3835
|
return;
|
|
3719
|
-
|
|
3720
|
-
activeItem,
|
|
3721
|
-
login,
|
|
3722
|
-
(msg) => {
|
|
3836
|
+
menu.open(
|
|
3837
|
+
actionsFor(activeItem, (msg) => {
|
|
3723
3838
|
menu.close();
|
|
3724
3839
|
showFlash(msg);
|
|
3725
|
-
}
|
|
3726
|
-
jiraBase,
|
|
3727
|
-
jiraKeyRe,
|
|
3728
|
-
jiraTransitions,
|
|
3729
|
-
onRefresh,
|
|
3730
|
-
dismissItem,
|
|
3731
|
-
openDrillView,
|
|
3732
|
-
{
|
|
3733
|
-
extensions,
|
|
3734
|
-
onOpenExt,
|
|
3735
|
-
onActed,
|
|
3736
|
-
onSettled,
|
|
3737
|
-
onOpenMove: jiraTransitionsFor ? openMoveFor : void 0
|
|
3738
|
-
}
|
|
3840
|
+
})
|
|
3739
3841
|
);
|
|
3740
|
-
menu.open(actions);
|
|
3741
3842
|
};
|
|
3742
3843
|
useInput((input, key) => {
|
|
3743
3844
|
const idleFor = Date.now() - lastKeyAt.current;
|
|
@@ -3868,6 +3969,56 @@ var BrowseScreen = ({
|
|
|
3868
3969
|
onRefresh?.();
|
|
3869
3970
|
return;
|
|
3870
3971
|
}
|
|
3972
|
+
if (input === "g" && focus) {
|
|
3973
|
+
const tabId = focus.target.tab;
|
|
3974
|
+
const at = localSections.findIndex((s) => s.id === tabId);
|
|
3975
|
+
if (at === -1) return;
|
|
3976
|
+
const drawn = (s) => {
|
|
3977
|
+
const one = search != null ? filterBySearch([s], search, login) : [s];
|
|
3978
|
+
const two = repoFilter.size > 0 ? filterByRepos(one, repoFilter, login) : one;
|
|
3979
|
+
return filterActive ? two[0]?.items ?? [] : s.items;
|
|
3980
|
+
};
|
|
3981
|
+
const urlOf = (i) => "url" in i ? i.url : void 0;
|
|
3982
|
+
let items = localSections[at].items;
|
|
3983
|
+
if (!items.some((i) => urlOf(i) === focus.target.url)) {
|
|
3984
|
+
const tail = items.find(
|
|
3985
|
+
(i) => i.kind === "show-more" && i.hidden.some((h) => h.url === focus.target.url)
|
|
3986
|
+
);
|
|
3987
|
+
if (!tail) {
|
|
3988
|
+
setTabIdx(at);
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
items = [
|
|
3992
|
+
...items.slice(0, items.indexOf(tail)),
|
|
3993
|
+
...tail.hidden,
|
|
3994
|
+
{
|
|
3995
|
+
kind: "show-less",
|
|
3996
|
+
toHide: tail.hidden,
|
|
3997
|
+
// The depth of the row it replaces, never a constant — a collapsed
|
|
3998
|
+
// row under a story sits deeper than 1, and pinning it breaks the
|
|
3999
|
+
// `treeUrls` walk exactly as the comment there warns.
|
|
4000
|
+
depth: depthOf(tail)
|
|
4001
|
+
},
|
|
4002
|
+
...items.slice(items.indexOf(tail) + 1)
|
|
4003
|
+
];
|
|
4004
|
+
const expanded = items;
|
|
4005
|
+
setLocalSections(
|
|
4006
|
+
(prev) => prev.map((s) => s.id === tabId ? { ...s, items: expanded } : s)
|
|
4007
|
+
);
|
|
4008
|
+
prevItems.current = { ...prevItems.current, [tabId]: expanded };
|
|
4009
|
+
}
|
|
4010
|
+
setTabIdx(at);
|
|
4011
|
+
const shown = drawn({ ...localSections[at], items });
|
|
4012
|
+
const dit = shown.findIndex((i) => urlOf(i) === focus.target.url);
|
|
4013
|
+
if (dit === -1) return;
|
|
4014
|
+
setCursors((p) => ({ ...p, [tabId]: dit }));
|
|
4015
|
+
setViewStarts((p) => {
|
|
4016
|
+
let vs = Math.min(p[tabId] ?? 0, withHeaders(shown, dit));
|
|
4017
|
+
while (dit >= vs + windowCount(shown, vs, listHeight)) vs++;
|
|
4018
|
+
return { ...p, [tabId]: vs };
|
|
4019
|
+
});
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
3871
4022
|
const ext = extensionFor(input, extensions);
|
|
3872
4023
|
if (ext) {
|
|
3873
4024
|
onOpenExt?.(ext.id, extensionTargetFor(activeItem));
|
|
@@ -4029,20 +4180,7 @@ var BrowseScreen = ({
|
|
|
4029
4180
|
return;
|
|
4030
4181
|
}
|
|
4031
4182
|
if (input === "x" && activeItem.kind === "pr" && activeItem.standing === "queued" && login) {
|
|
4032
|
-
|
|
4033
|
-
showFlash(`\u22EF Removing you from #${activeItem.number}\u2026`);
|
|
4034
|
-
void quietly`gh pr edit ${activeItem.number} --repo ${activeItem.repo} --remove-reviewer ${login}`.then(
|
|
4035
|
-
() => {
|
|
4036
|
-
showFlash(`\u2713 Removed you as reviewer on #${activeItem.number}`);
|
|
4037
|
-
onActed?.();
|
|
4038
|
-
onSettled?.(activeItem, { ok: true });
|
|
4039
|
-
},
|
|
4040
|
-
(e) => {
|
|
4041
|
-
showFlash(`\u2717 #${activeItem.number}: ${explainGhAction(e)}`);
|
|
4042
|
-
onRefresh?.();
|
|
4043
|
-
onSettled?.(activeItem, { ok: false, reason: firstLine(e) });
|
|
4044
|
-
}
|
|
4045
|
-
);
|
|
4183
|
+
actionsFor(activeItem, showFlash).find((a) => a.label === REMOVE_ME_AS_REVIEWER)?.run();
|
|
4046
4184
|
return;
|
|
4047
4185
|
}
|
|
4048
4186
|
if (input === "b" && activeItem.kind === "pr" && activeItem.branch) {
|
|
@@ -4296,6 +4434,7 @@ var BrowseScreen = ({
|
|
|
4296
4434
|
{
|
|
4297
4435
|
extensions,
|
|
4298
4436
|
hasJira: !!jiraBase,
|
|
4437
|
+
hasFocus: focus != null,
|
|
4299
4438
|
tabHelp,
|
|
4300
4439
|
maxRows: listHeight,
|
|
4301
4440
|
scroll: helpScroll,
|
|
@@ -4331,7 +4470,7 @@ var BrowseScreen = ({
|
|
|
4331
4470
|
InboxHeader,
|
|
4332
4471
|
{
|
|
4333
4472
|
brand,
|
|
4334
|
-
sections:
|
|
4473
|
+
sections: counted,
|
|
4335
4474
|
login,
|
|
4336
4475
|
scopeLabel: overlayOpen ? chrome?.scope ?? origin?.label : origin?.label,
|
|
4337
4476
|
budgetLabel: budgetNotice(budget)?.label,
|
|
@@ -4352,6 +4491,15 @@ var BrowseScreen = ({
|
|
|
4352
4491
|
width: COLS
|
|
4353
4492
|
}
|
|
4354
4493
|
) : null,
|
|
4494
|
+
reserveFocusRow ? /* @__PURE__ */ jsx(
|
|
4495
|
+
FocusSlotLine,
|
|
4496
|
+
{
|
|
4497
|
+
focus,
|
|
4498
|
+
label: focusLabel,
|
|
4499
|
+
empty: focusEmpty,
|
|
4500
|
+
width: COLS
|
|
4501
|
+
}
|
|
4502
|
+
) : null,
|
|
4355
4503
|
skippedForBudget ? /* @__PURE__ */ jsxs(Box, { marginBottom: 1, children: [
|
|
4356
4504
|
/* @__PURE__ */ jsx(Text3, { bold: true, color: colors.accent, children: " \u26A1 auto-refresh paused to save API budget" }),
|
|
4357
4505
|
/* @__PURE__ */ jsx(Text3, { dimColor: true, children: " r refreshes anyway" })
|
|
@@ -4360,10 +4508,12 @@ var BrowseScreen = ({
|
|
|
4360
4508
|
Tabs,
|
|
4361
4509
|
{
|
|
4362
4510
|
active: section.id,
|
|
4511
|
+
width: COLS,
|
|
4363
4512
|
items: localSections.map((s) => ({
|
|
4364
4513
|
value: s.id,
|
|
4365
4514
|
label: s.label,
|
|
4366
|
-
|
|
4515
|
+
icon: s.icon,
|
|
4516
|
+
count: countOf(s.id),
|
|
4367
4517
|
/*
|
|
4368
4518
|
* The denominator, and only where it can still be true.
|
|
4369
4519
|
*
|
|
@@ -4448,6 +4598,7 @@ var BrowseScreen = ({
|
|
|
4448
4598
|
columns: trailingColumns,
|
|
4449
4599
|
shed: trailingShed,
|
|
4450
4600
|
numberCols,
|
|
4601
|
+
focus,
|
|
4451
4602
|
gap: i > 0 && gapsAbove(section.items, viewStart + i)
|
|
4452
4603
|
},
|
|
4453
4604
|
`${viewStart + i}:${rowKey(item)}`
|
|
@@ -4543,6 +4694,8 @@ var App = ({
|
|
|
4543
4694
|
stripFetcher,
|
|
4544
4695
|
stripLabel,
|
|
4545
4696
|
stripPollMs = 3e5,
|
|
4697
|
+
focusLabel,
|
|
4698
|
+
focusEmpty,
|
|
4546
4699
|
watchPath,
|
|
4547
4700
|
watchDebounceMs = 400,
|
|
4548
4701
|
// Zero by default, deliberately. A package that adds randomness to its own
|
|
@@ -4725,6 +4878,7 @@ var App = ({
|
|
|
4725
4878
|
)
|
|
4726
4879
|
};
|
|
4727
4880
|
if (fresh.budget) setBudget(fresh.budget);
|
|
4881
|
+
if (fresh.focus !== void 0) setFocus(fresh.focus);
|
|
4728
4882
|
if (fresh.sidebar !== void 0) setSidebar(fresh.sidebar);
|
|
4729
4883
|
if (cacheKey) writeCache(cacheKey, fresh);
|
|
4730
4884
|
setRefreshing(false);
|
|
@@ -4835,6 +4989,26 @@ var App = ({
|
|
|
4835
4989
|
phase: "inflight"
|
|
4836
4990
|
});
|
|
4837
4991
|
} : void 0;
|
|
4992
|
+
const optimistic = (item, change) => {
|
|
4993
|
+
const key = keyOf(item);
|
|
4994
|
+
if (!key) return void 0;
|
|
4995
|
+
return hold(key, {
|
|
4996
|
+
patch: change.patch,
|
|
4997
|
+
action: change.action,
|
|
4998
|
+
label: rowLabelOf(item),
|
|
4999
|
+
since: Date.now(),
|
|
5000
|
+
phase: "inflight"
|
|
5001
|
+
});
|
|
5002
|
+
};
|
|
5003
|
+
const departing = useMemo(
|
|
5004
|
+
() => new Map(
|
|
5005
|
+
[...overrides.current].flatMap(
|
|
5006
|
+
([key, { patch }]) => patch.kind === "remove" ? [[key, null]] : patch.kind === "move" ? [[key, patch.toSection]] : []
|
|
5007
|
+
)
|
|
5008
|
+
),
|
|
5009
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5010
|
+
[overrideRev]
|
|
5011
|
+
);
|
|
4838
5012
|
const restore = () => {
|
|
4839
5013
|
const newest = failedOverrides(overrides.current)[0];
|
|
4840
5014
|
if (!newest) return;
|
|
@@ -4857,6 +5031,7 @@ var App = ({
|
|
|
4857
5031
|
displayedSections.current = cached.sections;
|
|
4858
5032
|
setFetchedAt(cached.at);
|
|
4859
5033
|
if (cached.sidebar) setSidebar(cached.sidebar);
|
|
5034
|
+
if (cached.focus !== void 0) setFocus(cached.focus);
|
|
4860
5035
|
setState({
|
|
4861
5036
|
phase: "browse",
|
|
4862
5037
|
sections: cached.sections,
|
|
@@ -4919,6 +5094,7 @@ var App = ({
|
|
|
4919
5094
|
if (shared && shared.at > firedAt) {
|
|
4920
5095
|
receive({ sections: shared.sections, login: shared.login });
|
|
4921
5096
|
if (shared.sidebar) setSidebar(shared.sidebar);
|
|
5097
|
+
if (shared.focus !== void 0) setFocus(shared.focus);
|
|
4922
5098
|
setFetchedAt(shared.at);
|
|
4923
5099
|
return;
|
|
4924
5100
|
}
|
|
@@ -4939,6 +5115,7 @@ var App = ({
|
|
|
4939
5115
|
);
|
|
4940
5116
|
const [appliedSummary, setAppliedSummary] = useState("");
|
|
4941
5117
|
const [sidebar, setSidebar] = useState(void 0);
|
|
5118
|
+
const [focus, setFocus] = useState(void 0);
|
|
4942
5119
|
const [skippedForBudget, setSkippedForBudget] = useState(false);
|
|
4943
5120
|
const [mergedUrls, setMergedUrls] = useState([]);
|
|
4944
5121
|
const [leavingUrls, setLeavingUrls] = useState([]);
|
|
@@ -4991,6 +5168,15 @@ var App = ({
|
|
|
4991
5168
|
width: COLS
|
|
4992
5169
|
}
|
|
4993
5170
|
) : null,
|
|
5171
|
+
focusLabel !== void 0 || focusEmpty !== void 0 ? /* @__PURE__ */ jsx(
|
|
5172
|
+
FocusSlotLine,
|
|
5173
|
+
{
|
|
5174
|
+
focus: void 0,
|
|
5175
|
+
label: focusLabel,
|
|
5176
|
+
empty: focusEmpty,
|
|
5177
|
+
width: COLS
|
|
5178
|
+
}
|
|
5179
|
+
) : null,
|
|
4994
5180
|
/* @__PURE__ */ jsx(
|
|
4995
5181
|
Box,
|
|
4996
5182
|
{
|
|
@@ -5070,6 +5256,8 @@ var App = ({
|
|
|
5070
5256
|
jiraTransitions,
|
|
5071
5257
|
jiraTransitionsFor,
|
|
5072
5258
|
optimisticMove,
|
|
5259
|
+
optimistic,
|
|
5260
|
+
departing,
|
|
5073
5261
|
pendingKeys,
|
|
5074
5262
|
failure,
|
|
5075
5263
|
onRestore: restore,
|
|
@@ -5101,6 +5289,9 @@ var App = ({
|
|
|
5101
5289
|
ciJob,
|
|
5102
5290
|
stripState: hasStrip ? stripState : void 0,
|
|
5103
5291
|
stripLabel,
|
|
5292
|
+
focus,
|
|
5293
|
+
focusLabel,
|
|
5294
|
+
focusEmpty,
|
|
5104
5295
|
tabHelp,
|
|
5105
5296
|
onOpenPr: (item) => {
|
|
5106
5297
|
setChromeSpec({});
|
|
@@ -5149,4 +5340,4 @@ var matchesFilter = (repo, filter) => {
|
|
|
5149
5340
|
};
|
|
5150
5341
|
var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
5151
5342
|
|
|
5152
|
-
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 };
|
|
5343
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, DEFAULT_FOCUS_EMPTY, DEFAULT_FOCUS_LABEL, 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.0",
|
|
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",
|