The forge panel opens a changes view in the right sidebar for the checkout's local changes and for the diff of each change request. - ForgePanelController reads local diffs in the branch, uncommitted, unstaged, and staged scopes through Git in the Session's read-only sandbox, change request diffs through the forge provider, and the text of a changed file to expand unchanged lines. Change request patches stay cached by head commit and base branch within maxCachedDiffBytes. A local diff over maxDiffBytes fails as forgePanel/diff-too-large, and a provider read over its output limit fails as too-large, which the view explains with a link to the code host; a gh timeout fails as unavailable. - forge adds the diff, fileText, and fileUrl provider methods and the too-large failure code, and forge-github implements them with gh. - The panel shows a local changes card that opens the view and asks the Agent for a change request. The view collapses files past a line budget, draws lines side by side or in one column with optional wrapping, expands unchanged lines on request, and collapses or expands every file from its toolbar; file headers use a bare chevron. - The checkout row copies the worktree or project directory path, the refresh glyph turns while a refresh runs, change request cards keep their actions while collapsed, and the header entry uses the shared compact button. - client-ui-forge classifies changeRequestKey and FsError as peer-required Host imports, which keeps forge and dsh-fs as its peers.
49 lines
1.9 KiB
TypeScript
49 lines
1.9 KiB
TypeScript
/** Change request patches kept for later reads: versions, recency, and the size bound. */
|
|
|
|
import { describe, expect, it } from 'vitest'
|
|
import { changeRequestKey } from '@deepseek-ai/dsh-experimental-forge'
|
|
import type { ChangeRequestKey } from '@deepseek-ai/dsh-experimental-forge'
|
|
import { PatchCache } from '../src/patch-cache.ts'
|
|
|
|
function key(number: number): ChangeRequestKey {
|
|
return changeRequestKey({ repository: { forge: 'fake', host: 'fake.test', owner: 'octo', name: 'demo' }, number })
|
|
}
|
|
|
|
describe('PatchCache', () => {
|
|
it('answers only for the version a patch was read at, keeping one version per change request', () => {
|
|
const cache = new PatchCache(100)
|
|
expect(cache.get(key(1), 'v1')).toBeUndefined()
|
|
|
|
cache.set(key(1), 'v1', 'one')
|
|
expect(cache.get(key(1), 'v1')).toBe('one')
|
|
expect(cache.get(key(1), 'v2')).toBeUndefined()
|
|
cache.set(key(1), 'v2', 'two')
|
|
expect(cache.get(key(1), 'v1')).toBeUndefined()
|
|
expect(cache.get(key(1), 'v2')).toBe('two')
|
|
})
|
|
|
|
it('drops the least recently read patches once the kept patches exceed the bound in UTF-8 bytes', () => {
|
|
const cache = new PatchCache(10)
|
|
cache.set(key(1), 'v', 'aaaa')
|
|
cache.set(key(2), 'v', 'bbb')
|
|
expect(cache.get(key(1), 'v')).toBe('aaaa')
|
|
|
|
// Two three-byte characters bring the total to 13 bytes, so #2, read least recently, leaves.
|
|
cache.set(key(3), 'v', '变更')
|
|
expect(cache.get(key(2), 'v')).toBeUndefined()
|
|
expect(cache.get(key(1), 'v')).toBe('aaaa')
|
|
expect(cache.get(key(3), 'v')).toBe('变更')
|
|
})
|
|
|
|
it('keeps no patch over the bound, dropping the earlier version all the same', () => {
|
|
const cache = new PatchCache(4)
|
|
cache.set(key(1), 'v1', 'one')
|
|
cache.set(key(1), 'v2', 'large')
|
|
expect(cache.get(key(1), 'v1')).toBeUndefined()
|
|
expect(cache.get(key(1), 'v2')).toBeUndefined()
|
|
|
|
cache.set(key(1), 'v3', 'four')
|
|
expect(cache.get(key(1), 'v3')).toBe('four')
|
|
})
|
|
})
|