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
* mods: the declarations are 2.1.273's, which add the vscode surface, plus a render viewport's layout * diff: the first edit opens the pane only where the layout docks it beside the transcript
| @@ -14,8 +14,11 @@ | ||
| 14 | 14 | as Claude edits and runs shell commands, and while it is open it polls |
| 15 | 15 | the repository's HEAD so a commit or checkout made elsewhere shows too. |
| 16 | 16 | The first successful edit of a session opens the pane by itself where the |
| 17 | -terminal is wide enough (144 columns when the person never chose, 110 when | |
| 18 | -they kept it open before; a person who closed it is left alone). | |
| 17 | +layout docks it beside the transcript (the fullscreen layout, which each | |
| 18 | +drawing's `viewport` says) and the terminal is wide enough (144 columns | |
| 19 | +when the person never chose, 110 when they kept it open before; a person | |
| 20 | +who closed it is left alone); where the surface does not say, nothing | |
| 21 | +opens by itself. | |
| 19 | 22 | |
| 20 | 23 | Under the fullscreen layout a terminal under 110 columns gets the |
| 21 | 24 | built-in's line asking for a wider one and nothing opens. Without that |
| @@ -58,7 +61,7 @@ | ||
| 58 | 61 | | event | what the hook does | |
| 59 | 62 | | --- | --- | |
| 60 | 63 | | `session.start` | Binds the engine once and registers `/diff` (a session where another `/diff` is listed leaves the plugin idle); asks nothing of the repository, which `/diff` or the first edit pins when it comes. | |
| 61 | -| `ui.render` of `PromptHint` | Reads the terminal's width, which decides whether the first edit opens the pane. | | |
| 64 | +| `ui.render` of `PromptHint` | Reads the terminal's width and whether its layout docks a pane, which decide whether the first edit opens the pane. | | |
| 62 | 65 | | `ui.render` of `Pane` | Draws the pane: docked, the header, base line, source picker, file list and toggles over the window of hunks; inline, the dialog. | |
| 63 | 66 | | `command.run` of `diff` | Pins the repository when none is, opens or closes the pane (focused and closing on Escape without the fullscreen layout), says which, and remembers the choice. | |
| 64 | 67 | | `ui.close` of the pane | Backs out of the dialog's detail view instead of closing; else remembers the person's close as `/diff`'s. | |
| @@ -9,8 +9,9 @@ | ||
| 9 | 9 | * Everything one drawing of the pane reads. |
| 10 | 10 | * |
| 11 | 11 | * The last good fetch, what the person picked, the bodies read so far, the |
| 12 | - * turns and todos, how the surface seated it, whether fullscreen (once | |
| 13 | - * `/diff` said), the dialog's view, and the docked scroll in its last box. | |
| 12 | + * turns and todos, how the surface seated it, whether the layout docks a | |
| 13 | + * pane (once a drawing or `/diff` said), the dialog's view, and the docked | |
| 14 | + * scroll in its last box. | |
| 14 | 15 | */ |
| 15 | 16 | export type PaneModel = { |
| 16 | 17 | words: Backend.BackendWords |
| @@ -452,7 +452,7 @@ | ||
| 452 | 452 | |
| 453 | 453 | const hasRoom = |
| 454 | 454 | preference !== false && |
| 455 | - model.isFullscreen !== false && | |
| 455 | + model.isFullscreen === true && | |
| 456 | 456 | columns !== null && |
| 457 | 457 | columns >= floor |
| 458 | 458 | |
| @@ -622,6 +622,11 @@ | ||
| 622 | 622 | on('ui.render', { component: 'PromptHint' }, ($, e, next) => { |
| 623 | 623 | if (isOnPaneSurface(e)) { |
| 624 | 624 | columns = e.viewport?.columns ?? columns |
| 625 | + | |
| 626 | + model = { | |
| 627 | + ...model, | |
| 628 | + isFullscreen: e.viewport?.isFullscreen ?? model.isFullscreen, | |
| 629 | + } | |
| 625 | 630 | } |
| 626 | 631 | |
| 627 | 632 | return next(e) |
| @@ -3,13 +3,17 @@ | ||
| 3 | 3 | import { HINT } from './hint.js' |
| 4 | 4 | |
| 5 | 5 | /** |
| 6 | - * The prompt's hint on a terminal so many columns wide: drawing it is how | |
| 7 | - * the plugin learns the width. | |
| 6 | + * The prompt's hint on a fullscreen terminal so many columns wide: drawing | |
| 7 | + * it is how the plugin learns the width. | |
| 8 | 8 | * |
| 9 | 9 | * @param columns the terminal's width |
| 10 | 10 | * @returns the `ui.render` input |
| 11 | 11 | */ |
| 12 | 12 | export const hintAt = (columns: number): RenderInput<'PromptHint'> => ({ |
| 13 | 13 | ...HINT, |
| 14 | - viewport: { columns, rows: HINT.viewport?.rows ?? columns }, | |
| 14 | + viewport: { | |
| 15 | + columns, | |
| 16 | + rows: HINT.viewport?.rows ?? columns, | |
| 17 | + isFullscreen: true, | |
| 18 | + }, | |
| 15 | 19 | }) |
| @@ -1,13 +1,14 @@ | ||
| 1 | 1 | import type { RenderInput } from 'claude-code' |
| 2 | 2 | |
| 3 | 3 | /** |
| 4 | - * The prompt's hint on a 160-column terminal: drawing it is how the plugin | |
| 5 | - * learns how wide the terminal is. | |
| 4 | + * The prompt's hint on a 160-column terminal under the fullscreen layout: | |
| 5 | + * drawing it is how the plugin learns how wide the terminal is and whether a | |
| 6 | + * pane docks beside the transcript there. | |
| 6 | 7 | */ |
| 7 | 8 | export const HINT: RenderInput<'PromptHint'> = { |
| 8 | 9 | component: 'PromptHint', |
| 9 | 10 | surface: 'terminal', |
| 10 | 11 | requestId: 'hint', |
| 11 | - viewport: { columns: 160, rows: 40 }, | |
| 12 | + viewport: { columns: 160, rows: 40, isFullscreen: true }, | |
| 12 | 13 | props: { isDraft: false, isWorking: false, hint: '' }, |
| 13 | 14 | } |
| @@ -25,6 +25,7 @@ | ||
| 25 | 25 | export * from './inline-pane.js' |
| 26 | 26 | export * from './keeping.js' |
| 27 | 27 | export * from './kind-walk-of' |
| 28 | +export * from './main-screen-hint.js' | |
| 28 | 29 | export * from './many-file-count.js' |
| 29 | 30 | export * from './many-files.js' |
| 30 | 31 | export * from './mode-label-of' |
| @@ -51,6 +52,7 @@ | ||
| 51 | 52 | export * from './turn-diffs-of' |
| 52 | 53 | export * from './two-files.js' |
| 53 | 54 | export * from './typed-prompt-of.js' |
| 55 | +export * from './unsaid-hint.js' | |
| 54 | 56 | export * from './vs-main.js' |
| 55 | 57 | export * from './wheel-over-list.js' |
| 56 | 58 | export * from './wheel-tick.js' |
| @@ -1,0 +1,12 @@ | ||
| 1 | +import type { RenderInput } from 'claude-code' | |
| 2 | + | |
| 3 | +import { HINT } from './hint.js' | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * The prompt's hint on the same wide terminal on its main screen | |
| 7 | + * (`CLAUDE_CODE_NO_FLICKER=0`), where nothing docks and a pane opens inline. | |
| 8 | + */ | |
| 9 | +export const MAIN_SCREEN_HINT: RenderInput<'PromptHint'> = { | |
| 10 | + ...HINT, | |
| 11 | + viewport: { columns: 160, rows: 40, isFullscreen: false }, | |
| 12 | +} |
| @@ -1,0 +1,13 @@ | ||
| 1 | +import type { RenderInput } from 'claude-code' | |
| 2 | + | |
| 3 | +import { HINT } from './hint.js' | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * The prompt's hint from a surface that measured its size but did not say | |
| 7 | + * whether it docks a pane: an engine that predates the word, or a remote | |
| 8 | + * surface that has not reported it. | |
| 9 | + */ | |
| 10 | +export const UNSAID_HINT: RenderInput<'PromptHint'> = { | |
| 11 | + ...HINT, | |
| 12 | + viewport: { columns: 160, rows: 40 }, | |
| 13 | +} |
| @@ -348,6 +348,70 @@ | ||
| 348 | 348 | expect(world.opened.map(pane => pane.id)).toEqual(['diff']) |
| 349 | 349 | }) |
| 350 | 350 | |
| 351 | + test('on the main screen the first edit opens nothing, and /diff still opens the dialog', async ($, on) => { | |
| 352 | + const world = Fixtures.inRepository(on) | |
| 353 | + | |
| 354 | + on('tool.call', () => ({ result: 'edited' })) | |
| 355 | + | |
| 356 | + await $.session.start(Fixtures.SESSION) | |
| 357 | + await $.ui.render(Fixtures.MAIN_SCREEN_HINT) | |
| 358 | + | |
| 359 | + await $.tool.call({ | |
| 360 | + tool: 'Edit', | |
| 361 | + file_path: '/work/app.ts', | |
| 362 | + old_string: '1', | |
| 363 | + new_string: '2', | |
| 364 | + }) | |
| 365 | + | |
| 366 | + await world.clock.advance(Fixtures.SETTLE_MS) | |
| 367 | + | |
| 368 | + expect(world.opened, 'a pane there would be an unasked dialog').toEqual([]) | |
| 369 | + expect(world.runs, 'so nothing was asked of the repository').toEqual([]) | |
| 370 | + | |
| 371 | + expect(await $.command.run(Fixtures.DIALOG_DIFF)).toEqual({}) | |
| 372 | + | |
| 373 | + expect( | |
| 374 | + world.opened[0], | |
| 375 | + "the person's /diff opens the dialog, not dismisses one", | |
| 376 | + ).toMatchObject({ id: 'diff', focus: true }) | |
| 377 | + }) | |
| 378 | + | |
| 379 | + test('a surface that does not say whether it docks a pane opens nothing at the first edit', async ($, on) => { | |
| 380 | + const world = Fixtures.inRepository(on) | |
| 381 | + | |
| 382 | + on('tool.call', () => ({ result: 'edited' })) | |
| 383 | + | |
| 384 | + await $.session.start(Fixtures.SESSION) | |
| 385 | + await $.ui.render(Fixtures.UNSAID_HINT) | |
| 386 | + | |
| 387 | + await $.tool.call({ | |
| 388 | + tool: 'Edit', | |
| 389 | + file_path: '/work/app.ts', | |
| 390 | + old_string: '1', | |
| 391 | + new_string: '2', | |
| 392 | + }) | |
| 393 | + | |
| 394 | + await world.clock.advance(Fixtures.SETTLE_MS) | |
| 395 | + | |
| 396 | + expect(world.opened, 'unknown is not a dock').toEqual([]) | |
| 397 | + | |
| 398 | + await $.ui.render(Fixtures.HINT) | |
| 399 | + | |
| 400 | + await $.tool.call({ | |
| 401 | + tool: 'Edit', | |
| 402 | + file_path: '/work/app.ts', | |
| 403 | + old_string: '2', | |
| 404 | + new_string: '3', | |
| 405 | + }) | |
| 406 | + | |
| 407 | + await world.clock.advance(Fixtures.SETTLE_MS) | |
| 408 | + | |
| 409 | + expect( | |
| 410 | + world.opened.map(pane => pane.id), | |
| 411 | + 'once a drawing says the layout docks, the next edit opens it', | |
| 412 | + ).toEqual(['diff']) | |
| 413 | + }) | |
| 414 | + | |
| 351 | 415 | test('an edit that failed or was refused opens nothing', async ($, on) => { |
| 352 | 416 | const world = Fixtures.inRepository(on) |
| 353 | 417 |
Large file, diff not shown.