feat(client): localize composer hints and rework input command interaction

Unify the /plan claimed hint with the plan placeholder through a locale
namespace, localize slash menu group titles, replace the PermissionSelect
native select with the Menu primitive, add a goal pause verb chain, clamp
anchored popups to the viewport with scroll-into-view and outside-dismiss,
and fix onPasteUpgrade insertedRange to account for the chip trailing gap.
This commit is contained in:
Yif
2026-07-29 19:46:48 +08:00
parent c451b63016
commit 45c9205caf
46 changed files with 623 additions and 225 deletions

View File

@@ -91,7 +91,7 @@
.actions {
display: flex;
align-items: center;
gap: 2px;
gap: 8px;
flex: none;
}

View File

@@ -11,7 +11,7 @@
import { useCallback, useEffect, useState } from 'react'
import type { GoalSnapshot } from '@deepseek-ai/dsh-goal/client'
import {
IconCheckOutline16, IconCloseOutline16, IconEditOutline16, IconPlayOutline16, IconSparkle16, IconTrashOutline16,
IconCheckOutline16, IconCloseOutline16, IconEditOutline16, IconPauseOutline16, IconPlayOutline16, IconSparkle16, IconTrashOutline16,
} from '@deepseek-ai/dsh-client-ui-primitives'
import type { GoalActionResult, GoalBarActions } from './slots.ts'
import css from './GoalBar.module.css'
@@ -28,7 +28,7 @@ const PHASE_LABELS = {
blocked: 'Blocked Goal',
} as const
export function GoalBar({ goal, onEdit, onResume, onClear }: GoalBarProps) {
export function GoalBar({ goal, onEdit, onPause, onResume, onClear }: GoalBarProps) {
const [editing, setEditing] = useState(false)
const [draft, setDraft] = useState('')
const [pending, setPending] = useState(false)
@@ -120,6 +120,11 @@ export function GoalBar({ goal, onEdit, onResume, onClear }: GoalBarProps) {
<span className={css.objective}>{goal.objective}</span>
{actionError !== null && <span className={css.error} role="alert">{actionError}</span>}
<div className={css.actions}>
{goal.phase === 'active' && (
<button type="button" className={css.iconBtn} disabled={pending} onClick={() => { void runAction(onPause) }} title="Pause goal" aria-label="Pause goal">
<IconPauseOutline16 />
</button>
)}
{goal.phase === 'paused' && (
<button type="button" className={css.iconBtn} disabled={pending} onClick={() => { void runAction(onResume) }} title="Resume goal" aria-label="Resume goal">
<IconPlayOutline16 />
@@ -148,12 +153,13 @@ export function GoalBar({ goal, onEdit, onResume, onClear }: GoalBarProps) {
export type GoalDockProps = import('@deepseek-ai/dsh-client-ui-slots').PropsRuntime<'conversation.input.dock'> & GoalBarActions
/** Dock adapter: reads the host-computed 'goal' projection (whole value; absent or null renders nothing). */
export function GoalDock({ useProjection, onEdit, onResume, onClear }: GoalDockProps) {
export function GoalDock({ useProjection, onEdit, onPause, onResume, onClear }: GoalDockProps) {
const projection = useProjection('goal')
return (
<GoalBar
goal={projection === undefined ? undefined : projection === null ? null : projection.goal}
onEdit={onEdit}
onPause={onPause}
onResume={onResume}
onClear={onClear}
/>

View File

@@ -66,6 +66,11 @@ export function apply(ctx: ClientContext): void {
if (ref === undefined) return noCurrentGoal
return settle((await goals.edit({ sessionId, ref, objective })).result)
},
onPause: async () => {
const ref = refOf(sessionId)
if (ref === undefined) return noCurrentGoal
return settle((await goals.pause({ sessionId, ref })).result)
},
onResume: async () => {
const ref = refOf(sessionId)
if (ref === undefined) return noCurrentGoal

View File

@@ -19,6 +19,8 @@ export interface GoalBarActions {
* @param objective - replacement objective text.
*/
onEdit: (objective: string) => Promise<GoalActionResult>
/** Pause an active goal. */
onPause: () => Promise<GoalActionResult>
/** Resume a paused goal. */
onResume: () => Promise<GoalActionResult>
/** Clear the current goal (tombstone). */