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/README.md |
| Mmods/diff/hooks/register.ts |
| Mmods/diff/tests/fixtures/beneath/index.ts |
| Amods/diff/tests/fixtures/beneath/slow-diff-ms.ts |
| Mmods/diff/tests/register.test.ts |
| @@ -20,8 +20,10 @@ | ||
| 20 | 20 | the person never chose, 110 when they kept it open before; a person who |
| 21 | 21 | closed it is left alone) and file checkpointing is on; a subagent's edit |
| 22 | 22 | opens nothing, and where the surface does not say, nothing opens by |
| 23 | -itself. An open the engine leaves waiting undrawn is withdrawn, so no | |
| 24 | -later resize seats it, and the next edit asks again. | |
| 23 | +itself. A docked pane fetches before it opens, as the built-in panel | |
| 24 | +primes its data, so it never lands on `Loading diff…`; an open the engine | |
| 25 | +leaves waiting undrawn is withdrawn, so no later resize seats it, and the | |
| 26 | +next edit asks again. | |
| 25 | 27 | |
| 26 | 28 | Under the fullscreen layout a terminal under 110 columns gets the |
| 27 | 29 | built-in's line asking for a wider one and nothing opens. Without that |
| @@ -34,7 +34,7 @@ | ||
| 34 | 34 | * |
| 35 | 35 | * Git runs when the built-in's would: `session.start` binds the host and |
| 36 | 36 | * registers `/diff`; `/diff` or the main loop's first checkpointed edit with |
| 37 | - * room pins the backend, until `/clear`; only a placed, open pane fetches. | |
| 37 | + * room pins the backend, until `/clear`; a docked pane fetches, then opens. | |
| 38 | 38 | * |
| 39 | 39 | * @param on the engine's registrar |
| 40 | 40 | */ |
| @@ -53,6 +53,7 @@ | ||
| 53 | 53 | let isRefreshing = false |
| 54 | 54 | let isRefreshQueued = false |
| 55 | 55 | let generation = 0 |
| 56 | + let landed = 0 | |
| 56 | 57 | let bodyStamp: string | null = null |
| 57 | 58 | let bodyBase: string | null = null |
| 58 | 59 | |
| @@ -409,6 +410,12 @@ | ||
| 409 | 410 | |
| 410 | 411 | dialogRows = isDialog ? Views.dialogRowsOf(model) : null |
| 411 | 412 | |
| 413 | + const landedBefore = landed | |
| 414 | + | |
| 415 | + if (!isDialog) { | |
| 416 | + await refresh(engine).catch(() => undefined) | |
| 417 | + } | |
| 418 | + | |
| 412 | 419 | const opened = await engine.openPane( |
| 413 | 420 | isDialog |
| 414 | 421 | ? { ...dialogPane(), focus: true } |
| @@ -432,8 +439,12 @@ | ||
| 432 | 439 | Record.recorderOf(engine).shown(trigger, Record.widthBucketOf(columns)) |
| 433 | 440 | } |
| 434 | 441 | |
| 435 | - void refresh(engine) | |
| 442 | + const isStale = isDialog || landed !== landedBefore | |
| 436 | 443 | |
| 444 | + if (isStale) { | |
| 445 | + void refresh(engine) | |
| 446 | + } | |
| 447 | + | |
| 437 | 448 | return true |
| 438 | 449 | } |
| 439 | 450 | |
| @@ -860,11 +871,15 @@ | ||
| 860 | 871 | result.deny === undefined && |
| 861 | 872 | result.isError !== true |
| 862 | 873 | |
| 863 | - const isStale = | |
| 864 | - isPaneOpen && | |
| 865 | - (isEdit ? hasEdited : result === undefined || result.deny === undefined) | |
| 874 | + const hasLanded = isEdit | |
| 875 | + ? hasEdited | |
| 876 | + : result === undefined || result.deny === undefined | |
| 866 | 877 | |
| 867 | - if (isStale) { | |
| 878 | + if (hasLanded) { | |
| 879 | + landed += 1 | |
| 880 | + } | |
| 881 | + | |
| 882 | + if (hasLanded && isPaneOpen) { | |
| 868 | 883 | scheduleRefresh(engine) |
| 869 | 884 | } |
| 870 | 885 |
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | export type * from './beneath.js' |
| 2 | 2 | export * from './left-waiting.js' |
| 3 | +export * from './slow-diff-ms.js' | |
| 3 | 4 | |
| 4 | 5 | export * as default from '.' |
| @@ -1,0 +1,5 @@ | ||
| 1 | +/** | |
| 2 | + * How long a slow repository takes to answer `git diff` in the tests that | |
| 3 | + * time the pane's opening against its first fetch: well inside git's timeout. | |
| 4 | + */ | |
| 5 | +export const SLOW_DIFF_MS = 3000 |
| @@ -565,9 +565,15 @@ | ||
| 565 | 565 | ).toEqual(['diff']) |
| 566 | 566 | |
| 567 | 567 | expect( |
| 568 | + world.runs.map(run => Fixtures.gitWordOf(run.argv)).slice(0, 2), | |
| 569 | + 'the repository was found and read once before the open, as the ' + | |
| 570 | + 'built-in primes; nothing polls for a pane no one sees', | |
| 571 | + ).toEqual(['rev-parse --show-toplevel', 'status']) | |
| 572 | + | |
| 573 | + expect( | |
| 568 | 574 | world.runs.map(run => Fixtures.gitWordOf(run.argv)), |
| 569 | - 'the repository was found, nothing fetched for a pane no one sees', | |
| 570 | - ).toEqual(['rev-parse --show-toplevel']) | |
| 575 | + 'no HEAD poll for a withdrawn pane', | |
| 576 | + ).not.toContain(Fixtures.POLL_WORD) | |
| 571 | 577 | |
| 572 | 578 | isNarrow = false |
| 573 | 579 | |
| @@ -622,6 +628,59 @@ | ||
| 622 | 628 | ).toEqual(['diff']) |
| 623 | 629 | }) |
| 624 | 630 | |
| 631 | + test('a docked pane opens once its first fetch settled', async ($, on) => { | |
| 632 | + const opened: string[] = [] | |
| 633 | + const clock = Fixtures.startsSession(on) | |
| 634 | + | |
| 635 | + on('process.run', async ($, e) => { | |
| 636 | + if (e.argv.includes('--shortstat')) { | |
| 637 | + await clock.sleep(Fixtures.SLOW_DIFF_MS) | |
| 638 | + } | |
| 639 | + | |
| 640 | + return { value: Fixtures.gitIn(e.argv) } | |
| 641 | + }) | |
| 642 | + | |
| 643 | + on('ui.open', ($, e) => { | |
| 644 | + opened.push(e.id) | |
| 645 | + | |
| 646 | + return { value: undefined } | |
| 647 | + }) | |
| 648 | + | |
| 649 | + on('ui.close', () => ({ value: undefined })) | |
| 650 | + on('ui.invalidate', () => ({ value: undefined })) | |
| 651 | + on('ui.render', { component: 'PromptHint' }, () => Fixtures.HINT_DRAWN) | |
| 652 | + on('session.messages', () => ({ value: [] })) | |
| 653 | + on('settings.read', () => ({ value: {} })) | |
| 654 | + on('tool.call', () => ({ result: 'edited' })) | |
| 655 | + mock.store(on) | |
| 656 | + mock.env(on, {}) | |
| 657 | + | |
| 658 | + await $.session.start(Fixtures.SESSION) | |
| 659 | + await $.ui.render(Fixtures.HINT) | |
| 660 | + | |
| 661 | + await $.tool.call({ | |
| 662 | + tool: 'Edit', | |
| 663 | + file_path: '/work/app.ts', | |
| 664 | + old_string: '1', | |
| 665 | + new_string: '2', | |
| 666 | + }) | |
| 667 | + | |
| 668 | + await clock.advance(Fixtures.SLOW_DIFF_MS - 1) | |
| 669 | + | |
| 670 | + expect(opened, 'git has not answered: no pane, no Loading frame').toEqual( | |
| 671 | + [], | |
| 672 | + ) | |
| 673 | + | |
| 674 | + await clock.advance(Fixtures.SETTLE_MS) | |
| 675 | + | |
| 676 | + expect(opened, 'the fetch settled: the pane opens filled').toEqual(['diff']) | |
| 677 | + | |
| 678 | + const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE)) | |
| 679 | + | |
| 680 | + expect(drawn).toContain('1 file changed') | |
| 681 | + expect(drawn).not.toContain('Loading diff') | |
| 682 | + }) | |
| 683 | + | |
| 625 | 684 | test('/clear closes the pane it finds open', async ($, on) => { |
| 626 | 685 | const world = Fixtures.inRepository(on) |
| 627 | 686 |