fix(gui): address goal UI review feedback

This commit is contained in:
_Kerman
2026-07-22 22:18:13 +08:00
parent feeea91bf8
commit beec67f187
31 changed files with 325 additions and 220 deletions

View File

@@ -60,6 +60,8 @@ export const goalEditRequestSchema = z.object({
ref: goalRefSchema,
objective: z.string().min(1).optional(),
maxGoalRounds: z.number().int().positive().optional(),
}).refine(value => value.objective !== undefined || value.maxGoalRounds !== undefined, {
message: 'goal.edit requires objective or maxGoalRounds',
}) as unknown as z.ZodType<Wire<RequestPayload<'goal.edit'>>>
/** goal.edit response value. */

View File

@@ -4,6 +4,7 @@
*/
import type { Branded } from '@deepseek-ai/dsh-brand'
import type { SessionId } from '@deepseek-ai/dsh-session/types'
import type { RpcRequest, RpcResponse } from './rpc.ts'
/** Identifies one goal across its durable revisions. */
@@ -60,29 +61,29 @@ export interface EditGoalRequest {
/** Goal-domain unary methods. */
export interface GoalsApi {
/** Read the current goal for one session. Returns null when no goal is current. */
get(request: RpcRequest<{ sessionId: string }>): Promise<RpcResponse<{ goal: GoalView | null }>>
get(request: RpcRequest<{ sessionId: SessionId }>): Promise<RpcResponse<{ goal: GoalView | null }>>
/** Create and arm a goal. */
create(request: RpcRequest<{ sessionId: string; objective: string; maxGoalRounds?: number }>):
create(request: RpcRequest<{ sessionId: SessionId; objective: string; maxGoalRounds?: number }>):
Promise<RpcResponse<{ goal: GoalView }>>
/** Edit objective and/or round cap without changing phase. */
edit(request: RpcRequest<{ sessionId: string; ref: GoalRef; objective?: string; maxGoalRounds?: number }>):
edit(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef; objective?: string; maxGoalRounds?: number }>):
Promise<RpcResponse<{ goal: GoalView }>>
/** Pause an active goal and disarm automatic continuation. */
pause(request: RpcRequest<{ sessionId: string; ref: GoalRef }>):
pause(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
Promise<RpcResponse<{ goal: GoalView }>>
/** Resume and arm a stopped goal. */
resume(request: RpcRequest<{ sessionId: string; ref: GoalRef }>):
resume(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
Promise<RpcResponse<{ goal: GoalView }>>
/** Mark a current non-complete goal complete and disarm it. */
complete(request: RpcRequest<{ sessionId: string; ref: GoalRef }>):
complete(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
Promise<RpcResponse<{ goal: GoalView }>>
/** Clear the current goal while retaining a durable tombstone and history. */
clear(request: RpcRequest<{ sessionId: string; ref: GoalRef }>):
clear(request: RpcRequest<{ sessionId: SessionId; ref: GoalRef }>):
Promise<RpcResponse<{ cleared: true }>>
}

View File

@@ -425,6 +425,13 @@ describe('goals unary surface', () => {
const response = await client(scriptedApi()).goals.create({ sessionId: sid('s1'), objective: '' })
expect(response.result.ok).toBe(false)
if (!response.result.ok) expect(response.result.error.code).toBe('bad-request')
let editCalls = 0
const api = scriptedApi({ goals: { edit: (r) => { editCalls++; return ok(r, { goal: view }) } } })
const emptyEdit = await client(api).goals.edit({ sessionId: sid('s1'), ref })
expect(emptyEdit.result.ok).toBe(false)
if (!emptyEdit.result.ok) expect(emptyEdit.result.error.code).toBe('bad-request')
expect(editCalls).toBe(0)
})
})

View File

@@ -15,6 +15,7 @@ import { hostDescribeRequestSchema, hostDescribeValueSchema } from '../src/api/h
import { hostFrameSchema, muxFrameSchema, askUserQuestionItemSchema } from '../src/api/events.schema.ts'
import { approvalRequestIdSchema, approvalResponsePayloadSchema } from '../src/api/approvals.schema.ts'
import { askUserQuestionAnswerSchema, questionResponsePayloadSchema } from '../src/api/questions.schema.ts'
import { goalEditRequestSchema } from '../src/api/goals.schema.ts'
describe('RpcId', () => {
it('brands a raw string at zero runtime cost', () => {
@@ -126,6 +127,15 @@ describe('host domain schemas', () => {
})
})
describe('goals domain schemas', () => {
it('requires at least one replacement field for goal.edit', () => {
const ref = { id: 'g1', revision: 1 }
expect(goalEditRequestSchema.parse({ sessionId: 's1', ref, objective: 'updated' }).objective).toBe('updated')
expect(goalEditRequestSchema.parse({ sessionId: 's1', ref, maxGoalRounds: 3 }).maxGoalRounds).toBe(3)
expect(() => goalEditRequestSchema.parse({ sessionId: 's1', ref })).toThrow()
})
})
describe('events frame schemas', () => {
it('accepts every mux frame branch', () => {
const frames = [