@marcomigozzi/trailhead 0.16.1 → 0.17.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.it.md +57 -10
- package/README.md +57 -10
- package/bin/capture-action.test.js +167 -0
- package/bin/capture-docs.test.js +32 -0
- package/bin/engine-route-docs.test.js +148 -0
- package/bin/grill-model-docs.test.js +155 -0
- package/bin/grilling-holistic-review-docs.test.js +7 -3
- package/bin/lib/codex-projection.js +22 -13
- package/bin/lib/codex-projection.test.js +58 -17
- package/bin/lib/host-descriptor.js +1 -1
- package/bin/lib/host-descriptor.test.js +6 -0
- package/bin/report-status-docs.test.js +10 -2
- package/bin/session-trust-docs.test.js +37 -0
- package/bin/surfaced-scope-docs.test.js +57 -0
- package/bin/trailhead.js +7 -5
- package/bin/trailhead.test.js +53 -11
- package/bin/unblock-docs.test.js +56 -0
- package/package.json +1 -1
- package/plugins/trailhead/.claude-plugin/plugin.json +1 -1
- package/plugins/trailhead/agents/trailhead-codebase-map.md +1 -1
- package/plugins/trailhead/agents/trailhead-grill.md +41 -0
- package/plugins/trailhead/hooks/lib/capture.test.js +439 -0
- package/plugins/trailhead/hooks/lib/checkout-find.test.js +195 -0
- package/plugins/trailhead/hooks/lib/checkout-in.test.js +83 -7
- package/plugins/trailhead/hooks/lib/dashboard.test.js +19 -0
- package/plugins/trailhead/hooks/lib/gh-auth-preflight.js +89 -0
- package/plugins/trailhead/hooks/lib/gh-auth-preflight.test.js +132 -0
- package/plugins/trailhead/hooks/lib/inherit-session.js +17 -0
- package/plugins/trailhead/hooks/lib/issue-body.test.js +84 -0
- package/plugins/trailhead/hooks/lib/model-defaults-review.js +22 -1
- package/plugins/trailhead/hooks/lib/model-defaults-review.test.js +64 -1
- package/plugins/trailhead/hooks/lib/model-route.js +207 -0
- package/plugins/trailhead/hooks/lib/model-route.test.js +231 -0
- package/plugins/trailhead/hooks/lib/report-status.test.js +56 -1
- package/plugins/trailhead/hooks/mod/README.md +16 -7
- package/plugins/trailhead/hooks/mod/map-pane.tsx +72 -16
- package/plugins/trailhead/hooks/mod/my-tickets.ts +76 -0
- package/plugins/trailhead/hooks/mod/repo-pin.ts +8 -5
- package/plugins/trailhead/hooks/trailhead-check-update.js +17 -1
- package/plugins/trailhead/hooks/trailhead-check-update.test.js +60 -0
- package/plugins/trailhead/skills/_shared/capture-core.md +118 -0
- package/plugins/trailhead/skills/_shared/choices.md +2 -2
- package/plugins/trailhead/skills/_shared/configuration-reference.md +18 -16
- package/plugins/trailhead/skills/_shared/configuration.md +1 -1
- package/plugins/trailhead/skills/_shared/session-handoff.md +1 -1
- package/plugins/trailhead/skills/_shared/substrate-commands.md +8 -4
- package/plugins/trailhead/skills/_shared/substrate.md +1 -1
- package/plugins/trailhead/skills/_shared/surfaced-scope.md +23 -0
- package/plugins/trailhead/skills/_shared/teamwork.md +6 -2
- package/plugins/trailhead/skills/_shared/techniques/acceptance-testing.md +1 -1
- package/plugins/trailhead/skills/_shared/techniques/code-review.md +5 -2
- package/plugins/trailhead/skills/_shared/techniques/codebase-map.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/debug.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/grilling.md +26 -1
- package/plugins/trailhead/skills/_shared/techniques/research.md +4 -1
- package/plugins/trailhead/skills/_shared/techniques/verify.md +3 -1
- package/plugins/trailhead/skills/_shared/techniques.md +6 -4
- package/plugins/trailhead/skills/trailhead-capture/SKILL.md +2 -4
- package/plugins/trailhead/skills/trailhead-capture/references/capture.md +29 -4
- package/plugins/trailhead/skills/trailhead-chart/SKILL.md +1 -1
- package/plugins/trailhead/skills/trailhead-chart/references/charting.md +4 -4
- package/plugins/trailhead/skills/trailhead-chart/references/inbox.md +3 -3
- package/plugins/trailhead/skills/trailhead-manage/references/auditing.md +1 -1
- package/plugins/trailhead/skills/trailhead-work/SKILL.md +11 -9
- package/plugins/trailhead/skills/trailhead-work/references/auto.md +1 -1
- package/plugins/trailhead/skills/trailhead-work/references/ticket-engines.md +10 -10
- package/plugins/trailhead/templates/trailhead-capture.js +418 -0
- package/plugins/trailhead/templates/trailhead-checkout-find.js +256 -0
- package/plugins/trailhead/templates/trailhead-checkout-in.js +76 -20
- package/plugins/trailhead/templates/trailhead-dashboard.js +15 -2
- package/plugins/trailhead/templates/trailhead-issue-body.js +69 -6
- package/plugins/trailhead/templates/trailhead-report-status.js +57 -9
- package/plugins/trailhead/tests/map-pane-ui.test.ts +122 -37
- package/plugins/trailhead/tests/my-tickets.test.ts +66 -0
- package/plugins/trailhead/tests/repo-pin.test.ts +20 -5
- package/plugins/trailhead/types/index.d.ts +7 -0
|
@@ -15,13 +15,15 @@ const ROOT = '/repo'
|
|
|
15
15
|
const ROOT_OTHER = '/other'
|
|
16
16
|
// A linked worktree of ROOT: the repo's root is the main tree's, the checkout's own is this.
|
|
17
17
|
const ROOT_TREE = '/repo-t5'
|
|
18
|
-
// The checkout handed to a verb: base64url of the root,
|
|
18
|
+
// The checkout handed to a verb: base64url of the root, a `.`, then base64url of the pinned repo (`host/owner/name`),
|
|
19
|
+
// computed here by hand so the pane's own encoder is checked.
|
|
19
20
|
// The helper's decodeIn cannot be imported here (the CommonJS template does not load in this suite); the node suite
|
|
20
21
|
// checks decodeIn against Buffer's base64url, and these tests pin the same alphabet.
|
|
21
|
-
const
|
|
22
|
-
const b64 = btoa(Array.from(new TextEncoder().encode(
|
|
23
|
-
return
|
|
22
|
+
const b64url = (text: string) => {
|
|
23
|
+
const b64 = btoa(Array.from(new TextEncoder().encode(text), byte => String.fromCharCode(byte)).join(''))
|
|
24
|
+
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
|
24
25
|
}
|
|
26
|
+
const IN = (root: string) => `--in=${b64url(root)}.${b64url('github.com/o/r')}`
|
|
25
27
|
|
|
26
28
|
const MAP = {
|
|
27
29
|
title: 'trailhead mod',
|
|
@@ -101,6 +103,9 @@ const ticketsCall = (label: string, after?: string) => [
|
|
|
101
103
|
const ticketsListCall = (label: string) => ['gh', 'issue', 'list', '--label', label, '--state', 'open', '--limit', '200', '--json', 'number,title,labels,assignees,url']
|
|
102
104
|
const isTicketsRead = (argv: readonly string[]) => argv[1] === 'api' && argv[2] === 'graphql' && argv.includes(`query=${TICKETS_QUERY}`)
|
|
103
105
|
// The maps read only: the ticket reads are GraphQL too, but answered and failed on their own.
|
|
106
|
+
// The login and my-tickets reads (any map, the whiteboard): answered apart from the pane's own reads, recorded in `mineCalls`.
|
|
107
|
+
const isLoginRead = (argv: readonly string[]) => argv[1] === 'api' && argv[2] === 'graphql' && argv.includes('query=query{viewer{login}}')
|
|
108
|
+
const isMineRead = (argv: readonly string[]) => argv[1] === 'api' && argv[2] === 'graphql' && argv.some(a => a.startsWith('login='))
|
|
104
109
|
const isGraphql = (argv: readonly string[]) => argv[1] === 'api' && argv[2] === 'graphql' && !isTicketsRead(argv) && !isUatRead(argv) && !isCountRead(argv)
|
|
105
110
|
// The open-ticket total: one aliased `totalCount` per open map (`m<n>`) plus the whiteboard (`wb`), in one call.
|
|
106
111
|
const countQuery = (maps: number[]) =>
|
|
@@ -137,7 +142,7 @@ const stateText = (over: Record<string, unknown> = {}): string =>
|
|
|
137
142
|
})
|
|
138
143
|
|
|
139
144
|
// The plugin's atoms, which the engine resets on /clear.
|
|
140
|
-
const ATOM_KEYS = ['workState', 'isOrphaned', 'promptSnapshot', 'mapPane', 'activeMap', 'viewedMap', 'isDashboard', 'dashboardPane', 'pendingWork', 'pendingPhase', 'paneAutoOpened', 'ownTicket', 'repoPin']
|
|
145
|
+
const ATOM_KEYS = ['workState', 'isOrphaned', 'promptSnapshot', 'mapPane', 'activeMap', 'viewedMap', 'isDashboard', 'dashboardPane', 'pendingWork', 'pendingPhase', 'paneAutoOpened', 'ownTicket', 'repoPin', 'myTickets']
|
|
141
146
|
|
|
142
147
|
const PANE_PROPS = {
|
|
143
148
|
title: 'Map',
|
|
@@ -179,6 +184,9 @@ const rig = (on: On, engine?: any) => {
|
|
|
179
184
|
// The UAT read's answer: one row per open UAT ticket, `{ number, body, labels }` (the scope label rides
|
|
180
185
|
// in `labels`); null makes the read fail. `uatErrors` answers a reply with `errors`, `uatBroken` non-JSON.
|
|
181
186
|
uat: [] as unknown[] | null,
|
|
187
|
+
// My open tickets on any map or the whiteboard (the `issue()` shape); null makes the read fail.
|
|
188
|
+
mine: [] as unknown[] | null,
|
|
189
|
+
mineCalls: [] as (readonly string[])[],
|
|
182
190
|
uatErrors: false,
|
|
183
191
|
uatBroken: false,
|
|
184
192
|
dashboard: DASHBOARD as unknown[],
|
|
@@ -209,6 +217,8 @@ const rig = (on: On, engine?: any) => {
|
|
|
209
217
|
calls: [] as (readonly string[])[],
|
|
210
218
|
panes: [] as { id: string; title: string; isShown: boolean; isFocused: boolean; isPlaced: boolean }[],
|
|
211
219
|
isPlaced: true,
|
|
220
|
+
// Why `ui.open` leaves the pane unplaced (the SDK's `reason`).
|
|
221
|
+
reason: 'terminal too narrow',
|
|
212
222
|
logs: [] as string[],
|
|
213
223
|
toasts: [] as string[],
|
|
214
224
|
// What a Bash call prints on stdout (the created issue's URL for a `gh issue create`).
|
|
@@ -396,6 +406,14 @@ const rig = (on: On, engine?: any) => {
|
|
|
396
406
|
const out = r.authHosts.includes(host) ? { exitCode: 0, stdout: '', stderr: '' } : { exitCode: 1, stdout: '', stderr: `You are not logged into any accounts on ${host}` }
|
|
397
407
|
return { value: { ...out, isStdoutTruncated: false, isStderrTruncated: false } }
|
|
398
408
|
}
|
|
409
|
+
if (isLoginRead(e.argv) || isMineRead(e.argv)) {
|
|
410
|
+
r.mineCalls.push([...e.argv])
|
|
411
|
+
const body = isLoginRead(e.argv)
|
|
412
|
+
? { data: { viewer: { login: 'me' } } }
|
|
413
|
+
: { data: { repository: { issues: { nodes: (r.mine ?? []).map(flatNode), pageInfo: { hasNextPage: false, endCursor: null } } } } }
|
|
414
|
+
const out = r.mine === null && isMineRead(e.argv) ? { exitCode: 1, stdout: '', stderr: 'boom' } : { exitCode: 0, stdout: JSON.stringify(body), stderr: '' }
|
|
415
|
+
return { value: { ...out, isStdoutTruncated: false, isStderrTruncated: false } }
|
|
416
|
+
}
|
|
399
417
|
r.calls.push([...e.argv])
|
|
400
418
|
r.cwds.push(e.init?.cwd)
|
|
401
419
|
// `gh` needs a repo around it: without its own cwd it uses the session's, wherever a `cd` left it.
|
|
@@ -413,7 +431,7 @@ const rig = (on: On, engine?: any) => {
|
|
|
413
431
|
...r.panes.filter(p => p.id !== e.id),
|
|
414
432
|
{ id: e.id, title: e.title ?? e.id, isShown: true, isFocused: false, isPlaced: r.isPlaced },
|
|
415
433
|
]
|
|
416
|
-
return { value: r.isPlaced ? { isPlaced: true as const } : { isPlaced: false as const, reason:
|
|
434
|
+
return { value: r.isPlaced ? { isPlaced: true as const } : { isPlaced: false as const, reason: r.reason } }
|
|
417
435
|
})
|
|
418
436
|
on('ui.close', (_$, e) => {
|
|
419
437
|
r.panes = r.panes.filter(p => p.id !== e.id)
|
|
@@ -496,7 +514,7 @@ for (const surface of SURFACES) {
|
|
|
496
514
|
expect(await textOf(ui, 'head-in-progress')).toBe('In progress · 2')
|
|
497
515
|
expect(await textOf(ui, 'head-blocked')).toBe('Blocked · 2')
|
|
498
516
|
expect((await ui.find({ key: 'head-frontier' }))?.children[0]).toMatchObject({ props: { bold: true } })
|
|
499
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
517
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-208', 'row-212', 'row-209', 'row-210', 'row-214', 'row-211', 'row-213'])
|
|
500
518
|
|
|
501
519
|
expect(await textOf(ui, 'ticket-209')).toBe('#209 Burn-down view of the map')
|
|
502
520
|
expect(await textOf(ui, 'type-209')).toBe(' build')
|
|
@@ -550,10 +568,10 @@ for (const surface of SURFACES) {
|
|
|
550
568
|
|
|
551
569
|
expect(await textOf(ui, 'head-uat')).toBe('UAT · 3')
|
|
552
570
|
expect((await ui.find({ key: 'head-uat' }))?.children[0]).toMatchObject({ props: { bold: true } })
|
|
553
|
-
expect(await keysOf(ui, 'group-')).toEqual(['group-maps', 'group-
|
|
571
|
+
expect(await keysOf(ui, 'group-')).toEqual(['group-maps', 'group-in-progress', 'group-frontier', 'group-uat', 'group-paused', 'group-blocked'])
|
|
554
572
|
// each ticket once: a UAT row is in no other group, a paused UAT stays under Paused
|
|
555
573
|
expect(await keysOf(ui, 'row-')).toEqual([
|
|
556
|
-
'row-
|
|
574
|
+
'row-208', 'row-212', 'row-209', 'row-210', 'row-214', 'row-240', 'row-241', 'row-242', 'row-243', 'row-211', 'row-213',
|
|
557
575
|
])
|
|
558
576
|
expect(await textOf(ui, 'head-frontier')).toBe('Frontier · 3')
|
|
559
577
|
expect(await textOf(ui, 'head-paused')).toBe('Paused · 1')
|
|
@@ -654,7 +672,7 @@ for (const surface of SURFACES) {
|
|
|
654
672
|
expect(r.calls.filter(isUatRead)).toContainEqual(uatCall())
|
|
655
673
|
expect(r.calls.filter(isUatSearch)).toEqual([])
|
|
656
674
|
expect(await textOf(ui, 'head-uat')).toBe('UAT · 1')
|
|
657
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
675
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-302', 'row-301', 'row-306', 'row-303'])
|
|
658
676
|
expect(await textOf(ui, 'uat-of-306')).toBe('tests Loose parent #290')
|
|
659
677
|
})
|
|
660
678
|
|
|
@@ -675,8 +693,8 @@ for (const surface of SURFACES) {
|
|
|
675
693
|
expect(await textOf(ui, 'head-in-progress')).toBe('In progress · 2')
|
|
676
694
|
expect(await textOf(ui, 'head-blocked')).toBe('Blocked · 2')
|
|
677
695
|
// a paused ticket appears in one group only: Frontier, In progress, Paused, Blocked in order
|
|
678
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
679
|
-
expect(await keysOf(ui, 'group-')).toEqual(['group-maps', 'group-
|
|
696
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-208', 'row-212', 'row-209', 'row-210', 'row-214', 'row-230', 'row-231', 'row-232', 'row-211', 'row-213'])
|
|
697
|
+
expect(await keysOf(ui, 'group-')).toEqual(['group-maps', 'group-in-progress', 'group-frontier', 'group-paused', 'group-blocked'])
|
|
680
698
|
for (const n of [230, 231, 232]) {
|
|
681
699
|
expect(await hasKey(ui, `resume-${n}`)).toBe(true)
|
|
682
700
|
expect(await hasKey(ui, `work-${n}`)).toBe(false)
|
|
@@ -692,12 +710,12 @@ for (const surface of SURFACES) {
|
|
|
692
710
|
const ui = await openAndMount($, r)
|
|
693
711
|
await pressDashboard(ui, r)
|
|
694
712
|
expect(await textOf(ui, 'head-paused')).toBe('Paused · 1')
|
|
695
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
713
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-302', 'row-301', 'row-305', 'row-303'])
|
|
696
714
|
expect(await hasKey(ui, 'work-305')).toBe(false)
|
|
697
715
|
expect(await hasKey(ui, 'resume-305')).toBe(true)
|
|
698
716
|
})
|
|
699
717
|
|
|
700
|
-
test(`needs-you tickets sit in their own group
|
|
718
|
+
test(`needs-you tickets sit in their own group right after In progress, once each, with a work button unless held [${surface}]`, async ($, on) => {
|
|
701
719
|
const r = rig(on, $)
|
|
702
720
|
r.tickets = [
|
|
703
721
|
...TICKETS,
|
|
@@ -713,9 +731,9 @@ for (const surface of SURFACES) {
|
|
|
713
731
|
|
|
714
732
|
expect(await textOf(ui, 'head-needs-you')).toBe('Needs you · 6')
|
|
715
733
|
expect((await ui.find({ key: 'head-needs-you' }))?.children[0]).toMatchObject({ props: { bold: true } })
|
|
716
|
-
expect(await keysOf(ui, 'group-')).toEqual(['group-maps', 'group-
|
|
717
|
-
// each ticket once, ahead of the frontier rows; the other counts leave them out
|
|
718
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
734
|
+
expect(await keysOf(ui, 'group-')).toEqual(['group-maps', 'group-in-progress', 'group-needs-you', 'group-frontier', 'group-blocked'])
|
|
735
|
+
// each ticket once, after my in-progress rows and ahead of the frontier rows; the other counts leave them out
|
|
736
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-208', 'row-212', 'row-250', 'row-251', 'row-252', 'row-253', 'row-254', 'row-255', 'row-209', 'row-210', 'row-214', 'row-211', 'row-213'])
|
|
719
737
|
expect(await textOf(ui, 'head-frontier')).toBe('Frontier · 3')
|
|
720
738
|
expect(await hasKey(ui, 'head-paused')).toBe(false)
|
|
721
739
|
expect(await hasKey(ui, 'head-uat')).toBe(false)
|
|
@@ -738,16 +756,16 @@ for (const surface of SURFACES) {
|
|
|
738
756
|
expect(r.runs).toEqual([{ command: 'clear', args: '' }, { command: 'trailhead:work', args: '251' }])
|
|
739
757
|
})
|
|
740
758
|
|
|
741
|
-
test(`the dashboard view puts a needs-you whiteboard ticket
|
|
759
|
+
test(`the dashboard view puts a needs-you whiteboard ticket in its own group right after In progress [${surface}]`, async ($, on) => {
|
|
742
760
|
const r = rig(on, $)
|
|
743
761
|
r.whiteboard = [...WHITEBOARD, issue(307, 'Loose needs you', [...WB, 'trailhead:task', 'trailhead:needs-you', 'trailhead:paused'])]
|
|
744
762
|
await $.session.start(START)
|
|
745
763
|
const ui = await openAndMount($, r)
|
|
746
764
|
await pressDashboard(ui, r)
|
|
747
765
|
expect(await textOf(ui, 'head-needs-you')).toBe('Needs you · 1')
|
|
748
|
-
//
|
|
749
|
-
expect((await keysOf(ui, 'group-')).slice(0,
|
|
750
|
-
expect((await keysOf(ui, 'row-'))
|
|
766
|
+
// right after the maps group and In progress
|
|
767
|
+
expect((await keysOf(ui, 'group-')).slice(0, 3)).toEqual(['group-maps', 'group-in-progress', 'group-needs-you'])
|
|
768
|
+
expect((await keysOf(ui, 'row-')).slice(0, 2)).toEqual(['row-302', 'row-307'])
|
|
751
769
|
expect(await hasKey(ui, 'head-paused')).toBe(false)
|
|
752
770
|
expect(await hasKey(ui, 'work-307')).toBe(true)
|
|
753
771
|
expect(await hasKey(ui, 'resume-307')).toBe(false)
|
|
@@ -1370,7 +1388,7 @@ for (const surface of SURFACES) {
|
|
|
1370
1388
|
await $.session.start(START)
|
|
1371
1389
|
const ui = await openAndMount($, r)
|
|
1372
1390
|
await pressDashboard(ui, r)
|
|
1373
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
1391
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-302', 'row-301', 'row-303'])
|
|
1374
1392
|
})
|
|
1375
1393
|
|
|
1376
1394
|
test(`a write that reads at once is followed by one more read at the floor's end [${surface}]`, async ($, on) => {
|
|
@@ -1623,9 +1641,11 @@ for (const surface of SURFACES) {
|
|
|
1623
1641
|
// The token the verb receives decodes (plain base64url, the helper's alphabet) to the pinned root; the suite has no Buffer.
|
|
1624
1642
|
const [number, word] = r.runs[1].args.split(' ')
|
|
1625
1643
|
expect(number).toBe('209')
|
|
1626
|
-
expect(word).toMatch(/^--in=[A-Za-z0-9_-]+$/)
|
|
1644
|
+
expect(word).toMatch(/^--in=[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/)
|
|
1627
1645
|
expect(word).toBe(IN(ROOT))
|
|
1628
|
-
|
|
1646
|
+
const [rootPart, repoPart] = word.slice('--in='.length).split('.')
|
|
1647
|
+
expect(atob(rootPart.replace(/-/g, '+').replace(/_/g, '/'))).toBe(ROOT)
|
|
1648
|
+
expect(atob(repoPart.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(repoPart.length / 4) * 4, '='))).toBe('github.com/o/r')
|
|
1629
1649
|
expect(await ui.find({ key: 'queued-209' })).toBeUndefined()
|
|
1630
1650
|
expect(await ui.find({ key: 'work-209' })).toBeDefined()
|
|
1631
1651
|
})
|
|
@@ -2280,7 +2300,7 @@ for (const surface of SURFACES) {
|
|
|
2280
2300
|
await settle(r)
|
|
2281
2301
|
expect(await textOf(ui, 'map')).toBe('#14 older map')
|
|
2282
2302
|
expect(await textOf(ui, 'viewing-text')).toBe('viewing #14 · active #198')
|
|
2283
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
2303
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
2284
2304
|
})
|
|
2285
2305
|
|
|
2286
2306
|
test(`the refresh button obeys the same floor [${surface}]`, async ($, on) => {
|
|
@@ -2378,6 +2398,20 @@ for (const surface of SURFACES) {
|
|
|
2378
2398
|
expect((await panel($)).text).toBe('Map panel closed.')
|
|
2379
2399
|
})
|
|
2380
2400
|
|
|
2401
|
+
// #321: nessuna superficie collegata (inizio sessione) non è un errore: il pannello resta aperto in attesa.
|
|
2402
|
+
test(`/trailhead:panel with no surface attached keeps the pane pending and says so plainly [${surface}]`, async ($, on) => {
|
|
2403
|
+
const r = rig(on, $)
|
|
2404
|
+
r.isPlaced = false
|
|
2405
|
+
r.reason = 'no-surface: no surface is attached ($.session.surfaces() is empty)'
|
|
2406
|
+
await $.session.start(START)
|
|
2407
|
+
const pending = 'Map panel will open as soon as the screen is ready.'
|
|
2408
|
+
expect((await panel($)).text).toBe(pending)
|
|
2409
|
+
expect(r.panes.map(p => p.id)).toEqual([PANE])
|
|
2410
|
+
// a second command while still pending is idempotent: it never closes the pane
|
|
2411
|
+
expect((await panel($)).text).toBe(pending)
|
|
2412
|
+
expect(r.panes.map(p => p.id)).toEqual([PANE])
|
|
2413
|
+
})
|
|
2414
|
+
|
|
2381
2415
|
test(`/trailhead:panel shows a pane the auto-open left undrawn instead of closing it [${surface}]`, async ($, on) => {
|
|
2382
2416
|
const r = rig(on, $)
|
|
2383
2417
|
// sessione vuota, terminale sotto la soglia dei pannelli non richiesti: l'auto-open resta non disegnato
|
|
@@ -2552,7 +2586,7 @@ for (const surface of SURFACES) {
|
|
|
2552
2586
|
|
|
2553
2587
|
expect(await textOf(ui, 'map')).toBe('#14 older map')
|
|
2554
2588
|
expect(await textOf(ui, 'destination')).toBe('An older destination.')
|
|
2555
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
2589
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
2556
2590
|
expect(await textOf(ui, 'holder-141')).toBe('@carol')
|
|
2557
2591
|
expect(await textOf(ui, 'burndown-counts')).toBe('open 2 · done 0 · removed 0 · peak 2')
|
|
2558
2592
|
// the ticket in play belongs to the other map: nothing here is marked
|
|
@@ -2727,7 +2761,7 @@ for (const surface of SURFACES) {
|
|
|
2727
2761
|
await settle(r)
|
|
2728
2762
|
expect(await hasKey(ui, 'ticket-300')).toBe(false)
|
|
2729
2763
|
expect(await textOf(ui, 'map')).toBe('#14 older map')
|
|
2730
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
2764
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
2731
2765
|
})
|
|
2732
2766
|
|
|
2733
2767
|
test(`with no active map the open maps are listed and one can be viewed [${surface}]`, async ($, on) => {
|
|
@@ -2757,6 +2791,57 @@ for (const surface of SURFACES) {
|
|
|
2757
2791
|
await settle(r)
|
|
2758
2792
|
}
|
|
2759
2793
|
|
|
2794
|
+
// My tickets in play on other maps and the whiteboard, under every view's In progress.
|
|
2795
|
+
const MINE = [
|
|
2796
|
+
issue(150, 'My whiteboard fix', [...WB, 'trailhead:build'], ['me']),
|
|
2797
|
+
issue(160, 'My other map ticket', ['trailhead:ticket', 'trailhead:build', 'trailhead:map-14'], ['me']),
|
|
2798
|
+
issue(161, 'My paused one', ['trailhead:ticket', 'trailhead:build', 'trailhead:map-14', 'trailhead:paused'], ['me']),
|
|
2799
|
+
issue(212, 'Native gh-backed tools', ['trailhead:ticket', 'trailhead:build', 'trailhead:map-198'], ['me']),
|
|
2800
|
+
]
|
|
2801
|
+
|
|
2802
|
+
test(`the map's In progress adds my tickets of the other maps and the whiteboard, each saying where [${surface}]`, async ($, on) => {
|
|
2803
|
+
const r = rig(on, $)
|
|
2804
|
+
r.mine = MINE
|
|
2805
|
+
await $.session.start(START)
|
|
2806
|
+
const ui = await openAndMount($, r)
|
|
2807
|
+
expect(r.mineCalls[0]).toEqual(['gh', 'api', 'graphql', '-f', 'query=query{viewer{login}}'])
|
|
2808
|
+
expect(r.mineCalls.some(argv => argv.includes('login=me'))).toBe(true)
|
|
2809
|
+
expect(await textOf(ui, 'head-in-progress')).toBe('In progress · 4')
|
|
2810
|
+
// the map's own first, then mine elsewhere; a paused one and one the map already lists are not added
|
|
2811
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-208', 'row-212', 'row-150', 'row-160', 'row-209', 'row-210', 'row-214', 'row-211', 'row-213'])
|
|
2812
|
+
expect(await textOf(ui, 'scope-of-150')).toBe('on whiteboard')
|
|
2813
|
+
expect(await textOf(ui, 'scope-of-160')).toBe('on map #14 older map')
|
|
2814
|
+
expect((await ui.find({ key: 'scope-of-160' }))?.children[0]).toMatchObject({ props: { dimColor: true } })
|
|
2815
|
+
expect(await textOf(ui, 'holder-160')).toBe('@me')
|
|
2816
|
+
expect(await hasKey(ui, 'scope-of-212')).toBe(false)
|
|
2817
|
+
expect(await hasKey(ui, 'row-161')).toBe(false)
|
|
2818
|
+
})
|
|
2819
|
+
|
|
2820
|
+
test(`the dashboard's In progress adds my tickets of every map [${surface}]`, async ($, on) => {
|
|
2821
|
+
const r = rig(on, $)
|
|
2822
|
+
r.mine = MINE
|
|
2823
|
+
await $.session.start(START)
|
|
2824
|
+
const ui = await openAndMount($, r)
|
|
2825
|
+
await pressDashboard(ui, r)
|
|
2826
|
+
expect(await textOf(ui, 'scope-of-160')).toBe('on map #14 older map')
|
|
2827
|
+
expect(await textOf(ui, 'scope-of-212')).toBe('on map #198 trailhead mod')
|
|
2828
|
+
// a whiteboard ticket of mine belongs to this view's own list, never a second time with a scope line
|
|
2829
|
+
expect(await hasKey(ui, 'scope-of-150')).toBe(false)
|
|
2830
|
+
})
|
|
2831
|
+
|
|
2832
|
+
test(`a failed read of my tickets keeps the last ones and sets no error [${surface}]`, async ($, on) => {
|
|
2833
|
+
const r = rig(on, $)
|
|
2834
|
+
r.mine = MINE
|
|
2835
|
+
await $.session.start(START)
|
|
2836
|
+
const ui = await openAndMount($, r)
|
|
2837
|
+
expect(await hasKey(ui, 'row-160')).toBe(true)
|
|
2838
|
+
r.mine = null
|
|
2839
|
+
await r.clock.advance(TIMER)
|
|
2840
|
+
await settle(r)
|
|
2841
|
+
expect(await hasKey(ui, 'row-160')).toBe(true)
|
|
2842
|
+
expect(await textOf(ui, 'age')).toBe('updated just now')
|
|
2843
|
+
})
|
|
2844
|
+
|
|
2760
2845
|
test(`the Whiteboard row shows the maps index and the whiteboard tickets [${surface}]`, async ($, on) => {
|
|
2761
2846
|
const r = rig(on, $)
|
|
2762
2847
|
await $.session.start(START)
|
|
@@ -2792,7 +2877,7 @@ for (const surface of SURFACES) {
|
|
|
2792
2877
|
expect(await textOf(ui, 'head-frontier')).toBe('Frontier · 1')
|
|
2793
2878
|
expect(await textOf(ui, 'head-in-progress')).toBe('In progress · 1')
|
|
2794
2879
|
expect(await textOf(ui, 'head-blocked')).toBe('Blocked · 1')
|
|
2795
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
2880
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-302', 'row-301', 'row-303'])
|
|
2796
2881
|
expect(await textOf(ui, 'holder-302')).toBe('@alice')
|
|
2797
2882
|
expect(await hasKey(ui, 'work-301')).toBe(true)
|
|
2798
2883
|
expect(await textOf(ui, 'unverified')).toBe('1 unverified ticket hidden (quarantined)')
|
|
@@ -3147,7 +3232,7 @@ for (const surface of SURFACES) {
|
|
|
3147
3232
|
const ui = await openAndMount($, r)
|
|
3148
3233
|
await pressDashboard(ui, r)
|
|
3149
3234
|
expect(await textOf(ui, 'head-whiteboard')).toBe('Whiteboard · 4 open · 2 assigned')
|
|
3150
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3235
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-302', 'row-301', 'row-306', 'row-303'])
|
|
3151
3236
|
expect(await textOf(ui, 'holder-302')).toBe('@alice')
|
|
3152
3237
|
expect(await hasKey(ui, 'resume-306')).toBe(true)
|
|
3153
3238
|
expect(await hasKey(ui, 'row-305')).toBe(false)
|
|
@@ -3176,7 +3261,7 @@ for (const surface of SURFACES) {
|
|
|
3176
3261
|
await pressDashboard(ui, r)
|
|
3177
3262
|
await pressMap(ui, r, 14)
|
|
3178
3263
|
expect(await textOf(ui, 'map')).toBe('#14 older map')
|
|
3179
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3264
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
3180
3265
|
|
|
3181
3266
|
await pressDashboard(ui, r)
|
|
3182
3267
|
await pressMap(ui, r, 198)
|
|
@@ -3228,7 +3313,7 @@ for (const surface of SURFACES) {
|
|
|
3228
3313
|
const ui = await openAndMount($, r)
|
|
3229
3314
|
expect(await textOf(ui, 'empty')).toBe('No active map. Run /trailhead:map to pick one.')
|
|
3230
3315
|
await pressDashboard(ui, r)
|
|
3231
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3316
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-302', 'row-301', 'row-303'])
|
|
3232
3317
|
await ui.press({ key: 'to-map' })
|
|
3233
3318
|
await settle(r)
|
|
3234
3319
|
expect(await textOf(ui, 'empty')).toBe('No active map. Run /trailhead:map to pick one.')
|
|
@@ -3242,7 +3327,7 @@ for (const surface of SURFACES) {
|
|
|
3242
3327
|
await $.command.run({ command: 'clear', args: '' } as never)
|
|
3243
3328
|
await settle(r)
|
|
3244
3329
|
expect(await textOf(ui, 'dashboard-title')).toBe('Dashboard')
|
|
3245
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3330
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-302', 'row-301', 'row-303'])
|
|
3246
3331
|
})
|
|
3247
3332
|
test(`the pane keeps its repo when the session cwd moves out of it [${surface}]`, async ($, on) => {
|
|
3248
3333
|
const r = rig(on, $)
|
|
@@ -3281,7 +3366,7 @@ for (const surface of SURFACES) {
|
|
|
3281
3366
|
await completeTurn($)
|
|
3282
3367
|
await settle(r)
|
|
3283
3368
|
expect(await textOf(ui, 'map')).toBe('#14 older map')
|
|
3284
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3369
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
3285
3370
|
expect(r.calls).toContainEqual(MAPS_CALL)
|
|
3286
3371
|
expect(r.cwds.every(cwd => cwd === ROOT_OTHER)).toBe(true)
|
|
3287
3372
|
// The toplevel is read where the session now is, not at its project root, which a shell `cd` leaves behind.
|
|
@@ -3369,7 +3454,7 @@ for (const surface of SURFACES) {
|
|
|
3369
3454
|
await $.turn.start({ text: 'go', turnId: 'race-1' })
|
|
3370
3455
|
release()
|
|
3371
3456
|
await settle(r)
|
|
3372
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3457
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
3373
3458
|
})
|
|
3374
3459
|
|
|
3375
3460
|
test(`a refresh in flight when the pin moves to another checkout of the same repo is read again [${surface}]`, async ($, on) => {
|
|
@@ -3401,7 +3486,7 @@ for (const surface of SURFACES) {
|
|
|
3401
3486
|
await r.clock.advance(FLOOR)
|
|
3402
3487
|
await settle(r)
|
|
3403
3488
|
// The dropped read is repeated: the pane shows the new tracker state, not the old list.
|
|
3404
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3489
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
3405
3490
|
})
|
|
3406
3491
|
|
|
3407
3492
|
test(`a same-repo pin move landing inside the stale check is read again [${surface}]`, async ($, on) => {
|
|
@@ -3433,7 +3518,7 @@ for (const surface of SURFACES) {
|
|
|
3433
3518
|
await settle(r)
|
|
3434
3519
|
await r.clock.advance(FLOOR)
|
|
3435
3520
|
await settle(r)
|
|
3436
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3521
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
3437
3522
|
})
|
|
3438
3523
|
|
|
3439
3524
|
test(`a dashboard read in flight when the pin moves to another checkout of the same repo is read again [${surface}]`, async ($, on) => {
|
|
@@ -3562,7 +3647,7 @@ for (const surface of SURFACES) {
|
|
|
3562
3647
|
expect(await keysOf(ui, 'row-')).not.toContain('row-209')
|
|
3563
3648
|
await settle(r)
|
|
3564
3649
|
expect(await textOf(ui, 'map')).toBe('#14 older map')
|
|
3565
|
-
expect(await keysOf(ui, 'row-')).toEqual(['row-
|
|
3650
|
+
expect(await keysOf(ui, 'row-')).toEqual(['row-141', 'row-140'])
|
|
3566
3651
|
})
|
|
3567
3652
|
|
|
3568
3653
|
// The panel follows the whiteboard ticket this session takes (#306).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { expect, test } from 'claude-code/testing'
|
|
2
|
+
|
|
3
|
+
import { parseTickets } from '../hooks/mod/map-pane-view'
|
|
4
|
+
import { elsewhereInProgress, myTicketsFromRows, parseLogin, scopeLine } from '../hooks/mod/my-tickets'
|
|
5
|
+
|
|
6
|
+
const row = (number: number, names: string[], logins: string[] = ['me']) => ({
|
|
7
|
+
number,
|
|
8
|
+
title: `t${number}`,
|
|
9
|
+
url: `https://github.com/o/r/issues/${number}`,
|
|
10
|
+
labels: names.map(name => ({ name })),
|
|
11
|
+
assignees: logins.map(login => ({ login })),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('parseLogin reads the viewer login, null on anything else', () => {
|
|
15
|
+
expect(parseLogin(JSON.stringify({ data: { viewer: { login: 'me' } } }))).toBe('me')
|
|
16
|
+
expect(parseLogin(JSON.stringify({ data: { viewer: { login: '' } } }))).toBeNull()
|
|
17
|
+
expect(parseLogin(JSON.stringify({ errors: [{}], data: { viewer: { login: 'me' } } }))).toBeNull()
|
|
18
|
+
expect(parseLogin('not json')).toBeNull()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test('myTicketsFromRows tags each ticket with its map or the whiteboard, and drops the rest', () => {
|
|
22
|
+
const mine = myTicketsFromRows([
|
|
23
|
+
row(5, ['trailhead:ticket', 'trailhead:build', 'trailhead:map-12']),
|
|
24
|
+
row(6, ['trailhead:ticket', 'trailhead:bug', 'trailhead:whiteboard']),
|
|
25
|
+
// no scope, contradictory scopes, and quarantined: never shown
|
|
26
|
+
row(7, ['trailhead:ticket', 'trailhead:build']),
|
|
27
|
+
row(8, ['trailhead:ticket', 'trailhead:map-1', 'trailhead:whiteboard']),
|
|
28
|
+
row(9, ['trailhead:ticket', 'trailhead:map-1', 'trailhead:map-2']),
|
|
29
|
+
row(10, ['trailhead:ticket', 'trailhead:map-1', 'trailhead:unverified']),
|
|
30
|
+
])
|
|
31
|
+
expect(mine.map(t => [t.number, t.scope])).toEqual([
|
|
32
|
+
[5, { kind: 'map', number: 12 }],
|
|
33
|
+
[6, { kind: 'whiteboard' }],
|
|
34
|
+
])
|
|
35
|
+
expect(mine[0]?.state).toBe('in-progress')
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test('elsewhereInProgress keeps my in-progress tickets of the other scopes only', () => {
|
|
39
|
+
const mine = myTicketsFromRows([
|
|
40
|
+
row(1, ['trailhead:ticket', 'trailhead:map-12']),
|
|
41
|
+
row(2, ['trailhead:ticket', 'trailhead:map-30']),
|
|
42
|
+
row(3, ['trailhead:ticket', 'trailhead:whiteboard']),
|
|
43
|
+
// not in progress: paused, blocked, needs-you, UAT
|
|
44
|
+
row(4, ['trailhead:ticket', 'trailhead:map-30', 'trailhead:paused']),
|
|
45
|
+
row(5, ['trailhead:ticket', 'trailhead:map-30', 'trailhead:blocked']),
|
|
46
|
+
row(6, ['trailhead:ticket', 'trailhead:map-30', 'trailhead:needs-you']),
|
|
47
|
+
row(7, ['trailhead:ticket', 'trailhead:map-30', 'trailhead:uat']),
|
|
48
|
+
])
|
|
49
|
+
expect(elsewhereInProgress(mine, { kind: 'map', number: 12 }, []).map(t => t.number)).toEqual([2, 3])
|
|
50
|
+
expect(elsewhereInProgress(mine, { kind: 'whiteboard' }, []).map(t => t.number)).toEqual([1, 2])
|
|
51
|
+
// a ticket the view already lists is never shown twice
|
|
52
|
+
const shown = parseTickets(JSON.stringify([row(2, ['trailhead:ticket', 'trailhead:map-12'])]))?.tickets ?? []
|
|
53
|
+
expect(elsewhereInProgress(mine, { kind: 'map', number: 12 }, shown).map(t => t.number)).toEqual([3])
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('scopeLine names the map by number and title, or the whiteboard, cut to the width', () => {
|
|
57
|
+
const [onMap, onBoard] = myTicketsFromRows([
|
|
58
|
+
row(1, ['trailhead:ticket', 'trailhead:map-30']),
|
|
59
|
+
row(2, ['trailhead:ticket', 'trailhead:whiteboard']),
|
|
60
|
+
])
|
|
61
|
+
const maps = [{ number: 30, title: 'Billing rework', counts: null }]
|
|
62
|
+
expect(onMap && scopeLine(onMap, maps, 60)).toBe('on map #30 Billing rework')
|
|
63
|
+
expect(onMap && scopeLine(onMap, [], 60)).toBe('on map #30')
|
|
64
|
+
expect(onBoard && scopeLine(onBoard, maps, 60)).toBe('on whiteboard')
|
|
65
|
+
expect((onMap && scopeLine(onMap, maps, 16))?.length).toBeLessThanOrEqual(16)
|
|
66
|
+
})
|
|
@@ -112,12 +112,27 @@ test('repoFromGit keeps a repo with no origin remote unpinned, so the last pin s
|
|
|
112
112
|
expect(nextPin(current, repo)).toBe(current)
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
const fromBase64url = (token: string): string => {
|
|
116
|
+
const bytes = Uint8Array.from(atob(token.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(token.length / 4) * 4, '=')), c => c.charCodeAt(0))
|
|
117
|
+
return new TextDecoder().decode(bytes)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
test('checkoutArg hands the root and the repo over as one shell-safe word the helper decodes back', () => {
|
|
116
121
|
for (const root of ['/repo', '/with space/r', "/it's/r", '/-dash/r', '/uni\u00e8\u4e2d/r', '/a"b$c`d']) {
|
|
117
122
|
const arg = checkoutArg({ ...GH, root })
|
|
118
|
-
expect(arg).toMatch(/^--in=[A-Za-z0-9_-]+$/)
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
expect(
|
|
123
|
+
expect(arg).toMatch(/^--in=[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/)
|
|
124
|
+
const [path, repo] = arg.slice('--in='.length).split('.')
|
|
125
|
+
expect(fromBase64url(path)).toBe(root)
|
|
126
|
+
expect(fromBase64url(repo)).toBe('github.com/o/r')
|
|
122
127
|
}
|
|
123
128
|
})
|
|
129
|
+
|
|
130
|
+
test('checkoutArg carries host and a nested slug in the repo part', () => {
|
|
131
|
+
const arg = checkoutArg({ root: '/w/p', host: 'gitlab.com', slug: 'g/sub/r' })
|
|
132
|
+
expect(fromBase64url(arg.split('.')[1])).toBe('gitlab.com/g/sub/r')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('checkoutArg carries a dotless host in the repo part', () => {
|
|
136
|
+
const arg = checkoutArg({ root: '/w/p', host: 'gitea', slug: 'owner/name' })
|
|
137
|
+
expect(fromBase64url(arg.split('.')[1])).toBe('gitea/owner/name')
|
|
138
|
+
})
|
|
@@ -42,6 +42,11 @@ export type PaneTicket = {
|
|
|
42
42
|
uatOf: UatParent | null
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
// Where one of my tickets lives: an open map by number, or the whiteboard.
|
|
46
|
+
export type TicketScope = { kind: 'map'; number: number } | { kind: 'whiteboard' }
|
|
47
|
+
// A ticket assigned to the gh login, tagged with its scope (the In progress group lists it outside that scope).
|
|
48
|
+
export type MyTicket = PaneTicket & { scope: TicketScope }
|
|
49
|
+
|
|
45
50
|
// A map's sub-issue counts: `open` still to do, `total` all of them.
|
|
46
51
|
export type MapCounts = { open: number; total: number }
|
|
47
52
|
// One open map of the repo, as the maps list shows it; `counts` null = unknown, drawn name-only.
|
|
@@ -113,6 +118,8 @@ declare module 'claude-code' {
|
|
|
113
118
|
// The pane shows the dashboard (maps index + whiteboard) instead of a map.
|
|
114
119
|
isDashboard: boolean
|
|
115
120
|
dashboardPane: DashboardPaneData
|
|
121
|
+
// My open tickets across every map and the whiteboard (the last good read), for every view's In progress.
|
|
122
|
+
myTickets: readonly MyTicket[]
|
|
116
123
|
pendingWork: number | null
|
|
117
124
|
// Whether the pending press waits for the turn to end or has been sent.
|
|
118
125
|
pendingPhase: 'queued' | 'starting' | null
|