diff --git a/packages/client/ui-conversation/src/client/chat/ChatView.tsx b/packages/client/ui-conversation/src/client/chat/ChatView.tsx index 2aae37d99d..f61c6da6ef 100644 --- a/packages/client/ui-conversation/src/client/chat/ChatView.tsx +++ b/packages/client/ui-conversation/src/client/chat/ChatView.tsx @@ -86,7 +86,8 @@ const CallRow = memo(function CallRow({ renderSlot, callId, toolName, block, seq seq: number onOpenDetails: OpenDetails selected: boolean - /** `run_code` sub-dispatches in dispatch order (reference-stable per parent; running entries settle in place); undefined for ordinary calls. */ + /** `run_code` sub-dispatches in dispatch order (reference-stable per + * parent; running entries settle in place); undefined for ordinary calls. */ subCalls?: readonly CodeSubCall[] | undefined /** The store's selected callId, matched against sub-rows (undefined when no sub-row here is selected). */ selectedCallId?: string | undefined @@ -162,7 +163,10 @@ function StreamingTail({ useSession, onGrow }: { return } -/** The chat view slot entry: pure component over the composed props (tool rows render through the declared keyed hole's renderSlot share). */ +/** + * The chat view slot entry: pure component over the composed props (tool rows + * render through the declared keyed hole's renderSlot share). + */ export function ChatView({ useSession, useStore, renderSlot, openDetails, loadOlder }: ChatViewSlotProps) { const nodes = useSession(s => s.nodes) const runningCalls = useSession(s => s.runningCalls) diff --git a/packages/client/ui-conversation/src/client/chat/MessageItem.tsx b/packages/client/ui-conversation/src/client/chat/MessageItem.tsx index 4ecfdadf88..3c1f3cf85e 100644 --- a/packages/client/ui-conversation/src/client/chat/MessageItem.tsx +++ b/packages/client/ui-conversation/src/client/chat/MessageItem.tsx @@ -32,6 +32,9 @@ function contentText(content: readonly unknown[]): { text: string; rest: unknown /** Best-effort clipboard write; rejections stay swallowed (no success chrome). */ async function writeClipboard(text: string): Promise { + // lib.dom types clipboard non-optional, but insecure contexts omit it — + // that runtime gap is exactly what this guard detects. + /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */ if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text) @@ -40,6 +43,9 @@ async function writeClipboard(text: string): Promise { } return } + // execCommand('copy') is the only clipboard fallback where the async API + // is missing (insecure contexts); deprecated but deliberately retained. + /* eslint-disable @typescript-eslint/no-deprecated */ const exec = typeof document.execCommand === 'function' ? document.execCommand.bind(document) : undefined @@ -56,6 +62,7 @@ async function writeClipboard(text: string): Promise { } catch { // Clipboard unavailable; the button stays idle. } + /* eslint-enable @typescript-eslint/no-deprecated */ el.remove() } diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx b/packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx index 515bfe1f93..45d98c1693 100644 --- a/packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/ConversationSession.tsx @@ -41,8 +41,8 @@ export function ConversationSession({ if (inputState.draft === '' && storedDraft !== '') inputActions.setDraft(storedDraft) const unmirror = bindDraftMirror(actions.setDraft) return () => { unmirror() } - // Mount-only: later store writes come from the machine mirror. - // eslint-disable-next-line react-hooks/exhaustive-deps + // Mount-only (deps pinned to inputActions): later store writes come from + // the machine mirror, not this seed effect. }, [inputActions]) if (blank && composerPhase === 'blank') return null diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx index 98aad0bb00..f2313d798b 100644 --- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx +++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx @@ -31,7 +31,11 @@ export function InputBar({ variant, placeholder, accessory, overlay, leftItems, rightItems, onAdd, addLabel = 'Add attachment', }: InputBarProps) { const input = useInput(s => s) - const notice = useSyncExternalStore(keyboard.notices.subscribe, keyboard.notices.getSnapshot) + const noticeStore = keyboard.notices + const notice = useSyncExternalStore( + (fn: () => void) => noticeStore.subscribe(fn), + () => noticeStore.getSnapshot(), + ) const promptError = useSession(s => s.promptError) const running = useSession(s => s.running) const disabled = useSession(s => s.removed) @@ -75,6 +79,8 @@ export function InputBar({ // Shift+Enter is the native newline UNCONDITIONALLY — decided before the // IME guard so a composition-closing Shift+Enter still breaks the line. if (e.key === 'Enter' && e.shiftKey) return + // keyCode 229 is the legacy IME-composition signal engines emit without isComposing. + // eslint-disable-next-line @typescript-eslint/no-deprecated const composing = composingRef.current || e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229 if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { if (keyboard.arbitrate(e.key === 'ArrowUp' ? 'up' : 'down', composing) === 'consumed') e.preventDefault() @@ -92,7 +98,7 @@ export function InputBar({ // the browser stack cannot represent); never let the native stack run. e.preventDefault() if (machineBusy || locked) return - const redo = e.key === 'y' || (e.shiftKey && (e.key === 'z' || e.key === 'Z')) + const redo = e.key === 'y' || e.shiftKey if (redo) keyboard.redo() else keyboard.undo() return @@ -134,6 +140,8 @@ export function InputBar({ if (machineBusy) return // submitting is the read-only span; adjudicating holds the pending lock const next = e.target.value keyboard.setDraft(next) + // selectionStart is number|null in lib.dom; the eslint program narrows it. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition keyboard.track(next, e.target.selectionStart ?? next.length) } @@ -145,10 +153,13 @@ export function InputBar({ // too (one char = one step). Mouse selection of a chip is handled in the // backdrop click handler below. Undo/redo must NOT reach the browser: the // machine owns the transaction log. + // selectionStart/End are number|null in lib.dom; the eslint program narrows them. + /* eslint-disable @typescript-eslint/no-unnecessary-condition */ const selectionOf = (el: HTMLTextAreaElement) => ({ start: el.selectionStart ?? 0, end: el.selectionEnd ?? el.selectionStart ?? 0, }) + /* eslint-enable @typescript-eslint/no-unnecessary-condition */ const onCopyOrCut = (e: React.ClipboardEvent, cut: boolean): void => { const el = e.currentTarget diff --git a/packages/client/ui-conversation/tests/apply-inject.spec.tsx b/packages/client/ui-conversation/tests/apply-inject.spec.tsx index 558fdbb9c8..504d0ec8c6 100644 --- a/packages/client/ui-conversation/tests/apply-inject.spec.tsx +++ b/packages/client/ui-conversation/tests/apply-inject.spec.tsx @@ -36,6 +36,8 @@ const SCOPE_TAG: symbol = (() => { const spy = new Proxy(new Context(), { get(target, prop, receiver) { recorded.push(prop) + // Reflect.get is typed any; the probe only records property names. + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return Reflect.get(target, prop, receiver) }, }) @@ -80,7 +82,8 @@ async function bench() { } type TestProvider = { resolve(binding: { sessionId: SessionId; session: typeof sessionFake; ctx: Context }): { - hooks?: Record; props?: Record + hooks?: Record + props?: Record } } const providers: TestProvider[] = [] @@ -158,10 +161,12 @@ async function bench() { const inputSurface = (id: SessionId) => { const contribution = providers[0]!.resolve(sessionsFake.binding(id)) const state = contribution.hooks!['input'] as { - getSnapshot(): { draft: string }; subscribe(fn: () => void): () => void + getSnapshot: () => { draft: string } + subscribe: (fn: () => void) => () => void } const actions = contribution.props!['inputActions'] as { - setDraft(text: string): void; submit(mode?: 'queue' | 'steer'): void + setDraft: (text: string) => void + submit: (mode?: 'queue' | 'steer') => void } return { state, actions } } @@ -263,7 +268,7 @@ describe('conversation slot inject surface', () => { // no draft movement, plain re-open. const { state, actions } = b.inputSurface(ROOT) actions.setDraft('carry me') - resident.selectWorkspace('workspace-1' as never) + void resident.selectWorkspace('workspace-1' as never) await vi.waitFor(() => { expect(b.sessionsFake.open).toHaveBeenCalledTimes(2) }) expect(b.workspacesFake.connectWorkspace).toHaveBeenCalledWith('workspace-1') expect(state.getSnapshot().draft).toBe('carry me') @@ -271,7 +276,7 @@ describe('conversation slot inject surface', () => { // new session's machine receives the text, then navigation lands there. const OTHER = 'other-1' as SessionId b.workspacesFake.connectWorkspace.mockResolvedValueOnce(OTHER) - resident.selectWorkspace('workspace-2' as never) + void resident.selectWorkspace('workspace-2' as never) await vi.waitFor(() => { expect(b.sessionsFake.open).toHaveBeenCalledWith(OTHER) }) expect(state.getSnapshot().draft).toBe('') expect(b.inputSurface(OTHER).state.getSnapshot().draft).toBe('carry me') diff --git a/packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx b/packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx index 3edbde1c21..991aca36e0 100644 --- a/packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-stats-bash-sample.spec.tsx @@ -36,7 +36,7 @@ function makeSource(init?: Partial) { let snap: ConversationSnapshot = { ...snapshotBase(), ...init } const subs = new Set<() => void>() return { - set(next: Partial) { + set: (next: Partial) => { snap = { ...snap, ...next } for (const fn of [...subs]) fn() }, diff --git a/packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx b/packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx index 645684d699..8134ddb9d6 100644 --- a/packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-toolview-slot.spec.tsx @@ -108,6 +108,10 @@ async function bench(nodes: ToolResultNode[]) { return info }, maybeProvideInfo(id: string | undefined) { + // `this` inside an object-literal method is any under strict lint; the + // fake resolves through its own provideInfo above. + /* eslint-disable-next-line @typescript-eslint/no-unsafe-return, + @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access */ return (id === undefined ? undefined : this.provideInfo(id)) ?? { hooks: {}, props: {} } }, provide: (d: { resolve: (typeof providers)[number] }) => { providers.push(d.resolve); return () => {} }, diff --git a/packages/client/ui-conversation/tests/chat-view.spec.tsx b/packages/client/ui-conversation/tests/chat-view.spec.tsx index 93445fe4ae..20389c9e23 100644 --- a/packages/client/ui-conversation/tests/chat-view.spec.tsx +++ b/packages/client/ui-conversation/tests/chat-view.spec.tsx @@ -7,7 +7,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { Profiler } from 'react' import { act, cleanup, fireEvent, render } from '@testing-library/react' import type { - AssistantMessageNode, ConversationNode, ConversationSnapshot, RunningToolCall, SessionId, SessionListState, ToolResultNode, UserMessageNode, WorkspaceListState, + AssistantMessageNode, ConversationNode, ConversationSnapshot, RunningToolCall, SessionId, + SessionListState, ToolResultNode, UserMessageNode, WorkspaceListState, } from '@deepseek-ai/dsh-client-runtime/client' import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react' import { createSnapshotStore, PendingWait } from '@deepseek-ai/dsh-client-runtime/client' @@ -39,7 +40,7 @@ function makeSource(init?: Partial) { let snap: ConversationSnapshot = { ...snapshotBase(), ...init } const subs = new Set<() => void>() return { - set(next: Partial) { + set: (next: Partial) => { snap = { ...snap, ...next } for (const fn of [...subs]) fn() }, diff --git a/packages/client/ui-conversation/tests/coverage-tails.spec.tsx b/packages/client/ui-conversation/tests/coverage-tails.spec.tsx index 94747731eb..136901bd6d 100644 --- a/packages/client/ui-conversation/tests/coverage-tails.spec.tsx +++ b/packages/client/ui-conversation/tests/coverage-tails.spec.tsx @@ -22,7 +22,7 @@ afterEach(cleanup) describe('tails', () => { it('node-half apply is an intentional no-op', () => { - expect(nodeApply()).toBeUndefined() + expect(() => { nodeApply() }).not.toThrow() }) it('ToolRow stopped state renders the warning dot in the leading slot', () => { diff --git a/packages/client/ui-conversation/tests/input-scenarios.spec.tsx b/packages/client/ui-conversation/tests/input-scenarios.spec.tsx index 11b272c906..414f3c15b4 100644 --- a/packages/client/ui-conversation/tests/input-scenarios.spec.tsx +++ b/packages/client/ui-conversation/tests/input-scenarios.spec.tsx @@ -12,7 +12,6 @@ import { Context } from 'cordis' import { afterEach, describe, expect, it, vi } from 'vitest' import { act, cleanup, fireEvent, render } from '@testing-library/react' import { SessionsService } from '@deepseek-ai/dsh-client-runtime/client' -import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client' import { SlashService } from '@deepseek-ai/dsh-client-ui-slash/client' import type { ClientSessionContext, CommandClaim, PickOutcome, SubmitOutcome } from '@deepseek-ai/dsh-client-ui-slash/client' import { FakeApiClient, ok } from '../../runtime/tests/fake-api.ts' diff --git a/packages/client/ui-layout/src/client/AppFrame.tsx b/packages/client/ui-layout/src/client/AppFrame.tsx index 5f4984f579..a7c7696222 100644 --- a/packages/client/ui-layout/src/client/AppFrame.tsx +++ b/packages/client/ui-layout/src/client/AppFrame.tsx @@ -33,7 +33,10 @@ function DetailsColumn(props: { children?: ReactNode }) { return
{props.children}
} -/** One drag handle: pointer capture, rAF-throttled dx reports against the drag-start origin. `side` keys the hover-reveal CSS to the owning column. */ +/** + * One drag handle: pointer capture, rAF-throttled dx reports against the drag-start origin. + * `side` keys the hover-reveal CSS to the owning column. + */ function DragHandle(props: { side: 'sidebar' | 'details'; left: number; onStart: () => void; onDrag: (dx: number) => void; onEnd: () => void }) { const [dragging, setDragging] = useState(false) const origin = useRef(0) diff --git a/packages/client/ui-primitives/src/Button.tsx b/packages/client/ui-primitives/src/Button.tsx index 642372868a..d2e39dbf23 100644 --- a/packages/client/ui-primitives/src/Button.tsx +++ b/packages/client/ui-primitives/src/Button.tsx @@ -19,7 +19,7 @@ export function Button({ variant = 'ghost', size = 'md', icon, className, childr variant?: ButtonVariant size?: 'md' | 'sm' icon?: ReactNode - className?: string + className?: string | undefined children?: ReactNode } & ButtonHTMLAttributes) { return ( diff --git a/packages/client/ui-primitives/src/Tooltip.tsx b/packages/client/ui-primitives/src/Tooltip.tsx index e2a49f6579..2c48854055 100644 --- a/packages/client/ui-primitives/src/Tooltip.tsx +++ b/packages/client/ui-primitives/src/Tooltip.tsx @@ -27,7 +27,8 @@ interface AnchorProps { * Attach a hover/focus tooltip to an anchor element. * @param props.label - bubble text. * @param props.side - placement relative to the anchor (default 'right'). - * @param props.disabled - suppress the bubble while true; the anchor renders identically so toggling never remounts it (which would cut its CSS transitions). + * @param props.disabled - suppress the bubble while true; the anchor renders identically so + * toggling never remounts it (which would cut its CSS transitions). * @param props.children - a single anchor element; its own ref (callback or object) is forwarded alongside the tooltip's. * @returns the cloned anchor plus a fixed-position bubble while hovered/focused. */ diff --git a/packages/client/ui-primitives/src/markdown/CodeBlock.tsx b/packages/client/ui-primitives/src/markdown/CodeBlock.tsx index 6e7a5c73ab..de6a478af4 100644 --- a/packages/client/ui-primitives/src/markdown/CodeBlock.tsx +++ b/packages/client/ui-primitives/src/markdown/CodeBlock.tsx @@ -20,6 +20,9 @@ export interface CodeBlockProps { /** @returns true only when the host accepted the write. */ async function writeClipboard(text: string): Promise { + // lib.dom types clipboard non-optional, but insecure contexts omit it — + // that runtime gap is exactly what this guard detects. + /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */ if (navigator.clipboard?.writeText) { try { await navigator.clipboard.writeText(text) @@ -30,6 +33,9 @@ async function writeClipboard(text: string): Promise { } } // jsdom and older hosts: best-effort execCommand path when present. + // execCommand('copy') is the only clipboard fallback where the async API + // is missing; deprecated but deliberately retained. + /* eslint-disable @typescript-eslint/no-deprecated */ const exec = typeof document.execCommand === 'function' ? document.execCommand.bind(document) : undefined @@ -48,6 +54,7 @@ async function writeClipboard(text: string): Promise { } finally { el.remove() } + /* eslint-enable @typescript-eslint/no-deprecated */ } export function CodeBlock({ code, lang, className }: CodeBlockProps) { @@ -73,9 +80,9 @@ export function CodeBlock({ code, lang, className }: CodeBlockProps) {
{trimmed}
) : ( - // eslint-disable-next-line react/no-danger -- shiki's output is a static - // span tree it generated from `code` (no user HTML passes through), the - // sanctioned innerHTML consumption path per shiki's own docs. + // shiki's output is a static span tree it generated from `code` (no user + // HTML passes through), the sanctioned innerHTML consumption path per + // shiki's own docs.
) diff --git a/packages/client/ui-primitives/src/markdown/JsonBlock.tsx b/packages/client/ui-primitives/src/markdown/JsonBlock.tsx index 4469b4a161..ecbf594261 100644 --- a/packages/client/ui-primitives/src/markdown/JsonBlock.tsx +++ b/packages/client/ui-primitives/src/markdown/JsonBlock.tsx @@ -15,6 +15,8 @@ export function JsonBlock({ label, payload, defaultOpen = false }: { if (!open) return '' let s: string try { + // lib typing hides stringify's undefined arm (undefined/function/symbol payloads). + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition s = JSON.stringify(payload, null, 2) ?? String(payload) } catch { s = String(payload) diff --git a/packages/client/ui-primitives/src/markdown/MarkdownText.tsx b/packages/client/ui-primitives/src/markdown/MarkdownText.tsx index 28bd9b5374..639f53dbb1 100644 --- a/packages/client/ui-primitives/src/markdown/MarkdownText.tsx +++ b/packages/client/ui-primitives/src/markdown/MarkdownText.tsx @@ -53,7 +53,9 @@ function buildComponents(streaming: boolean): Components { // plain arm — retokenizing a growing fence on every chunk is quadratic // main-thread work; the finalize swap highlights it once. pre: ({ children }) => { - /* v8 ignore next 2 -- the markdown pipeline always hands `pre` its single `code` element; the undefined arm guards a react-markdown representation change. */ + // The markdown pipeline always hands `pre` its single `code` element; + // the undefined arm guards a react-markdown representation change. + /* v8 ignore next 2 */ const child = isValidElement<{ className?: string; children?: unknown }>(children) ? children : undefined const raw = child?.props.children // A fence whose content isn't one plain string (e.g. an empty fence) diff --git a/packages/client/ui-question/src/client/QuestionComposer.tsx b/packages/client/ui-question/src/client/QuestionComposer.tsx index e11c943bc9..4380ae8db0 100644 --- a/packages/client/ui-question/src/client/QuestionComposer.tsx +++ b/packages/client/ui-question/src/client/QuestionComposer.tsx @@ -37,6 +37,8 @@ export function parseQuestionTitle(title: string): string { /** Return whether a textarea key event belongs to an active IME composition. */ function isComposing(event: KeyboardEvent): boolean { + // keyCode 229 is the legacy IME-composition signal engines emit without isComposing. + // eslint-disable-next-line @typescript-eslint/no-deprecated return event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229 } @@ -61,7 +63,10 @@ function QuestionFlow({ pending }: { pending: PendingQuestion }) { }))) const [busy, setBusy] = useState<'answer' | 'cancel' | null>(null) const [error, setError] = useState(null) + // index stays in bounds (every setIndex site clamps) and drafts mirrors questions 1:1. + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const question = questions[index]! + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const draft = drafts[index]! const hasOptions = (question.options?.length ?? 0) > 0 diff --git a/packages/client/ui-question/tests/question-composer.spec.tsx b/packages/client/ui-question/tests/question-composer.spec.tsx index 4494e87ed1..dd130d80e2 100644 --- a/packages/client/ui-question/tests/question-composer.spec.tsx +++ b/packages/client/ui-question/tests/question-composer.spec.tsx @@ -99,7 +99,7 @@ describe('QuestionComposer', () => { { id: 'detail', selected: [], custom: '要能独立排查线上问题' }, { id: 'signals', selected: ['系统设计', '代码质量'] }, ])) - expect((screen.getByRole('button', { name: '正在提交…' })).disabled).toBe(true) + expect(screen.getByRole('button', { name: '正在提交…' }).disabled).toBe(true) }) it('skips individual questions without discarding earlier answers', () => { @@ -173,7 +173,7 @@ describe('QuestionComposer', () => { // Receipt rejection surfaces through the domain face's thrown message. fireEvent.click(screen.getByRole('button', { name: '放弃整组问题' })) expect(await screen.findByText('question cancellation rejected: bad-response')).toBeTruthy() - expect((screen.getByRole('button', { name: '跳过本题' })).disabled).toBe(false) + expect(screen.getByRole('button', { name: '跳过本题' }).disabled).toBe(false) fireEvent.click(screen.getByRole('button', { name: '放弃整组问题' })) expect(await screen.findByText('第二次取消失败')).toBeTruthy() @@ -199,7 +199,7 @@ describe('QuestionComposer', () => { fireEvent.click(screen.getByRole('checkbox', { name: '系统设计' })) fireEvent.click(screen.getByRole('button', { name: '提交' })) expect(await screen.findByText('网络中断')).toBeTruthy() - expect((screen.getByRole('button', { name: '提交' })).disabled).toBe(false) + expect(screen.getByRole('button', { name: '提交' }).disabled).toBe(false) fireEvent.click(screen.getByRole('button', { name: '提交' })) expect(await screen.findByText('字符串错误')).toBeTruthy() diff --git a/packages/client/ui-settings-general/tests/components.spec.tsx b/packages/client/ui-settings-general/tests/components.spec.tsx index 33368b6f12..f4395b4fba 100644 --- a/packages/client/ui-settings-general/tests/components.spec.tsx +++ b/packages/client/ui-settings-general/tests/components.spec.tsx @@ -49,7 +49,7 @@ describe('GeneralSection', () => { mount() expect(screen.getByText('Permission')).toBeTruthy() expect(screen.getByText('Choose default permission mode')).toBeTruthy() - const selector = screen.getByRole('button', { name: /Read only/ }) + const selector = screen.getByRole('button', { name: /Read only/ }) expect(selector.disabled).toBe(true) }) diff --git a/packages/client/ui-slots/tests/type-chain.spec.tsx b/packages/client/ui-slots/tests/type-chain.spec.tsx index f7dcf97702..b3a6aec873 100644 --- a/packages/client/ui-slots/tests/type-chain.spec.tsx +++ b/packages/client/ui-slots/tests/type-chain.spec.tsx @@ -41,7 +41,7 @@ function createPanelStore() { }) } -const chatStore = () => defineStore({ +const _chatStore = () => defineStore({ init: () => ({ selection: null as { id: string } | null, draft: '' }), actions: { select: (d, t: { id: string }) => { d.selection = t }, @@ -49,7 +49,7 @@ const chatStore = () => defineStore({ clearDraft: (d) => { d.draft = '' }, }, }) -type ChatHandle = ReturnType +type ChatHandle = ReturnType type FrameProps = & PropsRuntime<'chain.frame'> @@ -211,7 +211,7 @@ describe('terminal-design type chain', () => { // @ts-expect-error non-chain keys have no renderSlotChain dispatch chainSlots.renderSlotChain('chain.conv', {}) // @ts-expect-error a children set without chain keys provides no renderSlotChain - fp.renderSlotChain + type _NoChainSeat = typeof fp.renderSlotChain // renderSlot owner share typed at the call site. // @ts-expect-error owner shape mismatch (width missing) diff --git a/packages/client/ui-trajectory/src/client/TrajectoryCell.tsx b/packages/client/ui-trajectory/src/client/TrajectoryCell.tsx index 94fc6042a4..bc868f64c0 100644 --- a/packages/client/ui-trajectory/src/client/TrajectoryCell.tsx +++ b/packages/client/ui-trajectory/src/client/TrajectoryCell.tsx @@ -16,11 +16,11 @@ const KIND_LABEL: Record = { subtool: 'Sub', } -const TAG_CLASS: Record = { - user: css.tagUser!, - message: css.tagMessage!, - tool: css.tagTool!, - subtool: css.tagSubtool!, +const TAG_CLASS: Record = { + user: css.tagUser, + message: css.tagMessage, + tool: css.tagTool, + subtool: css.tagSubtool, } export interface TrajectoryCellProps extends HTMLAttributes { @@ -84,7 +84,7 @@ export function TrajectoryCell({
#{index} - {KIND_LABEL[kind]} + c !== undefined).join(' ')}>{KIND_LABEL[kind]} {text} diff --git a/packages/client/ui-trajectory/tests/views.spec.tsx b/packages/client/ui-trajectory/tests/views.spec.tsx index f2015607fc..782e4f684b 100644 --- a/packages/client/ui-trajectory/tests/views.spec.tsx +++ b/packages/client/ui-trajectory/tests/views.spec.tsx @@ -248,7 +248,7 @@ describe('WaterfallView standalone branches', () => { describe('node half', () => { it('node apply is an intentional no-op (loader-managed lifecycle only)', () => { - expect(nodeApply()).toBeUndefined() + expect(() => { nodeApply() }).not.toThrow() }) }) diff --git a/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx b/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx index 155cbf3ff4..ea1753a63e 100644 --- a/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx +++ b/packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx @@ -21,7 +21,10 @@ import { ProjectRowItem, SessionNodeItem } from './rows/Rows.tsx' import { WorkspaceCreateFlow } from './WorkspacePicker.tsx' import css from './WorkspaceBrowser.module.css' -/** Column slide length (--ds-transition-duration-slow): rail-search focus waits it out — focus() forces a synchronous layout and would jank the slide. */ +/** + * Column slide length (--ds-transition-duration-slow): rail-search focus waits it out — + * focus() forces a synchronous layout and would jank the slide. + */ const EXPAND_SLIDE_MS = 300 const GROUP_BY_ITEMS = [ @@ -292,7 +295,7 @@ export function WorkspaceBrowser({ setRenameError(null) } const confirmRename = () => { - if (renameBlocked || renameTarget === null) return + if (renameBlocked) return setRenaming(true) setRenameError(null) renameWorkspace(renameTarget.workspaceId, renameTrimmed).then(() => { @@ -481,7 +484,7 @@ export function WorkspaceBrowser({ - + + )} > @@ -179,10 +179,10 @@ export function WorkspaceCreateFlow({ description="The name is used for both the workspace and its new folder." footer={( <> - +