surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
| Mmods/diff/hooks/git/probes/head-key-of/head-key-of.ts |
| Mmods/diff/hooks/register.ts |
| Mmods/diff/tests/git/probes/head-key-of.test.ts |
| Mmods/diff/tests/register.test.ts |
| @@ -9,9 +9,9 @@ | ||
| 9 | 9 | * A string that changes when HEAD moves: HEAD's text and the timestamps |
| 10 | 10 | * of HEAD, its ref file and packed-refs, each a listed real file. |
| 11 | 11 | * |
| 12 | - * A HEAD that is not a plain file, or a `ref:` outside the safe refname | |
| 13 | - * grammar, is read through `git rev-parse` instead and nothing is stat'ed; | |
| 14 | - * every directory above a stat'ed file was listed as a real directory. | |
| 12 | + * A `ref:` outside the safe refname grammar is dated by HEAD's own log, the | |
| 13 | + * listed real file `logs/HEAD`; without one, or where HEAD is not a plain | |
| 14 | + * file, `git rev-parse` answers instead. | |
| 15 | 15 | * |
| 16 | 16 | * @param deps git, listings, timestamps, a file read |
| 17 | 17 | * @param repository the repository's directories |
| @@ -54,13 +54,16 @@ | ||
| 54 | 54 | const isSymbolic = ref !== '' |
| 55 | 55 | |
| 56 | 56 | const isUnsafeRef = isSymbolic && !isSafeRefName(ref) |
| 57 | + const logged = isUnsafeRef ? await stampIfFile(own, 'logs/HEAD') : null | |
| 58 | + const isUndated = isUnsafeRef && typeof logged !== 'number' | |
| 59 | + const isRefDated = isSymbolic && !isUnsafeRef | |
| 57 | 60 | |
| 58 | - return isUnsafeRef | |
| 61 | + return isUndated | |
| 59 | 62 | ? viaGit() |
| 60 | 63 | : [ |
| 61 | 64 | head, |
| 62 | 65 | await stampIfFile(own, 'HEAD'), |
| 63 | - isSymbolic ? await stampIfFile(common, ref) : null, | |
| 66 | + isRefDated ? await stampIfFile(common, ref) : logged, | |
| 64 | 67 | await stampIfFile(common, 'packed-refs'), |
| 65 | 68 | ].join('|') |
| 66 | 69 | } |
| @@ -33,12 +33,9 @@ | ||
| 33 | 33 | * Registers the diff pane: `/diff` once the built-in stands down, the |
| 34 | 34 | * pane's drawing and refresh, its opening on Claude's first edit, the ask. |
| 35 | 35 | * |
| 36 | - * Git runs when the built-in's would: `session.start` binds the host and | |
| 37 | - * registers `/diff`, and off its dispatch reads the transcript, so a resumed | |
| 38 | - * session whose turns already edited opens as its first edit would; `/diff` | |
| 39 | - * or the main loop's first checkpointed edit with room pins the backend, | |
| 40 | - * until `/clear`, which reads afresh under a pane it leaves open; a docked | |
| 41 | - * pane fetches, then opens. | |
| 36 | + * Git runs when the built-in's would: none at the start; `/diff` or the main | |
| 37 | + * loop's first checkpointed edit with room pins the backend, until `/clear`, | |
| 38 | + * which reads afresh under a pane it leaves open. | |
| 42 | 39 | * |
| 43 | 40 | * @param on the engine's registrar |
| 44 | 41 | */ |
| @@ -150,6 +147,8 @@ | ||
| 150 | 147 | return asked.isAnswered || backend !== null |
| 151 | 148 | } |
| 152 | 149 | |
| 150 | + startPoll(engine, probed) | |
| 151 | + | |
| 153 | 152 | const stored = PaneState.baseModeOf( |
| 154 | 153 | await engine |
| 155 | 154 | .storeGet(Names.baseStoreKeyOf(probed.repository.toplevel)) |
| @@ -282,6 +281,25 @@ | ||
| 282 | 281 | ).includes(null) |
| 283 | 282 | } |
| 284 | 283 | |
| 284 | + function keepBaseline() { | |
| 285 | + const pinned = backend | |
| 286 | + | |
| 287 | + if (!pinned || polled.headKey !== '') { | |
| 288 | + return | |
| 289 | + } | |
| 290 | + | |
| 291 | + void pinned | |
| 292 | + .headKeyOf() | |
| 293 | + .catch(() => '') | |
| 294 | + .then(key => { | |
| 295 | + const isFirst = backend === pinned && polled.headKey === '' | |
| 296 | + | |
| 297 | + if (isFirst) { | |
| 298 | + polled.headKey = key | |
| 299 | + } | |
| 300 | + }) | |
| 301 | + } | |
| 302 | + | |
| 285 | 303 | function startPoll(engine: Host, pinned: Backend.Backend) { |
| 286 | 304 | const readHeadKey = () => pinned.headKeyOf().catch(() => '') |
| 287 | 305 | |
| @@ -353,10 +371,6 @@ | ||
| 353 | 371 | case 'data': |
| 354 | 372 | generation += 1 |
| 355 | 373 | |
| 356 | - if (pinned) { | |
| 357 | - startPoll(engine, pinned) | |
| 358 | - } | |
| 359 | - | |
| 360 | 374 | break |
| 361 | 375 | } |
| 362 | 376 | |
| @@ -400,6 +414,18 @@ | ||
| 400 | 414 | ) |
| 401 | 415 | } |
| 402 | 416 | |
| 417 | + async function markShown( | |
| 418 | + engine: Host, | |
| 419 | + trigger: (typeof Record.SHOWN_TRIGGERS)[number], | |
| 420 | + ): Promise<void> { | |
| 421 | + const sessionId = await engine.sessionId().catch(() => null) | |
| 422 | + | |
| 423 | + if (sessionId !== null && sessionId !== shownSessionId) { | |
| 424 | + shownSessionId = sessionId | |
| 425 | + Record.recorderOf(engine).shown(trigger, Record.widthBucketOf(columns)) | |
| 426 | + } | |
| 427 | + } | |
| 428 | + | |
| 403 | 429 | async function openPane( |
| 404 | 430 | engine: Host, |
| 405 | 431 | trigger: (typeof Record.SHOWN_TRIGGERS)[number], |
| @@ -436,14 +462,10 @@ | ||
| 436 | 462 | } |
| 437 | 463 | |
| 438 | 464 | isPaneOpen = true |
| 465 | + keepBaseline() | |
| 439 | 466 | |
| 440 | - const sessionId = await engine.sessionId().catch(() => null) | |
| 467 | + await markShown(engine, trigger) | |
| 441 | 468 | |
| 442 | - if (sessionId !== null && sessionId !== shownSessionId) { | |
| 443 | - shownSessionId = sessionId | |
| 444 | - Record.recorderOf(engine).shown(trigger, Record.widthBucketOf(columns)) | |
| 445 | - } | |
| 446 | - | |
| 447 | 469 | const isStale = isDialog || landed !== landedBefore |
| 448 | 470 | |
| 449 | 471 | if (isStale) { |
| @@ -506,10 +528,11 @@ | ||
| 506 | 528 | } |
| 507 | 529 | |
| 508 | 530 | async function openOnRestore(engine: Host): Promise<void> { |
| 509 | - const messages = await engine.messages().catch((): SessionMessage[] => []) | |
| 531 | + hasRestoredEdits = | |
| 532 | + Turns.turnDiffsOf( | |
| 533 | + await engine.messages().catch((): SessionMessage[] => []), | |
| 534 | + ).length > 0 | |
| 510 | 535 | |
| 511 | - hasRestoredEdits = Turns.turnDiffsOf(messages).length > 0 | |
| 512 | - | |
| 513 | 536 | if (hasRestoredEdits) { |
| 514 | 537 | await openOnFirstEdit(engine) |
| 515 | 538 | } |
| @@ -687,8 +710,7 @@ | ||
| 687 | 710 | |
| 688 | 711 | on('ui.render', { component: 'PromptHint' }, ($, e, next) => { |
| 689 | 712 | if (isOnPaneSurface(e)) { |
| 690 | - const viewport: { columns?: number; isFullscreen?: boolean } | undefined = | |
| 691 | - e.viewport | |
| 713 | + const { viewport } = e | |
| 692 | 714 | |
| 693 | 715 | const isFirstMeasure = columns === null && viewport?.columns !== undefined |
| 694 | 716 | |
| @@ -903,7 +925,9 @@ | ||
| 903 | 925 | (isResume ? sessionStartMs : await host.now()) |
| 904 | 926 | |
| 905 | 927 | if (isKeptOpen) { |
| 928 | + await markShown(host, 'manual') | |
| 906 | 929 | await pinBackend(host) |
| 930 | + keepBaseline() | |
| 907 | 931 | void refresh(host) |
| 908 | 932 | } |
| 909 | 933 |
| @@ -16,6 +16,7 @@ | ||
| 16 | 16 | function recorded( |
| 17 | 17 | listings: Readonly<Record<string, ReadonlyMap<string, Git.EntryKind>>>, |
| 18 | 18 | head: string, |
| 19 | + stampOf: (path: string) => number = () => Fixtures.STAMP, | |
| 19 | 20 | ): Recorded { |
| 20 | 21 | const statted: string[] = [] |
| 21 | 22 | const read: string[] = [] |
| @@ -30,7 +31,7 @@ | ||
| 30 | 31 | mtimeOf: path => { |
| 31 | 32 | statted.push(path) |
| 32 | 33 | |
| 33 | - return Promise.resolve(Fixtures.STAMP) | |
| 34 | + return Promise.resolve(stampOf(path)) | |
| 34 | 35 | }, |
| 35 | 36 | readFile: path => { |
| 36 | 37 | read.push(path) |
| @@ -57,6 +58,27 @@ | ||
| 57 | 58 | '/r/.git/refs/heads': heads, |
| 58 | 59 | } |
| 59 | 60 | |
| 61 | + const logged = { | |
| 62 | + '/r/.git': new Map<string, Git.EntryKind>([ | |
| 63 | + ...plainGitDir, | |
| 64 | + ['logs', 'dir'], | |
| 65 | + ]), | |
| 66 | + '/r/.git/logs': new Map<string, Git.EntryKind>([['HEAD', 'file']]), | |
| 67 | + } | |
| 68 | + | |
| 69 | + async function idleOn(branch: string) { | |
| 70 | + const probe = recorded(logged, `ref: refs/heads/${branch}\n`) | |
| 71 | + | |
| 72 | + return { | |
| 73 | + keys: new Set([ | |
| 74 | + await Git.headKeyOf(probe.deps, Fixtures.CHECKOUT), | |
| 75 | + await Git.headKeyOf(probe.deps, Fixtures.CHECKOUT), | |
| 76 | + await Git.headKeyOf(probe.deps, Fixtures.CHECKOUT), | |
| 77 | + ]).size, | |
| 78 | + ran: probe.ran, | |
| 79 | + } | |
| 80 | + } | |
| 81 | + | |
| 60 | 82 | test('a plain checkout reads HEAD, its ref file, packed-refs', async () => { |
| 61 | 83 | const probe = recorded(healthy, 'ref: refs/heads/main\n') |
| 62 | 84 | const key = await Git.headKeyOf(probe.deps, Fixtures.CHECKOUT) |
| @@ -79,6 +101,37 @@ | ||
| 79 | 101 | expect(key).toBe(`rev:${Fixtures.HEAD_SHA}`) |
| 80 | 102 | }) |
| 81 | 103 | |
| 104 | + test('a branch named with a # starts no git while idle', async () => { | |
| 105 | + expect(await idleOn('fix/#12')).toEqual({ ran: [], keys: 1 }) | |
| 106 | + }) | |
| 107 | + | |
| 108 | + test('a branch named outside ASCII starts no git while idle', async () => { | |
| 109 | + expect(await idleOn('höher')).toEqual({ ran: [], keys: 1 }) | |
| 110 | + }) | |
| 111 | + | |
| 112 | + test('on such a branch a moved HEAD is seen at the next look', async () => { | |
| 113 | + let moves = 0 | |
| 114 | + | |
| 115 | + const probe = recorded(logged, 'ref: refs/heads/fix/#12\n', path => | |
| 116 | + path.endsWith('/logs/HEAD') ? Fixtures.STAMP + moves : Fixtures.STAMP, | |
| 117 | + ) | |
| 118 | + | |
| 119 | + const before = await Git.headKeyOf(probe.deps, Fixtures.CHECKOUT) | |
| 120 | + | |
| 121 | + moves += 1 | |
| 122 | + | |
| 123 | + expect(await Git.headKeyOf(probe.deps, Fixtures.CHECKOUT)).not.toBe(before) | |
| 124 | + expect(probe.ran, 'dated by the log of HEAD, no git started').toEqual([]) | |
| 125 | + }) | |
| 126 | + | |
| 127 | + test('with no log of HEAD such a branch is read through git', async () => { | |
| 128 | + const probe = recorded(healthy, 'ref: refs/heads/fix/#12\n') | |
| 129 | + const key = await Git.headKeyOf(probe.deps, Fixtures.CHECKOUT) | |
| 130 | + | |
| 131 | + expect(probe.statted).toEqual([]) | |
| 132 | + expect(key).toBe(`rev:${Fixtures.HEAD_SHA}`) | |
| 133 | + }) | |
| 134 | + | |
| 82 | 135 | test('a HEAD that is a symbolic link is never read or stat-ed', async () => { |
| 83 | 136 | const probe = recorded( |
| 84 | 137 | { |
| @@ -1,13 +1,54 @@ | ||
| 1 | -import type { Args, ResultOf, SessionMessage } from 'claude-code' | |
| 1 | +import type { Args, FsEntry, On, ResultOf, SessionMessage } from 'claude-code' | |
| 2 | 2 | import { describe, expect, mock, test, tier } from 'claude-code/testing' |
| 3 | +import type { Engine } from 'claude-code/testing' | |
| 3 | 4 | |
| 4 | 5 | import Limits from '../hooks/limits' |
| 5 | 6 | import Names from '../hooks/names' |
| 7 | +import Record from '../hooks/record' | |
| 6 | 8 | import Fixtures from './fixtures' |
| 7 | 9 | |
| 8 | 10 | tier('builtin') |
| 9 | 11 | |
| 10 | 12 | describe('register', () => { |
| 13 | + async function drawnRoundMerge($: Engine, on: On, finishedMs: number) { | |
| 14 | + const script = { ...Fixtures.REPOSITORY, 'rev-parse --verify': 'merging' } | |
| 15 | + | |
| 16 | + let listing = Fixtures.MERGING.map(([name, kind]): FsEntry => ({ | |
| 17 | + name, | |
| 18 | + kind, | |
| 19 | + size: 0, | |
| 20 | + isLink: false, | |
| 21 | + })) | |
| 22 | + | |
| 23 | + on('fs.list', (_engine, e) => { | |
| 24 | + const isGitDir = e.path === '/work/.git' | |
| 25 | + | |
| 26 | + return { value: isGitDir ? listing : [] } | |
| 27 | + }) | |
| 28 | + | |
| 29 | + const world = Fixtures.inRepository(on, script) | |
| 30 | + | |
| 31 | + const drawn = async () => Fixtures.textOf(await $.ui.render(Fixtures.PANE)) | |
| 32 | + | |
| 33 | + await $.session.start(Fixtures.SESSION) | |
| 34 | + await $.command.run(Fixtures.DIFF) | |
| 35 | + await world.clock.advance(finishedMs) | |
| 36 | + | |
| 37 | + const before = await drawn() | |
| 38 | + | |
| 39 | + listing = [] | |
| 40 | + script['rev-parse --verify'] = 'merged' | |
| 41 | + | |
| 42 | + await world.clock.advance(Limits.HEAD_POLL_MS + Fixtures.SETTLE_MS) | |
| 43 | + | |
| 44 | + return { before, after: await drawn() } | |
| 45 | + } | |
| 46 | + | |
| 47 | + const NOTICED = { | |
| 48 | + before: expect.stringContaining('Diff unavailable'), | |
| 49 | + after: expect.stringContaining('1 file changed'), | |
| 50 | + } | |
| 51 | + | |
| 11 | 52 | test('the start asks nothing of git and registers /diff', async ($, on) => { |
| 12 | 53 | const world = Fixtures.inRepository(on) |
| 13 | 54 | |
| @@ -121,19 +162,19 @@ | ||
| 121 | 162 | ) |
| 122 | 163 | }) |
| 123 | 164 | |
| 124 | - test('a shell command the tool held read-only fetches nothing', async ($, on) => { | |
| 165 | + test('a shell command held read-only fetches nothing', async ($, on) => { | |
| 125 | 166 | const world = Fixtures.inRepository(on) |
| 126 | 167 | |
| 127 | 168 | const fetchesSince = (read: number) => |
| 128 | 169 | world.runs.slice(read).filter(run => run.argv.includes('--numstat')) |
| 129 | 170 | .length |
| 130 | 171 | |
| 131 | - on('tool.call', ($, e) => | |
| 132 | - e.tool === 'Bash' && e.command === 'ls' | |
| 133 | - ? Fixtures.READ_ONLY_ANSWER | |
| 134 | - : { result: 'done' }, | |
| 135 | - ) | |
| 172 | + on('tool.call', ($, e) => { | |
| 173 | + const isListing = e.tool === 'Bash' && e.command === 'ls' | |
| 136 | 174 | |
| 175 | + return isListing ? Fixtures.READ_ONLY_ANSWER : { result: 'done' } | |
| 176 | + }) | |
| 177 | + | |
| 137 | 178 | await $.session.start(Fixtures.SESSION) |
| 138 | 179 | await $.command.run(Fixtures.DIFF) |
| 139 | 180 | await world.clock.advance(Fixtures.SETTLE_MS) |
| @@ -383,7 +424,7 @@ | ||
| 383 | 424 | expect(world.opened.map(pane => pane.id)).toEqual(['diff']) |
| 384 | 425 | }) |
| 385 | 426 | |
| 386 | - test('on the main screen the first edit opens nothing, and /diff still opens the dialog', async ($, on) => { | |
| 427 | + test('on the main screen only /diff opens, as a dialog', async ($, on) => { | |
| 387 | 428 | const world = Fixtures.inRepository(on) |
| 388 | 429 | |
| 389 | 430 | on('tool.call', () => ({ result: 'edited' })) |
| @@ -411,7 +452,7 @@ | ||
| 411 | 452 | ).toMatchObject({ id: 'diff', focus: true }) |
| 412 | 453 | }) |
| 413 | 454 | |
| 414 | - test('a surface that does not say whether it docks a pane opens nothing at the first edit', async ($, on) => { | |
| 455 | + test('a surface silent on docking opens nothing by itself', async ($, on) => { | |
| 415 | 456 | const world = Fixtures.inRepository(on) |
| 416 | 457 | |
| 417 | 458 | on('tool.call', () => ({ result: 'edited' })) |
| @@ -716,7 +757,7 @@ | ||
| 716 | 757 | expect(drawn).not.toContain('Loading diff') |
| 717 | 758 | }) |
| 718 | 759 | |
| 719 | - test('/clear leaves the pane it finds open up and reads the repository afresh', async ($, on) => { | |
| 760 | + test('/clear leaves an open pane up and reads afresh', async ($, on) => { | |
| 720 | 761 | const world = Fixtures.inRepository(on) |
| 721 | 762 | |
| 722 | 763 | on('command.run', { command: 'clear' }, () => ({})) |
| @@ -745,7 +786,70 @@ | ||
| 745 | 786 | ).toContain('1 file changed') |
| 746 | 787 | }) |
| 747 | 788 | |
| 748 | - test('a resumed session whose turns edited opens the pane before any new edit', async ($, on) => { | |
| 789 | + test('/clear under an open pane counts it shown again', async ($, on) => { | |
| 790 | + const world = Fixtures.inRepository(on) | |
| 791 | + const logged = Fixtures.keeping<Args<'telemetry.log'>>() | |
| 792 | + | |
| 793 | + let sessionId = 'first' | |
| 794 | + | |
| 795 | + on('telemetry.log', logged.hook) | |
| 796 | + on('session.id', () => ({ value: sessionId })) | |
| 797 | + on('tool.call', () => ({ result: 'edited' })) | |
| 798 | + | |
| 799 | + on('command.run', { command: 'clear' }, () => { | |
| 800 | + sessionId = 'second' | |
| 801 | + | |
| 802 | + return {} | |
| 803 | + }) | |
| 804 | + | |
| 805 | + await $.session.start(Fixtures.SESSION) | |
| 806 | + await $.ui.render(Fixtures.HINT) | |
| 807 | + | |
| 808 | + await $.tool.call({ | |
| 809 | + tool: 'Edit', | |
| 810 | + file_path: '/work/app.ts', | |
| 811 | + old_string: '1', | |
| 812 | + new_string: '2', | |
| 813 | + }) | |
| 814 | + | |
| 815 | + await world.clock.advance(Fixtures.SETTLE_MS) | |
| 816 | + await $.command.run(Fixtures.CLEAR) | |
| 817 | + await world.clock.advance(Fixtures.SETTLE_MS) | |
| 818 | + | |
| 819 | + expect(world.closed, 'the pane stayed up across /clear').toEqual([]) | |
| 820 | + | |
| 821 | + expect( | |
| 822 | + logged.kept.map(shown => shown.props?.trigger), | |
| 823 | + "once a conversation: Claude's edit opened the first, and the second, " + | |
| 824 | + "which nobody opened, reads as the person's, as the built-in's does", | |
| 825 | + ).toEqual([ | |
| 826 | + { value: 'auto_open', of: [...Record.SHOWN_TRIGGERS] }, | |
| 827 | + { value: 'manual', of: [...Record.SHOWN_TRIGGERS] }, | |
| 828 | + ]) | |
| 829 | + }) | |
| 830 | + | |
| 831 | + test('a merge finished 3 s after the open is noticed', async ($, on) => { | |
| 832 | + expect( | |
| 833 | + await drawnRoundMerge($, on, 3000), | |
| 834 | + 'finished in another terminal: read with no edit or command', | |
| 835 | + ).toEqual(NOTICED) | |
| 836 | + }) | |
| 837 | + | |
| 838 | + test('a merge finished 1.2 s after the open is noticed', async ($, on) => { | |
| 839 | + expect( | |
| 840 | + await drawnRoundMerge($, on, 1200), | |
| 841 | + 'before the first look at HEAD on the clock: the open read it', | |
| 842 | + ).toEqual(NOTICED) | |
| 843 | + }) | |
| 844 | + | |
| 845 | + test('a merge finished 0.3 s after the open is noticed', async ($, on) => { | |
| 846 | + expect( | |
| 847 | + await drawnRoundMerge($, on, 300), | |
| 848 | + 'before the first look at HEAD on the clock: the open read it', | |
| 849 | + ).toEqual(NOTICED) | |
| 850 | + }) | |
| 851 | + | |
| 852 | + test('a resumed session that edited opens before an edit', async ($, on) => { | |
| 749 | 853 | const world = Fixtures.inRepository(on, Fixtures.REPOSITORY, { |
| 750 | 854 | messages: () => Fixtures.EDITED_TRANSCRIPT, |
| 751 | 855 | }) |
| @@ -763,7 +867,7 @@ | ||
| 763 | 867 | ).toEqual(['diff']) |
| 764 | 868 | }) |
| 765 | 869 | |
| 766 | - test('a resumed session opens nothing where its first edit would not', async ($, on) => { | |
| 870 | + test('a resumed session opens only where an edit would', async ($, on) => { | |
| 767 | 871 | const narrow = Fixtures.inRepository(on, Fixtures.REPOSITORY, { |
| 768 | 872 | messages: () => Fixtures.EDITED_TRANSCRIPT, |
| 769 | 873 | }) |
| @@ -779,7 +883,7 @@ | ||
| 779 | 883 | expect(narrow.runs, 'and no git for it').toEqual([]) |
| 780 | 884 | }) |
| 781 | 885 | |
| 782 | - test('a resumed session the person kept the pane open in opens it from the lower floor', async ($, on) => { | |
| 886 | + test('a resumed session kept open opens at the low floor', async ($, on) => { | |
| 783 | 887 | const world = Fixtures.inRepository(on, Fixtures.REPOSITORY, { |
| 784 | 888 | messages: () => Fixtures.EDITED_TRANSCRIPT, |
| 785 | 889 | stored: { [Names.STORE_OPEN_KEY]: true }, |
| @@ -805,7 +909,7 @@ | ||
| 805 | 909 | expect(world.runs).toEqual([]) |
| 806 | 910 | }) |
| 807 | 911 | |
| 808 | - test('a resumed session whose pane the person closed opens nothing', async ($, on) => { | |
| 912 | + test('a resumed session the person closed opens nothing', async ($, on) => { | |
| 809 | 913 | const world = Fixtures.inRepository(on, Fixtures.REPOSITORY, { |
| 810 | 914 | messages: () => Fixtures.EDITED_TRANSCRIPT, |
| 811 | 915 | stored: { [Names.STORE_OPEN_KEY]: false }, |
| @@ -819,7 +923,7 @@ | ||
| 819 | 923 | expect(world.runs).toEqual([]) |
| 820 | 924 | }) |
| 821 | 925 | |
| 822 | - test('/resume closes the pane, then opens it for the turns it brought back', async ($, on) => { | |
| 926 | + test('/resume closes the pane, reopens it for its turns', async ($, on) => { | |
| 823 | 927 | let transcript: readonly SessionMessage[] = [] |
| 824 | 928 | |
| 825 | 929 | const world = Fixtures.inRepository(on, Fixtures.REPOSITORY, { |
| @@ -839,9 +943,10 @@ | ||
| 839 | 943 | await $.command.run(Fixtures.RESUME) |
| 840 | 944 | await world.clock.advance(Fixtures.SETTLE_MS) |
| 841 | 945 | |
| 842 | - expect(world.closed.map(pane => pane.id), 'closed at /resume').toEqual([ | |
| 843 | - 'diff', | |
| 844 | - ]) | |
| 946 | + expect( | |
| 947 | + world.closed.map(pane => pane.id), | |
| 948 | + 'closed at /resume', | |
| 949 | + ).toEqual(['diff']) | |
| 845 | 950 | |
| 846 | 951 | expect( |
| 847 | 952 | world.opened.map(pane => pane.id), |
| @@ -934,7 +1039,7 @@ | ||
| 934 | 1039 | const { 'rev-parse --path-format=absolute': worktree = '', ...notYet } = |
| 935 | 1040 | Fixtures.oneSecret() |
| 936 | 1041 | |
| 937 | - const script: Record<string, string> = notYet | |
| 1042 | + const script = { ...notYet } | |
| 938 | 1043 | const world = Fixtures.inRepository(on, script) |
| 939 | 1044 | |
| 940 | 1045 | const probesOf = () => |
| @@ -991,7 +1096,7 @@ | ||
| 991 | 1096 | const { 'rev-parse --path-format=absolute': worktree = '', ...notYet } = |
| 992 | 1097 | Fixtures.oneSecret() |
| 993 | 1098 | |
| 994 | - const script: Record<string, string> = notYet | |
| 1099 | + const script = { ...notYet } | |
| 995 | 1100 | const world = Fixtures.inRepository(on, script) |
| 996 | 1101 | |
| 997 | 1102 | on('tool.call', () => ({ result: 'edited' })) |