@kud/gh-ink 0.44.2 → 0.44.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -332
- package/dist/index.js +10 -200
- package/package.json +4 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
2
|
import { PrComments, PrHealthData, PrCheck, Health } from '@kud/gh';
|
|
3
|
-
import { StyledLine
|
|
3
|
+
import { StyledLine } from '@kud/ink-ui';
|
|
4
|
+
import { AnyItem, Section, CiStatus, GHItem, OriginSplit } from '@kud/gh-workflow';
|
|
5
|
+
export { AnyItem, CiStatus, GHDetail, GHItem, InboxConfig, OriginSplit, PIN_MARK, RepoHeader, RepoProfile, Section, ShowLess, ShowMore, Standing, SubgroupHeader, TaskRow, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } from '@kud/gh-workflow';
|
|
4
6
|
|
|
5
7
|
type CommentsPanelProps = {
|
|
6
8
|
repo: string;
|
|
@@ -183,210 +185,6 @@ declare const writeCache: (key: string, data: {
|
|
|
183
185
|
budget?: InboxBudget;
|
|
184
186
|
}) => void;
|
|
185
187
|
|
|
186
|
-
type GHDetail = {
|
|
187
|
-
reviewDecision?: string;
|
|
188
|
-
mergeable?: string;
|
|
189
|
-
checksPass: number;
|
|
190
|
-
checksFail: number;
|
|
191
|
-
checksPending: number;
|
|
192
|
-
checksStale?: number;
|
|
193
|
-
threadsTotal: number;
|
|
194
|
-
lastCommitAt?: string;
|
|
195
|
-
lastEventAt?: string;
|
|
196
|
-
};
|
|
197
|
-
type GHItem = {
|
|
198
|
-
kind: "pr" | "issue";
|
|
199
|
-
number: number;
|
|
200
|
-
title: string;
|
|
201
|
-
repo: string;
|
|
202
|
-
url: string;
|
|
203
|
-
branch?: string;
|
|
204
|
-
health: Health;
|
|
205
|
-
author?: string;
|
|
206
|
-
age: string;
|
|
207
|
-
activityAge?: string;
|
|
208
|
-
ts: number;
|
|
209
|
-
unresolved: number;
|
|
210
|
-
conversation: number;
|
|
211
|
-
lastActor?: string;
|
|
212
|
-
/**
|
|
213
|
-
* The item's labels, unordered and by name only — which is all GitHub gives
|
|
214
|
-
* without the timeline API. `labels(first: N)` orders by when the label was
|
|
215
|
-
* created IN THE REPO, never by when it was applied here, so "the newest two"
|
|
216
|
-
* is not derivable and the row ranks them against the host's `labelPriority`
|
|
217
|
-
* instead.
|
|
218
|
-
*
|
|
219
|
-
* Optional because a `minimal` fetch omits the selection entirely — these
|
|
220
|
-
* fields vanish rather than degrade — so undefined means "not asked for",
|
|
221
|
-
* which the row draws as nothing at all rather than as an unlabelled item.
|
|
222
|
-
*/
|
|
223
|
-
labels?: readonly string[];
|
|
224
|
-
detail?: GHDetail;
|
|
225
|
-
/**
|
|
226
|
-
* Where YOU stand on this row, when the host knows it per row rather than per
|
|
227
|
-
* tab. Set it and the whose-move band reads it instead of inferring from the
|
|
228
|
-
* section id — which is what lets one tab hold rows of mixed standing: a
|
|
229
|
-
* review asked of you and one you have already given differ only in whether
|
|
230
|
-
* the ball comes back, and that is a fact about the search a row came from,
|
|
231
|
-
* never about the PR. Left unset, the section id decides as before.
|
|
232
|
-
*/
|
|
233
|
-
standing?: Standing;
|
|
234
|
-
/**
|
|
235
|
-
* A turn the viewer has claimed BY HAND, outranking everything inferred.
|
|
236
|
-
*
|
|
237
|
-
* The inference below is good and still gets things wrong in one direction it
|
|
238
|
-
* cannot see: a row can be genuinely yours while every signal says otherwise —
|
|
239
|
-
* nothing red, no unresolved thread, nobody waiting on a word from you — and
|
|
240
|
-
* no amount of reading the PR harder will discover it. That knowledge lives
|
|
241
|
-
* with the viewer, so this is the field where they put it.
|
|
242
|
-
*
|
|
243
|
-
* It is a PIN, not a correction: it applies whether or not the row was already
|
|
244
|
-
* yours, which is why it does not pair with an opposite. The other direction —
|
|
245
|
-
* "no reply is owed for this particular comment" — is a fact about one event
|
|
246
|
-
* and belongs where the events are read, not here; a host expresses it by
|
|
247
|
-
* handing back a `lastActor` that no longer claims a turn.
|
|
248
|
-
*/
|
|
249
|
-
pinned?: boolean;
|
|
250
|
-
/**
|
|
251
|
-
* What this row STANDS FOR, when that differs from what it is. Absent means
|
|
252
|
-
* the row is a unit of work and is counted as one.
|
|
253
|
-
*
|
|
254
|
-
* `"container"` marks a row that exists to carry context rather than to be
|
|
255
|
-
* done: the initiative a story hangs under, drawn as a real row because it is
|
|
256
|
-
* still selectable, openable, and worth seeing until it closes — but not
|
|
257
|
-
* itself a thing on anyone's plate. Four stories under one container is four
|
|
258
|
-
* items, not five.
|
|
259
|
-
*
|
|
260
|
-
* Deliberately not `uncounted`. A host knows what a row IS; it should not have
|
|
261
|
-
* to know what the tab badge does with that, and a field named after one
|
|
262
|
-
* consumer starts lying the moment a second one reads it.
|
|
263
|
-
*
|
|
264
|
-
* Orthogonal to `depth`, which says where the row hangs rather than what it
|
|
265
|
-
* stands for. A container is a genuine depth-0 row with genuine depth-1
|
|
266
|
-
* children; spelling it as a depth would mean lying about the tree to fix a
|
|
267
|
-
* number, and every site that draws indentation reads that lie as truth.
|
|
268
|
-
*
|
|
269
|
-
* A union with one member rather than a boolean. `indent` was a boolean that
|
|
270
|
-
* turned out to need a scalar, and widening it cost a deprecation that is
|
|
271
|
-
* still in this file. Adding a second role here is additive; turning a boolean
|
|
272
|
-
* into a union is not.
|
|
273
|
-
*/
|
|
274
|
-
role?: "container";
|
|
275
|
-
/**
|
|
276
|
-
* How deep this row hangs in the tree: 0 top level, 1 a child, 2 a
|
|
277
|
-
* grandchild. Absent means 0.
|
|
278
|
-
*
|
|
279
|
-
* Always read it through `depthOf`, never off the row — `indent` is still a
|
|
280
|
-
* legal way to spell "depth 1", and that helper is the only place that knows
|
|
281
|
-
* both spellings. A site that reads `item.indent` directly prices a `depth: 2`
|
|
282
|
-
* row as top level, which is the bug this field exists to end rather than a
|
|
283
|
-
* new one to introduce.
|
|
284
|
-
*/
|
|
285
|
-
depth?: number;
|
|
286
|
-
/**
|
|
287
|
-
* @deprecated Legacy spelling of `depth: 1`. Write `depth`; this is kept so
|
|
288
|
-
* every existing producer still typechecks, and it is read in exactly one
|
|
289
|
-
* place (`depthOf`). Due to be removed at the next major.
|
|
290
|
-
*/
|
|
291
|
-
indent?: boolean;
|
|
292
|
-
};
|
|
293
|
-
type TaskRow = {
|
|
294
|
-
kind: "task";
|
|
295
|
-
key: string;
|
|
296
|
-
summary: string;
|
|
297
|
-
url: string;
|
|
298
|
-
status: string;
|
|
299
|
-
age: string;
|
|
300
|
-
/** What this row stands for. See `GHItem.role`. */
|
|
301
|
-
role?: "container";
|
|
302
|
-
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
303
|
-
depth?: number;
|
|
304
|
-
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
305
|
-
indent?: boolean;
|
|
306
|
-
instanceKey?: string;
|
|
307
|
-
/**
|
|
308
|
-
* Trailing annotation, rendered dim after the summary — a recurrence marker,
|
|
309
|
-
* a source hint, anything secondary to the title. Its own node rather than
|
|
310
|
-
* part of `summary` so it can be dimmed, and so its width is measured
|
|
311
|
-
* separately instead of being smuggled past the truncation maths.
|
|
312
|
-
*/
|
|
313
|
-
note?: string;
|
|
314
|
-
/**
|
|
315
|
-
* A category this row BELONGS to — `epic`, `blocked`, `spike` — drawn as a
|
|
316
|
-
* filled pill after the summary.
|
|
317
|
-
*
|
|
318
|
-
* Deliberately not a second spelling of `note`, which the epic marker used to
|
|
319
|
-
* borrow. The two want opposite weights: a pill says the word itself is the
|
|
320
|
-
* information, while `note` is for a reference the reader follows — a parent
|
|
321
|
-
* ticket key, a source. Filling a breadcrumb gives it a weight it has not
|
|
322
|
-
* earned, and a row can legitimately carry both (a story under someone else's
|
|
323
|
-
* epic shows its parent's key AND, one day, a status of its own).
|
|
324
|
-
*/
|
|
325
|
-
pill?: string;
|
|
326
|
-
/** Which fill the pill takes. See `@kud/ink-ui`'s `PillVariant`. */
|
|
327
|
-
pillVariant?: PillVariant;
|
|
328
|
-
/**
|
|
329
|
-
* The Jira issue key behind this row, when one exists. Its PRESENCE is what
|
|
330
|
-
* turns on the ticket affordances — ↵ opens a menu led by `jira issue view`,
|
|
331
|
-
* and `t` transitions the issue. A row without it is just a task: ↵ opens its
|
|
332
|
-
* URL and nothing here shells out to `jira`.
|
|
333
|
-
*
|
|
334
|
-
* Separate from `key` because the two are not the same thing. `key` is the
|
|
335
|
-
* left column, and a caller is free to put anything legible there: cockpit
|
|
336
|
-
* puts the ticket key, `life` puts a Todoist project name. Reading the drill
|
|
337
|
-
* off `key` is what made `life` run `jira issue view "Maison "` and
|
|
338
|
-
* label it "View ticket" on a surface that has never touched Jira.
|
|
339
|
-
*/
|
|
340
|
-
ticket?: string;
|
|
341
|
-
};
|
|
342
|
-
type RepoHeader = {
|
|
343
|
-
kind: "repo-header";
|
|
344
|
-
repo: string;
|
|
345
|
-
age: string;
|
|
346
|
-
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
347
|
-
depth?: number;
|
|
348
|
-
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
349
|
-
indent?: boolean;
|
|
350
|
-
};
|
|
351
|
-
type ShowMore = {
|
|
352
|
-
kind: "show-more";
|
|
353
|
-
hidden: GHItem[];
|
|
354
|
-
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
355
|
-
depth?: number;
|
|
356
|
-
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
357
|
-
indent?: boolean;
|
|
358
|
-
};
|
|
359
|
-
type ShowLess = {
|
|
360
|
-
kind: "show-less";
|
|
361
|
-
toHide: GHItem[];
|
|
362
|
-
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
363
|
-
depth?: number;
|
|
364
|
-
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
365
|
-
indent?: boolean;
|
|
366
|
-
};
|
|
367
|
-
type SubgroupHeader = {
|
|
368
|
-
kind: "subgroup-header";
|
|
369
|
-
label: string;
|
|
370
|
-
age: string;
|
|
371
|
-
/** How deep this row hangs. See `GHItem.depth`; read it through `depthOf`. */
|
|
372
|
-
depth?: number;
|
|
373
|
-
/** @deprecated Legacy spelling of `depth: 1`. See `GHItem.indent`. */
|
|
374
|
-
indent?: boolean;
|
|
375
|
-
};
|
|
376
|
-
type AnyItem = GHItem | TaskRow | RepoHeader | SubgroupHeader | ShowMore | ShowLess;
|
|
377
|
-
type CiStatus = {
|
|
378
|
-
job: string;
|
|
379
|
-
buildNumber: number;
|
|
380
|
-
result: string;
|
|
381
|
-
building: boolean;
|
|
382
|
-
url: string;
|
|
383
|
-
age: string;
|
|
384
|
-
};
|
|
385
|
-
type Section = {
|
|
386
|
-
id: string;
|
|
387
|
-
label: string;
|
|
388
|
-
items: AnyItem[];
|
|
389
|
-
};
|
|
390
188
|
type DetailContext = {
|
|
391
189
|
item: GHItem;
|
|
392
190
|
kind: "pr" | "issue";
|
|
@@ -424,55 +222,11 @@ type JiraTransition = {
|
|
|
424
222
|
/** Offered as a second step when the transition asks for a resolution. */
|
|
425
223
|
resolutions?: string[];
|
|
426
224
|
};
|
|
427
|
-
declare const relativeTime: (iso: string) => string;
|
|
428
225
|
type ExplainSection = {
|
|
429
226
|
heading: string;
|
|
430
227
|
lines: string[];
|
|
431
228
|
};
|
|
432
229
|
declare const explainItem: (item: GHItem, login: string) => ExplainSection[];
|
|
433
|
-
declare const repoPriority: (repo: string) => number;
|
|
434
|
-
declare const labelPriority: (name: string) => number;
|
|
435
|
-
declare const sortItems: (items: GHItem[]) => GHItem[];
|
|
436
|
-
declare const sortByRecency: (items: GHItem[]) => GHItem[];
|
|
437
|
-
declare const insertRepoHeaders: (items: GHItem[]) => AnyItem[];
|
|
438
|
-
type Standing = "authored" | "queued" | "spoken";
|
|
439
|
-
declare const whoseMove: (health: Health, sectionId: string, standing?: Standing, theySpokeLast?: boolean, pinned?: boolean) => "you" | "them";
|
|
440
|
-
declare const PIN_MARK = "+";
|
|
441
|
-
declare const layoutGHItems: (items: GHItem[], sectionId: string, login?: string) => AnyItem[];
|
|
442
|
-
/**
|
|
443
|
-
* A two-way repo split, when a host has one. `undefined` — the ordinary case —
|
|
444
|
-
* means no split at all: every row stands.
|
|
445
|
-
*
|
|
446
|
-
* The package deliberately does not NAME the sides. It hardcoded "work" and
|
|
447
|
-
* "home" until 2026-08-27: one reader's two lives compiled into a library
|
|
448
|
-
* anyone can install, printed in a header where no other host would recognise
|
|
449
|
-
* the word. The predicate, the side, and the word are all yours.
|
|
450
|
-
*
|
|
451
|
-
* Chosen once, by the host, before the first paint. There is no in-app toggle,
|
|
452
|
-
* because a key that flipped it could only ever put the inbox out of step with
|
|
453
|
-
* the scope the command was started in, with nothing on screen to explain the
|
|
454
|
-
* disagreement.
|
|
455
|
-
*/
|
|
456
|
-
type OriginSplit = {
|
|
457
|
-
/** Rows whose repo this matches are one side; everything else is the other. */
|
|
458
|
-
match: (repo: string) => boolean;
|
|
459
|
-
/** Which side to show. */
|
|
460
|
-
show: "matched" | "rest";
|
|
461
|
-
/** What to call the visible side in the header. Omitted, nothing is shown. */
|
|
462
|
-
label?: string;
|
|
463
|
-
};
|
|
464
|
-
/**
|
|
465
|
-
* Keep only one side of a two-way repo split, leaving non-GH rows (task)
|
|
466
|
-
* untouched. Mirrors filterByRepos' gh/other split.
|
|
467
|
-
*
|
|
468
|
-
* `keep` is positional — "the ones that matched" or "the rest" — and never a
|
|
469
|
-
* name. This took the literal strings "work" and "home" until 2026-08-27, which
|
|
470
|
-
* is one reader's life compiled into a published library: no other host has
|
|
471
|
-
* those two categories, and several have none at all.
|
|
472
|
-
*/
|
|
473
|
-
declare const filterByOrigin: (sections: Section[], keep: "matched" | "rest", match: (repo: string) => boolean) => Section[];
|
|
474
|
-
declare const filterBySearch: (sections: Section[], query: string) => Section[];
|
|
475
|
-
declare const filterByRepos: (sections: Section[], repos: Set<string>) => Section[];
|
|
476
230
|
declare const withoutItem: (sections: Section[], target: GHItem) => Section[];
|
|
477
231
|
declare const reposInSections: (sections: Section[]) => string[];
|
|
478
232
|
/**
|
|
@@ -645,88 +399,6 @@ declare const App: ({ fetcher, cacheKey, title, detailFor, origin, jiraBase, jir
|
|
|
645
399
|
extensions?: InboxExtension[];
|
|
646
400
|
}) => React.JSX.Element;
|
|
647
401
|
|
|
648
|
-
/**
|
|
649
|
-
* A named slice of the reader's repos. Two or more turn on the in-app toggle;
|
|
650
|
-
* one or none means an undivided set and no toggle at all.
|
|
651
|
-
*
|
|
652
|
-
* Named rather than boolean because the distinction was never a property of a
|
|
653
|
-
* repo — it is whatever separation the reader actually keeps, and a boolean
|
|
654
|
-
* leaves a third slice nowhere to go.
|
|
655
|
-
*/
|
|
656
|
-
type RepoProfile = {
|
|
657
|
-
/** Stable id — what `--profile=<name>` selects. */
|
|
658
|
-
name: string;
|
|
659
|
-
/** Shown on the toggle. Defaults to `name`. */
|
|
660
|
-
label?: string;
|
|
661
|
-
/** Which repos belong to this profile. */
|
|
662
|
-
match: (repo: string) => boolean;
|
|
663
|
-
/** Where this profile's checkouts live, if it keeps its own directory. */
|
|
664
|
-
checkoutDir?: string;
|
|
665
|
-
};
|
|
666
|
-
type InboxConfig = {
|
|
667
|
-
/**
|
|
668
|
-
* Repo ranking, best first. An entry ending in `/` matches by owner prefix,
|
|
669
|
-
* anything else must equal `owner/name` exactly — so a single repo can outrank
|
|
670
|
-
* the owner that contains it. Repos matching nothing sort last, together.
|
|
671
|
-
*/
|
|
672
|
-
repoPriority: readonly string[];
|
|
673
|
-
/**
|
|
674
|
-
* Label ranking, best first, deciding which two labels a row shows when it
|
|
675
|
-
* carries more than two. An entry ending in `*` matches by prefix; anything
|
|
676
|
-
* else must equal the label name exactly. Labels matching nothing share the
|
|
677
|
-
* last rank and are ordered among themselves by name.
|
|
678
|
-
*
|
|
679
|
-
* Empty like everything else here, and that is load-bearing rather than
|
|
680
|
-
* merely consistent: `plan`, `spike` and `app:*` are one reader's filing
|
|
681
|
-
* conventions, invented in their own notes, and a library that shipped them
|
|
682
|
-
* as defaults would be ranking a stranger's labels by a scheme they have
|
|
683
|
-
* never seen. Empty means a labelled row shows its labels in name order,
|
|
684
|
-
* which is honest about knowing nothing.
|
|
685
|
-
*/
|
|
686
|
-
labelPriority: readonly string[];
|
|
687
|
-
profiles: readonly RepoProfile[];
|
|
688
|
-
/**
|
|
689
|
-
* Fallback directory holding checkouts, for repos in no profile that names one
|
|
690
|
-
* of its own. Unset means local checkouts are not resolved at all, which is
|
|
691
|
-
* correct for a host that only ever opens things in a browser.
|
|
692
|
-
*/
|
|
693
|
-
checkoutDir?: string;
|
|
694
|
-
/**
|
|
695
|
-
* Directory name the on-disk cache lives under, inside the platform cache
|
|
696
|
-
* root. Defaults to this package's name — a library has no business claiming a
|
|
697
|
-
* directory named after whichever tool happens to embed it, and two hosts on
|
|
698
|
-
* one machine would otherwise share a cache keyed only by section name.
|
|
699
|
-
*/
|
|
700
|
-
cacheNamespace: string;
|
|
701
|
-
/**
|
|
702
|
-
* How long a cached inbox is trusted before a launch refetches. Every launch
|
|
703
|
-
* past it pays the full query — eight searches with check rollups — so this is
|
|
704
|
-
* the single knob between "always current" and "always slow, and drawing 502s
|
|
705
|
-
* from the API".
|
|
706
|
-
*
|
|
707
|
-
* It can be generous because staleness is bounded from the other end: acting
|
|
708
|
-
* on a row drops the entry outright, and `r` refetches on demand. What the TTL
|
|
709
|
-
* actually governs is how long a change made ELSEWHERE — someone approving
|
|
710
|
-
* your PR while you were not looking — can go unseen, which is a glance
|
|
711
|
-
* arriving late, not a wrong action taken.
|
|
712
|
-
*/
|
|
713
|
-
cacheTtlMs: number;
|
|
714
|
-
};
|
|
715
|
-
/** Supply the host's opinions. Call once, before rendering. */
|
|
716
|
-
declare const configureInbox: (config: Partial<InboxConfig>) => void;
|
|
717
|
-
declare const inboxConfig: () => InboxConfig;
|
|
718
|
-
/** Test seam — restores the empty defaults. */
|
|
719
|
-
declare const resetInboxConfig: () => void;
|
|
720
|
-
/** The profile a repo belongs to, or null when none claims it. */
|
|
721
|
-
declare const profileOf: (repo: string) => RepoProfile | null;
|
|
722
|
-
/**
|
|
723
|
-
* Where checkouts are searched, in order: every profile that names its own
|
|
724
|
-
* directory, then the fallback. Deduped, since the ordinary single-directory
|
|
725
|
-
* case points them all at the same place. Empty when nothing is configured —
|
|
726
|
-
* callers treat that as "not checked out locally", which it truthfully is.
|
|
727
|
-
*/
|
|
728
|
-
declare const checkoutDirs: () => string[];
|
|
729
|
-
|
|
730
402
|
type RepoFilter = {
|
|
731
403
|
/** Show only repos matching one of these. Empty or absent means show all. */
|
|
732
404
|
include?: readonly string[];
|
|
@@ -746,4 +418,4 @@ declare const matchesFilter: (repo: string, filter: RepoFilter) => boolean;
|
|
|
746
418
|
*/
|
|
747
419
|
declare const parsePatterns: (value: string) => string[];
|
|
748
420
|
|
|
749
|
-
export { type Action, ActionMenu,
|
|
421
|
+
export { type Action, ActionMenu, App, COLS, CiStatusLine, type CiStatusState, CommentsPanel, type CommentsPanelProps, type DetailContext, type ExplainSection, type ExtensionTarget, HealthPanel, type HealthPanelProps, type InboxBudget, type InboxExtension, type JiraTransition, type RepoFilter, SIDEBAR_COLS, SidePanel, type Sidebar, type SidebarRow, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, topLevelCount, truncate, useActionMenu, windowCount, withHeaders, withoutItem, writeCache };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import { spawn } from 'child_process';
|
|
|
8
8
|
import { readFileSync, mkdirSync, writeFileSync, existsSync, readdirSync, statSync, watch, rmSync } from 'fs';
|
|
9
9
|
import { join, dirname, basename } from 'path';
|
|
10
10
|
import { homedir } from 'os';
|
|
11
|
+
import { depthOf, checkoutDirs, profileOf, inboxConfig, relativeTime, filterBySearch, filterByRepos, filterByOrigin, PIN_MARK, labelPriority } from '@kud/gh-workflow';
|
|
12
|
+
export { PIN_MARK, checkoutDirs, configureInbox, filterByOrigin, filterByRepos, filterBySearch, inboxConfig, insertRepoHeaders, labelPriority, layoutGHItems, profileOf, relativeTime, repoPriority, resetInboxConfig, sortByRecency, sortItems, whoseMove } from '@kud/gh-workflow';
|
|
11
13
|
|
|
12
14
|
// src/components/comments-panel.tsx
|
|
13
15
|
var ENTITIES = {
|
|
@@ -699,30 +701,6 @@ var healthLegend = [
|
|
|
699
701
|
["closed", "Closed"]
|
|
700
702
|
];
|
|
701
703
|
|
|
702
|
-
// src/inbox/config.ts
|
|
703
|
-
var EMPTY = {
|
|
704
|
-
repoPriority: [],
|
|
705
|
-
labelPriority: [],
|
|
706
|
-
profiles: [],
|
|
707
|
-
cacheNamespace: "gh-ink",
|
|
708
|
-
cacheTtlMs: 6e5
|
|
709
|
-
};
|
|
710
|
-
var current = EMPTY;
|
|
711
|
-
var configureInbox = (config) => {
|
|
712
|
-
current = { ...EMPTY, ...config };
|
|
713
|
-
};
|
|
714
|
-
var inboxConfig = () => current;
|
|
715
|
-
var resetInboxConfig = () => {
|
|
716
|
-
current = EMPTY;
|
|
717
|
-
};
|
|
718
|
-
var profileOf = (repo) => current.profiles.find((p) => p.match(repo)) ?? null;
|
|
719
|
-
var checkoutDirs = () => [
|
|
720
|
-
.../* @__PURE__ */ new Set([
|
|
721
|
-
...current.profiles.flatMap((p) => p.checkoutDir ?? []),
|
|
722
|
-
...current.checkoutDir ? [current.checkoutDir] : []
|
|
723
|
-
])
|
|
724
|
-
];
|
|
725
|
-
|
|
726
704
|
// src/inbox/cache.ts
|
|
727
705
|
var cacheTtlMs = () => inboxConfig().cacheTtlMs;
|
|
728
706
|
var CACHE_VERSION = 3;
|
|
@@ -903,18 +881,6 @@ var tabsOfMarks = (sections, marks) => {
|
|
|
903
881
|
return out;
|
|
904
882
|
};
|
|
905
883
|
var quietly = $({ quiet: true });
|
|
906
|
-
var depthOf = (item) => {
|
|
907
|
-
if (!item) return 0;
|
|
908
|
-
if (typeof item.depth === "number") return Math.max(0, Math.floor(item.depth));
|
|
909
|
-
return item.indent === true ? 1 : 0;
|
|
910
|
-
};
|
|
911
|
-
var relativeTime = (iso) => {
|
|
912
|
-
const diff = (Date.now() - new Date(iso).getTime()) / 1e3;
|
|
913
|
-
if (diff < 3600) return `${Math.floor(diff / 60)}m`;
|
|
914
|
-
if (diff < 86400) return `${Math.floor(diff / 3600)}h`;
|
|
915
|
-
if (diff < 604800) return `${Math.floor(diff / 86400)}d`;
|
|
916
|
-
return `${Math.floor(diff / 604800)}w`;
|
|
917
|
-
};
|
|
918
884
|
var healthSentence = (item) => {
|
|
919
885
|
const glyph = healthDisplay[item.health].glyph.trim();
|
|
920
886
|
const d = item.detail;
|
|
@@ -1010,160 +976,6 @@ var explainItem = (item, login) => {
|
|
|
1010
976
|
{ heading: "Whose turn", lines: turnSentences(item, login) }
|
|
1011
977
|
];
|
|
1012
978
|
};
|
|
1013
|
-
var repoPriority = (repo) => {
|
|
1014
|
-
const order = inboxConfig().repoPriority;
|
|
1015
|
-
const i = order.findIndex(
|
|
1016
|
-
(p) => p.endsWith("/") ? repo.startsWith(p) : repo === p
|
|
1017
|
-
);
|
|
1018
|
-
return i === -1 ? order.length : i;
|
|
1019
|
-
};
|
|
1020
|
-
var labelPriority = (name) => {
|
|
1021
|
-
const order = inboxConfig().labelPriority;
|
|
1022
|
-
const i = order.findIndex(
|
|
1023
|
-
(p) => p.endsWith("*") ? name.startsWith(p.slice(0, -1)) : name === p
|
|
1024
|
-
);
|
|
1025
|
-
return i === -1 ? Infinity : i;
|
|
1026
|
-
};
|
|
1027
|
-
var sortItems = (items) => [...items].sort((a, b) => {
|
|
1028
|
-
const pd = repoPriority(a.repo) - repoPriority(b.repo);
|
|
1029
|
-
if (pd !== 0) return pd;
|
|
1030
|
-
const rd = a.repo.localeCompare(b.repo);
|
|
1031
|
-
if (rd !== 0) return rd;
|
|
1032
|
-
const dd = Number(a.health === "draft") - Number(b.health === "draft");
|
|
1033
|
-
return dd !== 0 ? dd : b.ts - a.ts;
|
|
1034
|
-
});
|
|
1035
|
-
var sortByRecency = (items) => [...items].sort((a, b) => b.ts - a.ts);
|
|
1036
|
-
var insertRepoHeaders = (items) => {
|
|
1037
|
-
const result = [];
|
|
1038
|
-
let lastRepo = "";
|
|
1039
|
-
for (const item of items) {
|
|
1040
|
-
if (depthOf(item) === 0 && item.repo !== lastRepo) {
|
|
1041
|
-
lastRepo = item.repo;
|
|
1042
|
-
result.push({
|
|
1043
|
-
kind: "repo-header",
|
|
1044
|
-
repo: item.repo,
|
|
1045
|
-
age: "",
|
|
1046
|
-
indent: false
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
result.push(item);
|
|
1050
|
-
}
|
|
1051
|
-
return result;
|
|
1052
|
-
};
|
|
1053
|
-
var STANDING = {
|
|
1054
|
-
mine: "authored",
|
|
1055
|
-
// `open` and `draft` predate `mine` and are the same standing: a host that
|
|
1056
|
-
// still splits its own PRs by draft-ness keeps working, and one that folds
|
|
1057
|
-
// them into a single tab (as cockpit does — the band already sinks a draft,
|
|
1058
|
-
// so the split said it twice) gets the same reading.
|
|
1059
|
-
open: "authored",
|
|
1060
|
-
draft: "authored",
|
|
1061
|
-
assigned: "authored",
|
|
1062
|
-
issues: "authored",
|
|
1063
|
-
review: "queued",
|
|
1064
|
-
incoming: "queued",
|
|
1065
|
-
reviewed: "spoken"
|
|
1066
|
-
};
|
|
1067
|
-
var YOURS = {
|
|
1068
|
-
authored: [
|
|
1069
|
-
"ci-fail",
|
|
1070
|
-
"conflict",
|
|
1071
|
-
"changes-req",
|
|
1072
|
-
"threads",
|
|
1073
|
-
"approved",
|
|
1074
|
-
"draft"
|
|
1075
|
-
],
|
|
1076
|
-
queued: ["waiting", "pending", "threads", "approved"],
|
|
1077
|
-
spoken: ["threads"]
|
|
1078
|
-
};
|
|
1079
|
-
var whoseMove = (health, sectionId, standing, theySpokeLast, pinned) => {
|
|
1080
|
-
const position = standing ?? STANDING[sectionId] ?? "queued";
|
|
1081
|
-
if (pinned) return "you";
|
|
1082
|
-
if (theySpokeLast && position === "authored") return "you";
|
|
1083
|
-
return YOURS[position].includes(health) ? "you" : "them";
|
|
1084
|
-
};
|
|
1085
|
-
var PIN_MARK = "+";
|
|
1086
|
-
var BAND_LABEL = {
|
|
1087
|
-
you: "Your move",
|
|
1088
|
-
them: "Their move"
|
|
1089
|
-
};
|
|
1090
|
-
var layoutGHItems = (items, sectionId, login) => {
|
|
1091
|
-
if (sectionId === "done") return insertRepoHeaders(sortByRecency(items));
|
|
1092
|
-
const sorted = sortItems(items);
|
|
1093
|
-
const bands = ["you", "them"].map((side) => ({
|
|
1094
|
-
side,
|
|
1095
|
-
rows: sorted.filter(
|
|
1096
|
-
(i) => whoseMove(
|
|
1097
|
-
i.health,
|
|
1098
|
-
sectionId,
|
|
1099
|
-
i.standing,
|
|
1100
|
-
!!login && !!i.lastActor && i.lastActor !== login,
|
|
1101
|
-
i.pinned
|
|
1102
|
-
) === side
|
|
1103
|
-
)
|
|
1104
|
-
}));
|
|
1105
|
-
const filled = bands.filter((b) => b.rows.length > 0);
|
|
1106
|
-
if (filled.length < 2) return insertRepoHeaders(sorted);
|
|
1107
|
-
return filled.flatMap(({ side, rows }) => [
|
|
1108
|
-
{
|
|
1109
|
-
kind: "subgroup-header",
|
|
1110
|
-
label: `${BAND_LABEL[side]} (${rows.length})`,
|
|
1111
|
-
age: "",
|
|
1112
|
-
indent: false
|
|
1113
|
-
},
|
|
1114
|
-
...insertRepoHeaders(rows)
|
|
1115
|
-
]);
|
|
1116
|
-
};
|
|
1117
|
-
var filterByOrigin = (sections, keep, match) => sections.map((s) => {
|
|
1118
|
-
const kept = s.items.filter(
|
|
1119
|
-
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && i.kind !== "show-more" && i.kind !== "show-less" && (i.kind === "pr" || i.kind === "issue" ? keep === "matched" ? match(i.repo) : !match(i.repo) : true)
|
|
1120
|
-
);
|
|
1121
|
-
const gh = kept.filter(
|
|
1122
|
-
(i) => i.kind === "pr" || i.kind === "issue"
|
|
1123
|
-
);
|
|
1124
|
-
const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
|
|
1125
|
-
return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
|
|
1126
|
-
}).filter(
|
|
1127
|
-
(s) => s.items.some(
|
|
1128
|
-
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"
|
|
1129
|
-
)
|
|
1130
|
-
);
|
|
1131
|
-
var searchText = (i) => i.kind === "pr" || i.kind === "issue" ? `${i.title} ${i.repo} #${i.number}` : i.kind === "task" ? `${i.summary} ${i.key}` : "";
|
|
1132
|
-
var filterBySearch = (sections, query) => {
|
|
1133
|
-
const q = query.trim().toLowerCase();
|
|
1134
|
-
if (!q) return sections;
|
|
1135
|
-
return sections.map((s) => {
|
|
1136
|
-
const kept = s.items.filter(
|
|
1137
|
-
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && i.kind !== "show-more" && i.kind !== "show-less" && searchText(i).toLowerCase().includes(q)
|
|
1138
|
-
);
|
|
1139
|
-
const gh = kept.filter(
|
|
1140
|
-
(i) => i.kind === "pr" || i.kind === "issue"
|
|
1141
|
-
);
|
|
1142
|
-
const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
|
|
1143
|
-
return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
|
|
1144
|
-
}).filter(
|
|
1145
|
-
(s) => s.items.some(
|
|
1146
|
-
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"
|
|
1147
|
-
)
|
|
1148
|
-
);
|
|
1149
|
-
};
|
|
1150
|
-
var filterByRepos = (sections, repos) => {
|
|
1151
|
-
if (repos.size === 0) return sections;
|
|
1152
|
-
return sections.map((s) => {
|
|
1153
|
-
const kept = s.items.filter(
|
|
1154
|
-
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header" && i.kind !== "show-more" && i.kind !== "show-less" && (i.kind === "pr" || i.kind === "issue" ? repos.has(i.repo) : true)
|
|
1155
|
-
);
|
|
1156
|
-
const gh = kept.filter(
|
|
1157
|
-
(i) => i.kind === "pr" || i.kind === "issue"
|
|
1158
|
-
);
|
|
1159
|
-
const other = kept.filter((i) => i.kind !== "pr" && i.kind !== "issue");
|
|
1160
|
-
return { ...s, items: [...layoutGHItems(gh, s.id), ...other] };
|
|
1161
|
-
}).filter(
|
|
1162
|
-
(s) => s.items.some(
|
|
1163
|
-
(i) => i.kind !== "repo-header" && i.kind !== "subgroup-header"
|
|
1164
|
-
)
|
|
1165
|
-
);
|
|
1166
|
-
};
|
|
1167
979
|
var isHeader = (i) => i.kind === "repo-header" || i.kind === "subgroup-header";
|
|
1168
980
|
var headerOwnsContent = (items, idx) => {
|
|
1169
981
|
const kind = items[idx].kind;
|
|
@@ -1190,11 +1002,11 @@ var reposInSections = (sections) => [
|
|
|
1190
1002
|
sections.flatMap((s) => s.items).filter((i) => i.kind === "pr" || i.kind === "issue").map((i) => i.repo)
|
|
1191
1003
|
)
|
|
1192
1004
|
].sort();
|
|
1193
|
-
var moveCursor = (items,
|
|
1194
|
-
let next =
|
|
1005
|
+
var moveCursor = (items, current, dir) => {
|
|
1006
|
+
let next = current + dir;
|
|
1195
1007
|
while (next >= 0 && next < items.length && items[next].kind === "subgroup-header")
|
|
1196
1008
|
next += dir;
|
|
1197
|
-
if (next < 0 || next >= items.length) return
|
|
1009
|
+
if (next < 0 || next >= items.length) return current;
|
|
1198
1010
|
return next;
|
|
1199
1011
|
};
|
|
1200
1012
|
var budgetNotice = (budget, now = Date.now()) => {
|
|
@@ -1992,7 +1804,7 @@ var isDeparture = (t) => t === "out" || t === "moved-out";
|
|
|
1992
1804
|
var isArrival = (t) => t === "in" || t === "moved-in";
|
|
1993
1805
|
var LEAVING_HOLD_MS = 2500;
|
|
1994
1806
|
var tabMarker = (marked, id, frame = 0) => marked.has(id) ? `${TAB_PULSE[frame % TAB_PULSE.length]} ` : " ";
|
|
1995
|
-
var TAB_PULSE = ["\xB7", "\u25CB", "\u25CE", "\u25C9"];
|
|
1807
|
+
var TAB_PULSE = ["\xB7", "\u25CB", "\u25CE", "\u25C9", "\u25CE", "\u25CB"];
|
|
1996
1808
|
var ItemRow = ({
|
|
1997
1809
|
item,
|
|
1998
1810
|
active,
|
|
@@ -3105,11 +2917,9 @@ var BrowseScreen = ({
|
|
|
3105
2917
|
count: topLevelCount(s),
|
|
3106
2918
|
// Its own cell, always two wide, so news arriving never slides the
|
|
3107
2919
|
// bar — and free to animate for exactly that reason.
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
Math.floor(sparkFrame / TRANSIT_FRAME_TICKS)
|
|
3112
|
-
),
|
|
2920
|
+
// The ticker undivided — see TAB_PULSE. The row marks' /3 buys quiet
|
|
2921
|
+
// for motion sitting beside text, and this is not that.
|
|
2922
|
+
marker: tabMarker(markedTabs, s.id, sparkFrame),
|
|
3113
2923
|
markerColor: "#FF8700"
|
|
3114
2924
|
}))
|
|
3115
2925
|
}
|
|
@@ -3637,4 +3447,4 @@ var matchesFilter = (repo, filter) => {
|
|
|
3637
3447
|
};
|
|
3638
3448
|
var parsePatterns = (value) => value.split(",").map((p) => p.trim()).filter(Boolean);
|
|
3639
3449
|
|
|
3640
|
-
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel,
|
|
3450
|
+
export { ActionMenu, App, COLS, CiStatusLine, CommentsPanel, HealthPanel, SIDEBAR_COLS, SidePanel, budgetNotice, buildActions, buildCheckoutCmd, clipboard, counts, drillCmd, explainGhAction, explainItem, fitCount, gapsAbove, healthColor, healthDisplay, healthGlyph, healthLegend, itermRun, jumpToRepo, jumpToRepoPane, matchesFilter, maxViewStart, moveCursor, openInTab, parsePatterns, railCapacity, readCache, renderMarkdown, reposInSections, resolveRepoPath, runHere, runInPane, runInPaneHorizontal, sameCiStatusState, signatureOf, toCiStatusState, topLevelCount, truncate2 as truncate, useActionMenu, windowCount, withHeaders, withoutItem, writeCache };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kud/gh-ink",
|
|
3
|
-
"version": "0.44.
|
|
3
|
+
"version": "0.44.3",
|
|
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",
|
|
@@ -48,9 +48,10 @@
|
|
|
48
48
|
"react": ">=19"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@kud/gh": "0.
|
|
51
|
+
"@kud/gh": "0.11.0",
|
|
52
52
|
"@kud/ink-ui": "0.22.0",
|
|
53
|
-
"zx": "8.8.5"
|
|
53
|
+
"zx": "8.8.5",
|
|
54
|
+
"@kud/gh-workflow": "0.2.0"
|
|
54
55
|
},
|
|
55
56
|
"devDependencies": {
|
|
56
57
|
"@types/node": "22.20.1",
|