spacehub

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: blank lines between statement kinds and around multi-line statementsBrowse files
poteat authored on Sep 12, 2026 parent ee2315b · commit 748e0c4a4488
48 changed files+179 -0compared with ee2315b
Mmods/diff/hooks/classify/is-generated-file/is-generated-file.ts
Mmods/diff/hooks/git/git-backend-of.ts
Mmods/diff/hooks/git/parse/parse-file-diff.ts
Mmods/diff/hooks/git/parse/parse-numstat.ts
Mmods/diff/hooks/git/probes/branch-base-of/branch-base-of.ts
Mmods/diff/hooks/git/probes/current-branch-of/current-branch-of.ts
Mmods/diff/hooks/git/probes/datings-of/datings-of.ts
Mmods/diff/hooks/git/probes/default-branch-of/default-branch-of.ts
Mmods/diff/hooks/git/probes/head-key-of/head-key-of.ts
Mmods/diff/hooks/git/probes/kind-walk-of/kind-walk-of.ts
Mmods/diff/hooks/git/probes/missing-base-of/missing-base-of.ts
Mmods/diff/hooks/git/probes/overlay-unstaged.ts
Mmods/diff/hooks/git/probes/repository-of/repository-of.ts
Mmods/diff/hooks/git/probes/stats-of.ts
Mmods/diff/hooks/git/probes/untracked-rows-of/untracked-rows-of.ts
Mmods/diff/hooks/git/tiers/unborn-tier/unborn-tier.ts
Mmods/diff/hooks/git/tiers/working-tree-tier.ts
Mmods/diff/hooks/pane-toggle/pane-toggle-of.ts
Mmods/diff/hooks/register.ts
Mmods/diff/hooks/todos/todo-progress-of.ts
Mmods/diff/hooks/turns/turn-of/file-edit-of/hunks-of/hunks-of.ts
Mmods/diff/hooks/turns/turn-of/turn-of.ts
Mmods/diff/hooks/views/current-pane/current-pane.tsx
Mmods/diff/hooks/views/current-pane/file-row-of/file-row-of.ts
Mmods/diff/hooks/views/current-pane/message-pane-of/message-pane-of.tsx
Mmods/diff/hooks/views/detail/code-blocks-of/code-blocks-of.ts
Mmods/diff/hooks/views/detail/detail-view.tsx
Mmods/diff/hooks/views/detail/leading-hunk-of/leading-hunk-of.ts
Mmods/diff/hooks/views/detail/placeholder-of/placeholder-of.ts
Mmods/diff/hooks/views/detail/sub-hunks-of/first-line-cut/first-line-cut.ts
Mmods/diff/hooks/views/detail/sub-hunks-of/sub-hunks-of.ts
Mmods/diff/hooks/views/layout/cell-width/cell-width.ts
Mmods/diff/hooks/views/sections/controls-view.tsx
Mmods/diff/hooks/views/sections/file-row/file-row.tsx
Mmods/diff/hooks/views/sections/todo-bar.tsx
Mmods/diff/hooks/views/turn-pane/turn-pane.tsx
Mmods/diff/tests/fixtures/in-repository.ts
Mmods/diff/tests/fixtures/text-of.ts
Mmods/diff/tests/git.test.ts
Mmods/diff/tests/register.test.ts
Mmods/sec-default/hooks/policy/managed-tools-restored/is-org-tool/org-server-names/org-server-names.ts
Mmods/sec-default/tests/fixtures/tools-registered.ts
Mmods/sec-default/tests/register.test.ts
Mmods/telemetry/hooks/entries/checked-value/checked-value.ts
Mmods/telemetry/hooks/is-analytics-off/is-analytics-off.ts
Mmods/telemetry/hooks/telemetry-of/telemetry-of.ts
Mmods/telemetry/tests/fixtures/first-party-session.ts
Mmods/telemetry/tests/register.test.ts
@@ -15,6 +15,7 @@
1515 export function isGeneratedFile(path: string) {
1616 const name = baseNameOf(path).toLowerCase()
1717 const rooted = rootedPathOf(path)
18+
1819 const hasGeneratedSuffix = GENERATED_SUFFIXES.some(suffix =>
1920 name.endsWith(suffix),
2021 )
@@ -46,6 +46,7 @@
4646 }
4747
4848 const run = runOf(repository)
49+
4950 const depsOf = (): Types.GitDeps => ({
5051 run,
5152 repository,
@@ -22,6 +22,7 @@
2222 }
2323
2424 const rows = stdout.split('\n')
25+
2526 const headerRows = rows.flatMap((row, at) =>
2627 HUNK_HEADER.test(row) ? [at] : [],
2728 )
@@ -26,11 +26,13 @@
2626 const [addedField, removedField, ...pathFields] = (records[at] ?? '').split(
2727 '\t',
2828 )
29+
2930 const inline = pathFields.join('\t')
3031 const isRename = pathFields.length === 1 && inline === ''
3132 const renamedFrom = isRename ? (records[at + 1] ?? null) : null
3233 const path = isRename ? (records[at + 2] ?? '') : inline
3334 at += isRename ? RENAME_RECORDS : 1
35+
3436 const isRow =
3537 addedField !== undefined && removedField !== undefined && path !== ''
3638
@@ -41,6 +43,7 @@
4143 const isBinary = addedField === '-' || removedField === '-'
4244 const added = isBinary ? 0 : Number(addedField) || 0
4345 const removed = isBinary ? 0 : Number(removedField) || 0
46+
4447 stats.filesCount += 1
4548 stats.linesAdded += added
4649 stats.linesRemoved += removed
@@ -23,6 +23,7 @@
2323 currentBranchOf(run),
2424 defaultBranchOf(run),
2525 ])
26+
2627 const isUnrelated = branch === 'HEAD' || defaultBranch.startsWith('-')
2728
2829 if (isUnrelated) {
@@ -37,6 +38,7 @@
3738 run,
3839 defaultBranch,
3940 )
41+
4042 const [first, second] = candidates
4143
4244 if (first === undefined) {
@@ -57,6 +59,7 @@
5759 second,
5860 ])
5961 ).exitCode === 0
62+
6063 const mergeBase = isLocalBaseNewer ? second : first
6164 const head = await run([Argv.NO_OPTIONAL_LOCKS, 'rev-parse', 'HEAD'])
6265
@@ -14,6 +14,7 @@
1414 '--abbrev-ref',
1515 'HEAD',
1616 ])
17+
1718 const name = stdout.trim()
1819
1920 return exitCode === 0 && name !== '' ? name : 'HEAD'
@@ -19,6 +19,7 @@
1919 paths: readonly string[],
2020 ): Promise<ReadonlyMap<string, Types.Dating>> {
2121 const { sessionStartMs } = context.deps
22+
2223 const stamps: readonly Types.Stamp[] = await Promise.all(
2324 paths.map(path => stampIfFile(context.stamps, path)),
2425 )
@@ -22,12 +22,14 @@
2222 `refs/remotes/origin/${branch}`,
2323 ])
2424 ).exitCode === 0
25+
2526 const symref = await run([
2627 Argv.NO_OPTIONAL_LOCKS,
2728 'symbolic-ref',
2829 '--short',
2930 'refs/remotes/origin/HEAD',
3031 ])
32+
3133 const named =
3234 symref.exitCode === 0 ? symref.stdout.trim().replace(/^origin\//, '') : ''
3335
@@ -26,7 +26,9 @@
2626 repository.gitDir,
2727 Limits.MAX_LISTED_DIRECTORIES,
2828 )
29+
2930 const isShared = repository.commonDir === repository.gitDir
31+
3032 const common = isShared
3133 ? own
3234 : stampProbeOf(deps, repository.commonDir, Limits.MAX_LISTED_DIRECTORIES)
@@ -33,6 +33,7 @@
3333
3434 const listed =
3535 held ?? entryKindsOf(directory === '' ? root : `${root}/${directory}`)
36+
3637 listings.set(directory, listed)
3738
3839 return listed
@@ -27,6 +27,7 @@
2727 '--quiet',
2828 ref,
2929 ])
30+
3031 const isPresentOrUnknown = exitCode === 0 || exitCode === -1
3132
3233 if (isPresentOrUnknown) {
@@ -17,6 +17,7 @@
1717 unstaged: Types.NumstatResult,
1818 ): Types.NumstatResult {
1919 const edits = new Map(unstaged.files.map(file => [file.path, file]))
20+
2021 const files = staged.files.map(file => {
2122 const edit = edits.get(file.path)
2223
@@ -25,16 +26,19 @@
2526 }
2627
2728 const isBinary = file.isBinary || edit.isBinary
29+
2830 const added = isBinary
2931 ? 0
3032 : Math.max(0, file.added + edit.added - edit.removed)
3133
3234 return { ...file, added, removed: 0, isBinary }
3335 })
36+
3437 const linesAdded =
3538 staged.stats.linesAdded +
3639 files.reduce((sum, file) => sum + file.added, 0) -
3740 staged.files.reduce((sum, file) => sum + file.added, 0)
41+
3842 const stats = { ...staged.stats, linesAdded }
3943
4044 return { stats, files }
@@ -25,8 +25,10 @@
2525 '--git-dir',
2626 '--git-common-dir',
2727 ])
28+
2829 const lines = stdout.split('\n').filter(line => line !== '')
2930 const [toplevel = '', gitDir = '', commonDir = ''] = lines
31+
3032 const isResolved =
3133 exitCode === 0 &&
3234 lines.length === REPOSITORY_LINES &&
@@ -21,8 +21,10 @@
2121 const run = context.run
2222
2323 const shortstat = await run([...Argv.DIFF_LEADING_ARGS, base, '--shortstat'])
24+
2425 const totals =
2526 shortstat.exitCode === 0 ? Parse.parseShortstat(shortstat.stdout) : null
27+
2628 const isPastDetails =
2729 totals !== null && totals.filesCount > Limits.MAX_FILES_FOR_DETAILS
2830
@@ -36,6 +38,7 @@
3638 '--numstat',
3739 '-z',
3840 ])
41+
3942 const isRead = Parse.isWholeAnswer(numstat)
4043
4144 return isRead ? Parse.parseNumstat(numstat.stdout, Limits.MAX_FILES) : null
@@ -23,11 +23,14 @@
2323 ): Promise<readonly Types.FileStat[]> {
2424 const probedPaths = paths.slice(0, Limits.MAX_UNTRACKED_PROBES)
2525 const datings = await datingsOf(context, probedPaths)
26+
2627 const probed = probedPaths.map(path => ({
2728 path,
2829 isPreSession: datings.get(path) !== 'session',
2930 }))
31+
3032 const isWithPreSession = place.scope === 'with-pre-session'
33+
3134 const earlier = isWithPreSession
3235 ? [
3336 ...probed.filter(file => file.isPreSession),
@@ -36,6 +39,7 @@
3639 .map(path => ({ path, isPreSession: true })),
3740 ]
3841 : []
42+
3943 const ordered = [...probed.filter(file => !file.isPreSession), ...earlier]
4044
4145 return ordered.slice(0, place.slots).map(file => ({
@@ -31,6 +31,7 @@
3131 }
3232
3333 const isOverlaid = staged.files.length > 0
34+
3435 const unstaged = isOverlaid
3536 ? await Probes.unstagedNumstat(run)
3637 : EMPTY_NUMSTAT
@@ -26,9 +26,11 @@
2626 }
2727
2828 const isSession = context.mode === 'session'
29+
2930 const files = isSession
3031 ? await Probes.tagPreSession(context, workingTree.files)
3132 : workingTree.files
33+
3234 const data = diffDataOf(
3335 context,
3436 await withUntracked(
@@ -16,6 +16,7 @@
1616 pane: PaneBelief,
1717 ): 'open' | 'close' | 'too-narrow' {
1818 const isClosing = pane.isBelievedOpen && pane.wasDrawnWhenProbed
19+
1920 const isTooNarrow =
2021 pane.columns !== null && pane.columns < Limits.OPEN_MIN_COLUMNS
2122
@@ -59,6 +59,7 @@
5959 sessionStartMsOf: () => sessionStartMs,
6060 onBranchBase: base => {
6161 const isError = base.kind === 'error'
62+
6263 const outcome: Record.MarkOutcome = isError
6364 ? { kind: 'sad', reason: base.reason }
6465 : {
@@ -70,6 +71,7 @@
7071
7172 if (!loggedBaseKinds.has(outcome.kind)) {
7273 loggedBaseKinds.add(outcome.kind)
74+
7375 Record.recorderOf(currentOf(engine)).mark(
7476 Record.FEATURES.baseResolve,
7577 outcome,
@@ -93,6 +95,7 @@
9395 async function probeBackend(engine: Host): Promise<boolean> {
9496 const asked = { isAnswered: true }
9597 const probeHost = backendHostOf(engine)
98+
9699 const probed = await Backend.backendOf(
97100 {
98101 ...probeHost,
@@ -100,11 +103,13 @@
100103 probeHost.run(argv, init).catch((error: unknown) => {
101104 asked.isAnswered &&=
102105 argv[0] !== 'git' || !/\baborted\b/.test(messageOf(error))
106+
103107 throw error
104108 }),
105109 },
106110 Backend.INSTALLED_BACKEND_PROBES,
107111 )
112+
108113 backend ??= probed
109114
110115 if (!probed || backend !== probed) {
@@ -116,7 +121,9 @@
116121 .storeGet(Names.baseStoreKeyOf(probed.repository.toplevel))
117122 .catch(() => undefined),
118123 )
124+
119125 const mode = stored && probed.baseModes.includes(stored) ? stored : null
126+
120127 model = {
121128 ...model,
122129 words: probed.words,
@@ -173,6 +180,7 @@
173180 bodyKey = key
174181 model = { ...model, body: null, bodyState: 'loading' }
175182 redraw(engine)
183+
176184 const body = await backend.fetchFileHunks(data, selected)
177185
178186 if (bodyKey !== key) {
@@ -195,6 +203,7 @@
195203 timers.get('poll')?.cancel()
196204 polled.toplevel = pinned.repository.toplevel
197205 polled.headKey = ''
206+
198207 timers.set(
199208 'poll',
200209 engine.every(Limits.HEAD_POLL_MS, () => {
@@ -204,6 +213,7 @@
204213
205214 void readHeadKey().then(key => {
206215 const hasMoved = polled.headKey !== '' && key !== polled.headKey
216+
207217 polled.headKey = key
208218
209219 if (hasMoved) {
@@ -222,8 +232,10 @@
222232 }
223233
224234 isRefreshing = true
235+
225236 const record = Record.recorderOf(engine)
226237 const pinned = backend
238+
227239 const fetched = (): Promise<Git.FetchOutcome> =>
228240 pinned
229241 ? pinned.fetchDiff(model.requestedMode)
@@ -231,10 +243,12 @@
231243
232244 try {
233245 model = { ...model, isLoading: model.data === null }
246+
234247 const [outcome, messages] = await Promise.all([
235248 fetched(),
236249 engine.messages().catch((): SessionMessage[] => []),
237250 ])
251+
238252 model = PaneState.afterFetch(model, { outcome, messages })
239253
240254 switch (outcome.kind) {
@@ -245,6 +259,7 @@
245259 kind: 'sad',
246260 reason: 'git_diff_failed',
247261 })
262+
248263 break
249264 case 'data':
250265 generation += 1
@@ -286,6 +301,7 @@
286301
287302 function scheduleRefresh(engine: Host): void {
288303 timers.get('refresh')?.cancel()
304+
289305 timers.set(
290306 'refresh',
291307 engine.after(Limits.REFRESH_DEBOUNCE_MS, () => {
@@ -303,6 +319,7 @@
303319 const isManual = trigger === 'manual'
304320 await engine.openPane(isManual ? { ...pane, ...Names.FOCUSED_PANE } : pane)
305321 isPaneOpen = true
322+
306323 const sessionId = await engine.sessionId().catch(() => null)
307324
308325 if (sessionId !== null && sessionId !== shownSessionId) {
@@ -341,11 +358,15 @@
341358 }
342359
343360 const preference = await engine.storeGet(Names.STORE_OPEN_KEY)
361+
344362 await pinBackend(engine)
363+
345364 const isKeptOpen = preference === true
365+
346366 const floor = isKeptOpen
347367 ? Limits.OPEN_MIN_COLUMNS
348368 : Limits.AUTO_OPEN_MIN_COLUMNS
369+
349370 const isEligible =
350371 preference !== false &&
351372 columns !== null &&
@@ -391,10 +412,12 @@
391412
392413 bodyKey = null
393414 model = { ...model, requestedMode: mode, body: null, bodyState: 'idle' }
415+
394416 Record.recorderOf(engine).mark(Record.FEATURES.baseSwitch, {
395417 kind: 'ok',
396418 props: { mode: { value: mode, of: model.baseModes } },
397419 })
420+
398421 const toplevel = model.data?.repository.toplevel
399422
400423 if (toplevel !== undefined) {
@@ -409,9 +432,11 @@
409432 chooseSource: value => {
410433 const index = Number(value)
411434 const isTurn = value !== 'current' && Number.isInteger(index)
435+
412436 const source: PaneState.Source = isTurn
413437 ? { kind: 'turn', index }
414438 : { kind: 'current' }
439+
415440 model = { ...model, source, selectedPath: null }
416441 void loadBody(engine)
417442 redraw(engine)
@@ -442,11 +467,14 @@
442467 model.body?.hunks ??
443468 [],
444469 )
470+
445471 model = { ...model, armedPath: path }
472+
446473 engine.status(
447474 `${Views.sanitizeName(path)} rides your next prompt (press ` +
448475 `asked ✓ to drop it)`,
449476 )
477+
450478 redraw(engine)
451479 }
452480
@@ -510,6 +538,7 @@
510538 }
511539
512540 const { Box, Text, Button, Select, Code } = await $.ui.resolve(e)
541+
513542 wasDrawnSinceProbe = true
514543 columns = e.viewport?.columns ?? columns
515544 model = { ...model, isFocused: e.props.isFocused }
@@ -624,6 +653,7 @@
624653 }
625654
626655 const context = e.context ?? []
656+
627657 const text = Ask.fittedAskTextOf(
628658 asked.text,
629659 Limits.PROMPT_CONTEXT_MAX_CHARS -
@@ -632,10 +662,12 @@
632662
633663 if (text === undefined) {
634664 disarm(host)
665+
635666 host.status(
636667 `${Views.sanitizeName(asked.path)}'s diff did not fit in the prompt ` +
637668 `and was dropped`,
638669 )
670+
639671 redraw(host)
640672
641673 return next(e)
@@ -21,6 +21,7 @@
2121 const latest = messages
2222 .flatMap(message => message.toolUses)
2323 .findLast(use => use.tool === Tools.TODO_TOOL)
24+
2425 const statuses = arrayOf(latest?.input.todos).map(todo =>
2526 isRecord(todo) ? todo.status : null,
2627 )
@@ -14,6 +14,7 @@
1414 .filter(isRecord)
1515 .flatMap(hunk => {
1616 const { oldStart, newStart, lines } = hunk
17+
1718 const isHunk =
1819 typeof oldStart === 'number' &&
1920 typeof newStart === 'number' &&
@@ -22,17 +22,21 @@
2222 ): Types.TurnDiff {
2323 const text = rows[0]?.text ?? ''
2424 const isShort = [...text].length <= PREVIEW_CHARS
25+
2526 const preview = isShort
2627 ? text
2728 : `${[...text].slice(0, PREVIEW_CHARS - 1).join('')}…`
29+
2830 const edits = rows
2931 .flatMap(row => (row.role === 'assistant' ? row.toolUses : []))
3032 .map(use => fileEditOf(use.result))
3133 .filter((edit): edit is Types.TurnFile => edit !== null)
34+
3235 const byPath = new Map<string, Types.TurnFile>()
3336
3437 for (const edit of edits) {
3538 const earlier = byPath.get(edit.path)
39+
3640 byPath.set(
3741 edit.path,
3842 earlier
@@ -50,56 +50,71 @@
5050 const noise = model.isNoiseShown ? 'shown' : 'hidden'
5151 const preSession = model.isPreSessionShown ? 'shown' : 'hidden'
5252 const partition = PaneState.partitionOf(data?.files ?? [], noise)
53+
5354 const totals = data
5455 ? PaneState.headerTotalsOf(data, partition)
5556 : PaneState.ZERO_TOTALS
57+
5658 const empty = PaneState.emptyStateOf(data, totals.filesCount, model.words)
5759 const baseLabel = PaneState.baseLabelOf(model, totals.filesCount)
60+
5861 const selected = PaneState.selectionOf(
5962 PaneState.listedOf(partition, preSession),
6063 model.selectedPath,
6164 )
65+
6266 const rowOf = (file: Git.FileStat): RenderElement =>
6367 Sections.fileRow(kit, fileRowOf(file, selected?.path ?? null), () =>
6468 kit.actions.selectFile(file.path),
6569 )
70+
6671 const noteOf = (text: string | null): RenderElement | null =>
6772 text === null ? null : Sections.dimNote(kit, Layout.sanitizeName(text))
73+
6874 const noiseFace = model.isNoiseShown ? 'hide' : 'show'
6975 const noiseCount = Layout.plural(partition.noiseCount, 'test')
7076 const hasNoise = partition.noiseCount > 0
77+
7178 const noiseToggle = hasNoise
7279 ? Sections.toggleRow(kit, 'noise', {
7380 label: `${noiseCount}/generated (${noiseFace})`,
7481 onPress: kit.actions.toggleNoise,
7582 })
7683 : null
84+
7785 const earlierFace = model.isPreSessionShown ? 'hide' : 'show'
7886 const earlierCount = Layout.plural(partition.preSession.length, 'file')
7987 const hasEarlier = partition.preSession.length > 0
88+
8089 const earlierToggle = hasEarlier
8190 ? Sections.toggleRow(kit, 'presession', {
8291 label: `+${earlierCount} edited before this session (${earlierFace})`,
8392 onPress: kit.actions.togglePreSession,
8493 })
8594 : null
95+
8696 const earlierFiles = model.isPreSessionShown ? partition.preSession : []
8797 const isEarlierListed = earlierFiles.length > 0
98+
8899 const earlierRows = isEarlierListed
89100 ? [<Box height={1} />, ...earlierFiles.map(rowOf)]
90101 : []
102+
91103 const isUntrackedNoted = data?.isUntrackedWithheld === true && !empty
92104 const listed = listBodyOf(kit, { data, partition, totals, empty })
93105 const notes = listed.filter(line => typeof line === 'string')
94106 const hasRows = notes.length < listed.length
95107 const message = empty ? [empty.headline, ...notes] : hasRows ? [] : notes
96108 const header = Sections.headerView(kit, empty ? null : totals)
109+
97110 const notShownNote = noteOf(
98111 totals.notShown > 0 ? `${totals.notShown} not shown` : null,
99112 )
113+
100114 const untrackedNote = noteOf(
101115 isUntrackedNoted ? Names.untrackedWithheldTextOf(model.words) : null,
102116 )
117+
103118 const detail = selected
104119 ? [
105120 Sections.divider(kit),
@@ -14,6 +14,7 @@
1414 selectedPath: string | null,
1515 ): Sections.FileRowModel {
1616 const isRenamed = file.renamedFrom !== null
17+
1718 const note = file.isUntracked
1819 ? 'untracked'
1920 : file.isBinary
@@ -24,20 +24,26 @@
2424 export function messagePaneOf(kit: Kit, pane: MessagePane): RenderElement {
2525 const { Box, Text } = kit.ui
2626 const { top, controls, earlier, rest } = pane
27+
2728 const lines = pane.message.flatMap(text =>
2829 Layout.wrappedLines(Layout.sanitizeName(text), kit.columns),
2930 )
31+
3032 const drawn = lines.map(line => <Text dimColor>{line}</Text>)
3133 const controlRows = controls === null ? [] : [controls]
3234 const earlierRows = earlier === null ? [] : [earlier]
35+
3336 const isControlsAboveEarlier =
3437 controlRows.length > 0 || earlierRows.length === 0
38+
3539 const aboveEarlier = isControlsAboveEarlier
3640 ? controlRows
3741 : [<Box height={1} />]
42+
3843 const footRows = earlier === null ? 0 : FOOT_ROWS
3944 const middleRows = kit.rows - top.length - 1 - footRows
4045 const above = Math.floor((middleRows - lines.length) / 2)
46+
4147 const isCentered =
4248 rest.length === 0 && middleRows - above - lines.length >= controlRows.length
4349
@@ -57,6 +63,7 @@
5763 }
5864
5965 const spacer = above > 0 ? [<Box height={above} />] : []
66+
6067 const foot = earlierRows.map(row => (
6168 <Box flexDirection="column" marginTop={1} paddingBottom={1}>
6269 {row}
@@ -36,15 +36,19 @@
3636 const last = blocks.at(-1)
3737 const source = hunkSourceOf(piece)
3838 const isHunkStart = at === 0
39+
3940 const isJoining =
4041 isHunkStart &&
4142 last !== undefined &&
4243 last.source.length + 1 + source.length <= MAX_CODE_CHARS
44+
4345 const hasDivider = isHunkStart && !isJoining && last !== undefined
4446 const overhead = isJoining ? 1 : hasDivider ? HUNK_DIVIDER.length : 0
47+
4548 const addedNodes = isJoining
4649 ? 0
4750 : 1 + (hasDivider ? HUNK_DIVIDER_NODES : 0)
51+
4852 const room = MAX_BODY_CHARS - chars - overhead
4953 const fitted = source.length <= room ? piece : leadingHunkOf(piece, room)
5054 const isOverNodes = nodes + addedNodes > MAX_BODY_NODES
@@ -63,6 +67,7 @@
6367 source: isJoining ? `${last.source}\n${text}` : text,
6468 hasDivider: isJoining ? last.hasDivider : hasDivider,
6569 })
70+
6671 chars += overhead + text.length
6772 nodes += addedNodes
6873
@@ -35,16 +35,21 @@
3535 const code = codeBlocksOf(placeholder ? [] : (detail.body?.hunks ?? []))
3636 const isTruncated = detail.body?.isTruncated === true || code.isTruncated
3737 const path = Layout.sanitizeName(detail.path).slice(-MAX_CODE_CHARS)
38+
3839 const nameOf = (name: string) =>
3940 Layout.truncateStart(Layout.sanitizeName(name), kit.columns)
41+
4042 const renamedFrom =
4143 detail.renamedFrom === null ? null : nameOf(detail.renamedFrom)
44+
4245 const asides = [
4346 renamedFrom === null ? null : `renamed from ${renamedFrom}`,
4447 detail.isUntracked ? 'untracked' : null,
4548 isTruncated ? 'truncated' : null,
4649 ].filter(word => word !== null)
50+
4751 const aside = asides.length === 0 ? '' : ` (${asides.join(', ')})`
52+
4853 const ask = placeholder
4954 ? []
5055 : [
@@ -52,6 +57,7 @@
5257 {detail.isArmed ? 'asked ✓' : 'ask'}
5358 </Button>,
5459 ]
60+
5561 const footer = isTruncated
5662 ? [
5763 <Text dimColor italic>
@@ -59,11 +65,13 @@
5965 </Text>,
6066 ]
6167 : []
68+
6269 const notes = (placeholder ?? []).map(line => (
6370 <Text dimColor italic wrap="wrap">
6471 {line}
6572 </Text>
6673 ))
74+
6775 const hunks = code.blocks.flatMap(block => [
6876 ...(block.hasDivider ? [<Text dimColor>{HUNK_DIVIDER}</Text>] : []),
6977 <Code source={block.source} format="diff" path={path} />,
@@ -18,6 +18,7 @@
1818 ...hunk,
1919 lines: hunk.lines.slice(0, taken + 1),
2020 })
21+
2122 bodyChars += 1 + line.length
2223
2324 if (header.length + bodyChars > maxChars) {
@@ -34,6 +34,7 @@
3434
3535 const { isLarge, hunks } = detail.body
3636 const isEmpty = hunks.length === 0
37+
3738 const note = isLarge
3839 ? 'Large file - diff exceeds 1 MB limit'
3940 : isEmpty
@@ -11,6 +11,7 @@
1111 */
1212 export function firstLineCut(hunk: Git.Hunk, maxChars: number) {
1313 const [first = ''] = hunk.lines
14+
1415 const kept = first.slice(
1516 0,
1617 Math.max(
@@ -25,13 +25,16 @@
2525 while (rest.lines.length > 0) {
2626 const leading = leadingHunkOf(rest, maxChars)
2727 const isLineCut = leading.lines.length === 0
28+
2829 const piece = isLineCut
2930 ? { ...rest, lines: [firstLineCut(rest, maxChars)] }
3031 : leading
32+
3133 const taken = rest.lines.slice(0, piece.lines.length)
3234
3335 hunks.push(piece)
3436 isTruncated ||= isLineCut
37+
3538 rest = {
3639 oldStart: rest.oldStart + countOf(taken, line => !line.startsWith('+')),
3740 newStart: rest.newStart + countOf(taken, line => !line.startsWith('-')),
@@ -17,8 +17,10 @@
1717
1818 for (const character of text) {
1919 const point = character.codePointAt(0) ?? 0
20+
2021 const holds = ([low, high]: readonly [number, number]) =>
2122 point >= low && point <= high
23+
2224 const isNarrow = point < FIRST_WIDE_POINT
2325 const isZero = !isNarrow && ZERO_WIDTH_RANGES.some(holds)
2426 const isWide = !isNarrow && !isZero && WIDE_RANGES.some(holds)
@@ -29,6 +29,7 @@
2929 const { Box, Select } = kit.ui
3030 const { source, turns } = model
3131 const hasTurns = turns.length > 0
32+
3233 const sourcePicker = hasTurns ? (
3334 <Select
3435 key="source"
@@ -44,10 +45,12 @@
4445 onSelect={value => kit.actions.chooseSource(value)}
4546 />
4647 ) : null
48+
4749 const isCurrent = source.kind === 'current'
4850 const fetchedSource = model.data?.source
4951 const isWorkingTree = fetchedSource?.kind === 'working-tree'
5052 const base = isWorkingTree ? fetchedSource.base : model.words.base
53+
5154 const basePicker = isCurrent ? (
5255 <Select
5356 key="base"
@@ -55,6 +58,7 @@
5558 options={model.baseModes.map(mode => {
5659 const isUncommitted = mode === 'uncommitted'
5760 const isSession = mode === 'session'
61+
5862 const label = isUncommitted
5963 ? `uncommitted (vs ${base})`
6064 : isSession
@@ -67,7 +71,9 @@
6771 onSelect={value => kit.actions.chooseBase(value)}
6872 />
6973 ) : null
74+
7075 const pickers = present([sourcePicker, basePicker])
76+
7177 const row = (
7278 <Box flexDirection="row" gap={2}>
7379 {pickers}
@@ -29,11 +29,13 @@
2929 const { Box, Text, Button } = kit.ui
3030 const room = Math.max(kit.columns - STAT_CELLS, STAT_CELLS)
3131 const mark = row.isSelected ? POINTER : ' '
32+
3233 const note = (
3334 <Text dimColor italic>
3435 {row.note ?? ''}
3536 </Text>
3637 )
38+
3739 const hasNote = row.note !== null
3840 const tail = hasNote ? note : diffStat(kit, row.added, row.removed)
3941
@@ -27,10 +27,12 @@
2727 }
2828
2929 const { Box, Text } = kit.ui
30+
3031 const cells = Math.max(
3132 1,
3233 Math.min(TODO_BAR_CELLS, kit.columns - TODO_BAR_RESERVE),
3334 )
35+
3436 const filled = Math.round((todos.done / todos.total) * cells)
3537
3638 return (
@@ -25,10 +25,12 @@
2525 turn: Turns.TurnDiff,
2626 ): RenderElement {
2727 const { Box } = kit.ui
28+
2829 const selected =
2930 turn.files.find(file => file.path === model.selectedPath) ??
3031 turn.files[0] ??
3132 null
33+
3234 const rows = turn.files.map(file =>
3335 Sections.fileRow(
3436 kit,
@@ -43,6 +45,7 @@
4345 () => kit.actions.selectFile(file.path),
4446 ),
4547 )
48+
4649 const detail = selected
4750 ? [
4851 Sections.divider(kit),
@@ -20,11 +20,13 @@
2020 const opened = keeping<Args<'ui.open'>>()
2121 const closed = keeping<Args<'ui.close'>>()
2222 const clock = startsSession(on)
23+
2324 on('process.run', ($, e) => {
2425 runs.push(e)
2526
2627 return { value: gitIn(e.argv) }
2728 })
29+
2830 on('ui.open', opened.hook)
2931 on('ui.close', closed.hook)
3032 on('ui.invalidate', () => ({ value: undefined }))
@@ -18,8 +18,10 @@
1818 }
1919
2020 const props: unknown = Reflect.get(tree, 'props')
21+
2122 const label: unknown =
2223 typeof props === 'object' && props ? Reflect.get(props, 'label') : undefined
24+
2325 const lead = typeof label === 'string' ? label : ''
2426
2527 return `${lead}${textOf(Reflect.get(tree, 'children') ?? [])}`
@@ -11,6 +11,7 @@
1111 await $.session.start(Fixtures.SESSION)
1212 await $.command.run(Fixtures.DIFF)
1313 await world.clock.advance(Fixtures.SETTLE_MS)
14+
1415 const [discovery, ...pinned] = world.runs
1516
1617 expect(discovery?.argv).toContain('--show-toplevel')
@@ -23,6 +24,7 @@
2324 '--git-dir=/work/.git',
2425 '--work-tree=/work',
2526 ])
27+
2628 expect(run.init).toMatchObject({ cwd: '/work', env: { LC_ALL: 'C' } })
2729 }
2830 })
@@ -9,6 +9,7 @@
99 test('/diff at boot joins the boot probe, then asks again', async ($, on) => {
1010 const probes: (readonly string[])[] = []
1111 const clock = Fixtures.startsSession(on)
12+
1213 on('process.run', async ($, e) => {
1314 probes.push(e.argv)
1415
@@ -22,8 +23,11 @@
2223 })
2324
2425 const booting = $.session.start(Fixtures.SESSION)
26+
2527 await clock.advance(0)
28+
2629 const ran = $.command.run(Fixtures.DIFF)
30+
2731 await clock.advance(0)
2832
2933 expect(probes, 'the boot probe, which /diff joined').toHaveLength(1)
@@ -34,13 +38,16 @@
3438 expect(await ran).toEqual({
3539 text: expect.stringContaining("isn't in a git repository"),
3640 })
41+
3742 expect(probes, 'then one more of its own').toHaveLength(2)
3843 })
3944
4045 test('outside a repository /diff says so, opens nothing', async ($, on) => {
4146 const opened: string[] = []
47+
4248 Fixtures.startsSession(on)
4349 on('process.run', () => ({ value: Fixtures.NOT_A_REPOSITORY }))
50+
4451 on('ui.open', ($, e, next) => {
4552 opened.push(e.id)
4653
@@ -48,6 +55,7 @@
4855 })
4956
5057 await $.session.start(Fixtures.SESSION)
58+
5159 const { text } = await $.command.run(Fixtures.DIFF)
5260
5361 expect(text).toContain("isn't in a git repository")
@@ -59,6 +67,7 @@
5967 on('process.run', () => ({ deny: Fixtures.GIT_HUNG }))
6068
6169 await $.session.start(Fixtures.SESSION)
70+
6271 const { text } = await $.command.run(Fixtures.DIFF)
6372
6473 expect(text).toContain("git didn't answer")
@@ -66,10 +75,12 @@
6675
6776 test('when the built-in holds /diff, the mod stands down', async ($, on) => {
6877 const logged: string[] = []
78+
6979 mock.clock(on)
7080 on('session.start', ($, e) => ({ cwd: e.cwd }))
7181 on('command.register', () => ({ deny: Fixtures.BUILTIN_HOLDS }))
7282 on('command.run', () => ({ text: 'the built-in /diff ran' }))
83+
7384 on('ui.log', ($, e) => {
7485 logged.push(e.text)
7586
@@ -81,16 +92,20 @@
8192 expect(await $.command.run(Fixtures.DIFF)).toEqual({
8293 text: 'the built-in /diff ran',
8394 })
95+
8496 expect(logged).toEqual([])
8597 })
8698
8799 test('a refusal the built-in did not cause is said aloud', async ($, on) => {
88100 const logged: string[] = []
101+
89102 mock.clock(on)
90103 on('session.start', ($, e) => ({ cwd: e.cwd }))
104+
91105 on('command.register', () => ({
92106 deny: '32 commands are registered already',
93107 }))
108+
94109 on('ui.log', ($, e) => {
95110 logged.push(e.text)
96111
@@ -114,6 +129,7 @@
114129 expect(world.opened.map(pane => pane.id)).toEqual(['diff'])
115130
116131 await world.clock.advance(Fixtures.SETTLE_MS)
132+
117133 const drawn = Fixtures.textOf(await $.ui.render(Fixtures.PANE))
118134
119135 expect(drawn).toContain('1 file changed')
@@ -141,16 +157,19 @@
141157
142158 test('a wide terminal opens the pane at the first edit', async ($, on) => {
143159 const world = Fixtures.inRepository(on)
160+
144161 on('tool.call', () => ({ result: 'edited' }))
145162
146163 await $.session.start(Fixtures.SESSION)
147164 await $.ui.render(Fixtures.HINT)
165+
148166 await $.tool.call({
149167 tool: 'Edit',
150168 file_path: '/work/app.ts',
151169 old_string: '1',
152170 new_string: '2',
153171 })
172+
154173 await world.clock.advance(Fixtures.SETTLE_MS)
155174
156175 expect(world.opened.map(pane => pane.id)).toEqual(['diff'])
@@ -158,6 +177,7 @@
158177
159178 test('/clear closes the pane it finds open', async ($, on) => {
160179 const world = Fixtures.inRepository(on)
180+
161181 on('command.run', { command: 'clear' }, () => ({}))
162182
163183 await $.session.start(Fixtures.SESSION)
@@ -19,6 +19,7 @@
1919 ...(Array.isArray(allowlist) ? allowlist : []).flatMap((entry: unknown) => {
2020 const isEntry =
2121 typeof entry === 'object' && entry !== null && 'serverName' in entry
22+
2223 const name = isEntry ? entry.serverName : undefined
2324 const isNamed = typeof name === 'string' && name !== ''
2425
@@ -11,7 +11,9 @@
1111 */
1212 export function toolsRegistered(on: On) {
1313 const registered: string[] = []
14+
1415 on('session.start', ($, e) => ({ cwd: e.cwd }))
16+
1517 on('tool.register', ($, e, next) => {
1618 registered.push(next.origin.plugin)
1719
@@ -15,6 +15,7 @@
1515 },
1616 async ($, on) => {
1717 on('settings.read', () => ({ value: Fixtures.ALLOWLIST }))
18+
1819 const registered = Fixtures.toolsRegistered(on)
1920
2021 await $.session.start(Fixtures.SESSION)
@@ -28,6 +29,7 @@
2829 { plugins: [Fixtures.registering('mine')] },
2930 async ($, on) => {
3031 on('settings.read', () => ({ value: Fixtures.NO_ALLOWLIST }))
32+
3133 const registered = Fixtures.toolsRegistered(on)
3234
3335 await $.session.start(Fixtures.SESSION)
@@ -41,6 +43,7 @@
4143 { plugins: [Fixtures.registering('mine')] },
4244 async ($, on) => {
4345 on('settings.read', () => ({ deny: 'managed settings unreadable' }))
46+
4447 const registered = Fixtures.toolsRegistered(on)
4548
4649 await $.session.start(Fixtures.SESSION)
@@ -51,6 +51,7 @@
5151
5252 const members = value.of
5353 const chosen = value.value
54+
5455 const isTokenList =
5556 members.length > 0 &&
5657 members.length <= CHOICES_LIMIT &&
@@ -18,6 +18,7 @@
1818 isEnvSet(environment.disableTelemetry) ||
1919 isEnvSet(environment.disableNonessentialTraffic) ||
2020 isEnvTruthy(environment.doNotTrack)
21+
2122 const isThirdParty = [
2223 environment.useBedrock,
2324 environment.useVertex,
@@ -26,6 +27,7 @@
2627 environment.useAnthropicGoogleCloud,
2728 environment.useMantle,
2829 ].some(isEnvTruthy)
30+
2931 const isCustomDeployment = isEnvSet(environment.customOauthUrl?.trim())
3032
3133 return isPrivate || isThirdParty || isCustomDeployment
@@ -32,6 +32,7 @@
3232 model: await deps.model(),
3333 userType: environment.userType === 'ant' ? 'ant' : 'external',
3434 })
35+
3536 const auth = await deps.authorize()
3637
3738 if (!auth) {
@@ -16,9 +16,11 @@
1616 authorization: SessionAuthorization = BEARER,
1717 ) {
1818 const posts: Args<'http.fetch'>[] = []
19+
1920 on('session.id', () => ({ value: 'the-session' }))
2021 on('session.model', () => ({ value: 'the-model' }))
2122 on('session.authorize', () => ({ value: authorization }))
23+
2224 on('http.fetch', ($, e) => {
2325 posts.push(e)
2426
@@ -10,14 +10,17 @@
1010 { plugins: [Fixtures.recording] },
1111 async ($, on) => {
1212 mock.env(on, { USER_TYPE: 'ant' })
13+
1314 const posts = Fixtures.firstPartySession(on)
1415
1516 expect(
1617 await $.command.run(Fixtures.record(Fixtures.surveyAnswer())),
1718 ).toEqual({ text: 'sent' })
19+
1820 expect(posts.map(post => post.init)).toMatchObject([
1921 { method: 'POST', auth: 'the-handle' },
2022 ])
23+
2124 expect(posts.map(Fixtures.batchOf)).toMatchObject([
2225 {
2326 events: [
@@ -40,6 +43,7 @@
4043 { plugins: [Fixtures.recording] },
4144 async ($, on) => {
4245 mock.env(on, {})
46+
4347 const posts = Fixtures.firstPartySession(on)
4448
4549 await $.command.run(
@@ -69,7 +73,9 @@
6973 { plugins: [Fixtures.recording] },
7074 async ($, on) => {
7175 mock.env(on, { DO_NOT_TRACK: '1' })
76+
7277 const posts = Fixtures.firstPartySession(on)
78+
7379 const { text } = await $.command.run(
7480 Fixtures.record(Fixtures.surveyAnswer()),
7581 )
@@ -83,6 +89,7 @@
8389 { plugins: [Fixtures.recording] },
8490 async ($, on) => {
8591 mock.env(on, { CLAUDE_CODE_USE_BEDROCK: '1' })
92+
8693 const posts = Fixtures.firstPartySession(on)
8794
8895 await $.command.run(Fixtures.record(Fixtures.surveyAnswer()))
@@ -96,7 +103,9 @@
96103 { plugins: [Fixtures.recording] },
97104 async ($, on) => {
98105 mock.env(on, {})
106+
99107 const posts = Fixtures.firstPartySession(on, null)
108+
100109 const { text } = await $.command.run(
101110 Fixtures.record(Fixtures.surveyAnswer()),
102111 )
@@ -105,6 +114,7 @@
105114 '$.telemetry.log: this session has no first-party credential to ' +
106115 'authorize',
107116 )
117+
108118 expect(posts).toEqual([])
109119 },
110120 )
@@ -114,7 +124,9 @@
114124 { plugins: [Fixtures.recording] },
115125 async ($, on) => {
116126 mock.env(on, {})
127+
117128 const posts = Fixtures.firstPartySession(on)
129+
118130 const { text } = await $.command.run({
119131 ...Fixtures.record(Fixtures.surveyAnswer()),
120132 args: '{"event":"survey_answered","props":{"note":"hello world"}}',