Merge remote-tracking branch 'origin/master' into worktree/attachment-alignment-2

This commit is contained in:
creatixchu
2026-08-12 13:02:39 +08:00
201 changed files with 3681 additions and 1720 deletions

View File

@@ -319,6 +319,50 @@ describe('workspace.create', () => {
})
})
describe('workspace.insertBefore', () => {
it('commits the complete order, streams one order frame, and maps unknown ids', async () => {
const { api, ctx, root } = await harness()
const first = expectOk(await api.workspace.create(request({ path: stageDir(root, 'first') }))).workspace
const second = expectOk(await api.workspace.create(request({ path: stageDir(root, 'second') }))).workspace
const third = expectOk(await api.workspace.create(request({ path: stageDir(root, 'third') }))).workspace
const abort = new AbortController()
const listWorkspaces = vi.spyOn(ctx.workspace, 'list')
const stream: AsyncIterator<RpcRequest<HostFrame>> =
api.events.host(request({}), abort.signal)[Symbol.asyncIterator]()
expect(listWorkspaces).toHaveBeenCalledTimes(1)
const changed = nextHostFrame(stream)
const reordered = expectOk(await api.workspace.insertBefore(request({
workspaceId: first.workspaceId,
beforeWorkspaceId: second.workspaceId,
})))
expect(reordered.workspaceIds).toEqual([third.workspaceId, first.workspaceId, second.workspaceId])
expect(await changed).toMatchObject({
payload: {
type: 'host/workspace-order-changed',
workspaceIds: [third.workspaceId, first.workspaceId, second.workspaceId],
},
})
expect(expectOk(await api.workspace.list(request({}))).items.map(item => item.workspaceId))
.toEqual(reordered.workspaceIds)
const missingSource = await api.workspace.insertBefore(request({
workspaceId: 'missing' as WorkspaceId,
}))
expect(missingSource.result).toMatchObject({
ok: false, error: { code: 'workspace-not-found', details: { workspaceId: 'missing' } },
})
const missingAnchor = await api.workspace.insertBefore(request({
workspaceId: first.workspaceId,
beforeWorkspaceId: 'missing-anchor' as WorkspaceId,
}))
expect(missingAnchor.result).toMatchObject({
ok: false, error: { code: 'workspace-not-found', details: { workspaceId: 'missing-anchor' } },
})
abort.abort()
})
})
describe('session creation and Workspace membership', () => {
it('attaches a preallocated idempotent session while cwd-only sessions stay ungrouped', async () => {
const { api, ctx, root } = await harness()

View File

@@ -85,6 +85,7 @@ function scriptedApi(overrides: {
create: r => ok(r, { workspace: { workspaceId: 'w1' as never, path: '/t', title: 't', sessionIds: [], createdAt: '0', updatedAt: '0' }, created: true }),
rename: r => ok(r, { workspace: { workspaceId: 'w1' as never, path: '/t', title: 't', sessionIds: [], createdAt: '0', updatedAt: '0' } }),
delete: r => ok(r, { deleted: true as const }),
insertBefore: r => ok(r, { workspaceIds: [r.payload.workspaceId] }),
insertSessionBefore: r => ok(r, { workspace: { workspaceId: 'w1' as never, path: '/t', title: 't', sessionIds: [], createdAt: '0', updatedAt: '0' } }),
archiveSession: r => ok(r, { archivedSessionIds: [r.payload.sessionId] }),
},
@@ -218,7 +219,7 @@ describe('unary round trip', () => {
expect(response.result).toEqual({ ok: true, value: { sessionId: 's-child' } })
})
it('routes workspace rename, delete, and insertSessionBefore through the wire', async () => {
it('routes workspace rename, delete, and ordering through the wire', async () => {
const api = scriptedApi()
const c = client(api)
const renamed = await c.workspace.rename({ workspaceId: 'w1' as never, title: 'next' })
@@ -227,6 +228,11 @@ describe('unary round trip', () => {
expect(blankTitle.result).toMatchObject({ ok: false, error: { code: 'bad-request' } })
const deleted = await c.workspace.delete({ workspaceId: 'w1' as never })
expect(deleted.result).toEqual({ ok: true, value: { deleted: true } })
const workspaceOrder = await c.workspace.insertBefore({
workspaceId: 'w1' as never,
beforeWorkspaceId: 'w2' as never,
})
expect(workspaceOrder.result).toEqual({ ok: true, value: { workspaceIds: ['w1'] } })
const anchored = await c.workspace.insertSessionBefore({ workspaceId: 'w1' as never, sessionId: sid('s1'), beforeSessionId: sid('s2') })
expect(anchored.result.ok).toBe(true)
const appended = await c.workspace.insertSessionBefore({ workspaceId: 'w1' as never, sessionId: sid('s1') })

View File

@@ -179,6 +179,9 @@ function fakeApi(overrides: Partial<{ muxFrames: MuxFrame[]; hostFrames: HostFra
async delete(request) {
return { rpcId: request.rpcId, result: { ok: true, value: { deleted: true as const } } }
},
async insertBefore(request) {
return { rpcId: request.rpcId, result: { ok: true, value: { workspaceIds: [request.payload.workspaceId] } } }
},
async insertSessionBefore(request) {
return {
rpcId: request.rpcId,

View File

@@ -23,6 +23,7 @@ import {
workspaceArchiveSessionRequestSchema, workspaceArchiveSessionValueSchema,
workspaceCreateRequestSchema, workspaceCreateValueSchema, workspaceIdSchema,
workspaceDeleteRequestSchema, workspaceDeleteValueSchema,
workspaceInsertBeforeRequestSchema, workspaceInsertBeforeValueSchema,
workspaceInsertSessionBeforeRequestSchema, workspaceInsertSessionBeforeValueSchema,
workspaceListRequestSchema, workspaceListValueSchema,
workspaceRenameRequestSchema, workspaceRenameValueSchema, workspaceViewSchema,
@@ -394,6 +395,17 @@ describe('workspace domain schemas', () => {
expect(workspaceDeleteValueSchema.parse({ deleted: true })).toEqual({ deleted: true })
expect(() => workspaceDeleteValueSchema.parse({ deleted: false })).toThrow()
})
it('insertBefore accepts an anchored or anchorless Workspace move and returns the complete order', () => {
expect(workspaceInsertBeforeRequestSchema.parse({
workspaceId: 'w1', beforeWorkspaceId: 'w2',
}).beforeWorkspaceId).toBe('w2')
expect(workspaceInsertBeforeRequestSchema.parse({ workspaceId: 'w1' }).beforeWorkspaceId)
.toBeUndefined()
expect(() => workspaceInsertBeforeRequestSchema.parse({ beforeWorkspaceId: 'w2' })).toThrow()
expect(workspaceInsertBeforeValueSchema.parse({ workspaceIds: ['w2', 'w1'] }).workspaceIds)
.toEqual(['w2', 'w1'])
})
})
describe('skills domain schemas', () => {