From e8d4a6287713fafb4f10d5c0bb47da9ede8dcd11 Mon Sep 17 00:00:00 2001 From: murashit Date: Thu, 4 Jun 2026 20:05:48 +0900 Subject: [PATCH] Standardize chat toolbar and add composer runtime meta --- src/features/chat/chat-composer-controller.ts | 3 + .../chat/chat-view-controller-assembly.ts | 3 + src/features/chat/toolbar-model.ts | 6 - src/features/chat/ui/composer.tsx | 24 ++++ src/features/chat/ui/toolbar.tsx | 78 ++++-------- src/features/chat/view-model.ts | 79 +++++++----- src/features/chat/view-snapshot.ts | 7 ++ src/features/chat/view.ts | 6 + src/runtime/settings.ts | 6 - src/styles/00-tokens.css | 8 -- src/styles/20-chat-toolbar.css | 73 ----------- src/styles/21-chat-status-diagnostics.css | 80 +----------- src/styles/33-chat-composer.css | 32 +++++ src/styles/90-responsive-overrides.css | 31 ----- .../chat/chat-composer-controller.test.ts | 1 + .../chat/ui/renderers/composer.test.ts | 29 +++++ .../chat/ui/renderers/toolbar.test.ts | 55 ++++---- tests/features/chat/view-model.test.ts | 117 ++++++------------ tests/runtime/runtime-settings.test.ts | 8 -- tests/styles.test.ts | 17 ++- 20 files changed, 249 insertions(+), 414 deletions(-) diff --git a/src/features/chat/chat-composer-controller.ts b/src/features/chat/chat-composer-controller.ts index eb018a84..bddb5f77 100644 --- a/src/features/chat/chat-composer-controller.ts +++ b/src/features/chat/chat-composer-controller.ts @@ -17,6 +17,7 @@ import { userInputWithWikiLinkMentionsAndSkills } from "./composer/wikilink-cont import type { UserInput } from "../../generated/app-server/v2/UserInput"; import { renderComposerShell, syncComposerHeight } from "./ui/composer"; import { chatTurnBusy, type ChatAction, type ChatState, type ChatStateStore } from "./chat-state"; +import type { ComposerMetaViewModel } from "./view-model"; export interface ChatComposerControllerOptions { app: App; @@ -26,6 +27,7 @@ export interface ChatComposerControllerOptions { scrollThreadFromComposerEdges: () => boolean; canInterrupt: () => boolean; composerPlaceholder: () => string; + composerMeta: () => ComposerMetaViewModel; currentModelForSuggestions: () => string | null; renderIfDetached: () => void; onDraftChange: () => void; @@ -117,6 +119,7 @@ export class ChatComposerController { this.insertSuggestion(suggestion); }, }, + this.options.composerMeta(), ); this.composer = elements.composer; syncComposerHeight(this.composer); diff --git a/src/features/chat/chat-view-controller-assembly.ts b/src/features/chat/chat-view-controller-assembly.ts index 3a91b943..15ebdfd9 100644 --- a/src/features/chat/chat-view-controller-assembly.ts +++ b/src/features/chat/chat-view-controller-assembly.ts @@ -7,6 +7,7 @@ import type { RuntimeSnapshot } from "../../runtime/state"; import { currentModel } from "../../runtime/state"; import type { ChatState, ChatStateStore } from "./chat-state"; import type { DisplayDetailSection } from "./display/types"; +import type { ComposerMetaViewModel } from "./view-model"; import { ChatAppServerController } from "./chat-app-server-controller"; import { ChatComposerController } from "./chat-composer-controller"; import { ChatController } from "./chat-controller"; @@ -95,6 +96,7 @@ export interface ChatViewControllerAssemblyHost { pendingRequestsSignature: () => string; activeComposerThreadName: () => string | null; composerPlaceholder: () => string; + composerMetaViewModel: () => ComposerMetaViewModel; runtimeSnapshot: () => RuntimeSnapshot; collaborationModeLabel: () => string; connectionDiagnosticDetails: () => DisplayDetailSection[]; @@ -248,6 +250,7 @@ export function createChatViewControllerAssembly(host: ChatViewControllerAssembl canInterrupt: () => host.getState().turnLifecycle.kind !== "idle" && Boolean(host.getState().activeThreadId && activeTurnId(host.getState())), composerPlaceholder: host.composerPlaceholder, + composerMeta: host.composerMetaViewModel, currentModelForSuggestions: () => currentModel(host.runtimeSnapshot()), renderIfDetached: host.effects.render.now, onDraftChange: host.effects.liveState.refresh, diff --git a/src/features/chat/toolbar-model.ts b/src/features/chat/toolbar-model.ts index 9f0ad10f..96d6421e 100644 --- a/src/features/chat/toolbar-model.ts +++ b/src/features/chat/toolbar-model.ts @@ -1,7 +1,6 @@ import type { EffectiveConfigSection, RateLimitSummary } from "../../runtime/view"; export type ToolbarPanelKind = "history" | "status" | "runtime"; -export type ToolbarStatusState = "offline" | "ready" | "degraded" | "blocked" | "running"; export interface ToolbarChoice { label: string; @@ -38,17 +37,12 @@ export interface ToolbarDiagnosticSection { export interface ToolbarViewModel { connected: boolean; status: string; - statusState: ToolbarStatusState; historyOpen: boolean; statusPanelOpen: boolean; runtimeOpen: boolean; planActive: boolean; autoReviewActive: boolean; fastActive: boolean; - runtimeSummary: string; - runtimeTitle: string; - runtimeEmphasized: boolean; - context: { level: "ok" | "warn" | "danger"; title: string; label: string; percent: number | null } | null; rateLimit: RateLimitSummary | null; configSections: EffectiveConfigSection[]; openPanel: ToolbarPanelKind | null; diff --git a/src/features/chat/ui/composer.tsx b/src/features/chat/ui/composer.tsx index 084988e9..25ecbc5d 100644 --- a/src/features/chat/ui/composer.tsx +++ b/src/features/chat/ui/composer.tsx @@ -2,6 +2,7 @@ import type { ButtonHTMLAttributes, ComponentChild as UiNode, Ref } from "preact import { useLayoutEffect, useRef } from "preact/hooks"; import type { ComposerSuggestion } from "../composer/suggestions"; +import type { ComposerMetaViewModel } from "../view-model"; import { IconButton } from "../../../shared/ui/components"; import { renderUiRoot } from "../../../shared/ui/ui-root"; import { syncTextareaHeight } from "../../../shared/ui/textarea-autogrow"; @@ -25,6 +26,12 @@ type ButtonProps = ButtonHTMLAttributes & { disabled?: boolean | undefined; }; +const DEFAULT_COMPOSER_META: ComposerMetaViewModel = { + fatal: null, + contextIndicator: "⣀⣀⣀⣀⣀⣀⣀⣀", + runtime: "default", +}; + export function renderComposerShell( parent: HTMLElement, viewId: string, @@ -35,6 +42,7 @@ export function renderComposerShell( suggestions: readonly ComposerSuggestion[], selectedSuggestionIndex: number, callbacks: ComposerCallbacks, + meta: ComposerMetaViewModel = DEFAULT_COMPOSER_META, ): ComposerElements { const elements: Partial = {}; renderUiRoot( @@ -45,6 +53,7 @@ export function renderComposerShell( busy={busy} canInterrupt={canInterrupt} normalPlaceholder={normalPlaceholder} + meta={meta} suggestions={suggestions} selectedSuggestionIndex={selectedSuggestionIndex} callbacks={callbacks} @@ -63,6 +72,7 @@ function ComposerShell({ busy, canInterrupt, normalPlaceholder, + meta, suggestions, selectedSuggestionIndex, callbacks, @@ -73,6 +83,7 @@ function ComposerShell({ busy: boolean; canInterrupt: boolean; normalPlaceholder: string; + meta: ComposerMetaViewModel; suggestions: readonly ComposerSuggestion[]; selectedSuggestionIndex: number; callbacks: ComposerCallbacks; @@ -137,6 +148,7 @@ function ComposerShell({ onClick={callbacks.onSendOrInterrupt} /> + {meta.fatal}; + } + return ( + + ); +} + function composerSendMode( busy: boolean, canInterrupt: boolean, diff --git a/src/features/chat/ui/toolbar.tsx b/src/features/chat/ui/toolbar.tsx index ff4b0aa8..90f5f99c 100644 --- a/src/features/chat/ui/toolbar.tsx +++ b/src/features/chat/ui/toolbar.tsx @@ -36,19 +36,18 @@ export function renderToolbar(toolbar: HTMLElement, model: ToolbarViewModel, act function Toolbar({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode { return ( <> -
- -
- +
+
+ + +
- -
@@ -75,9 +74,9 @@ function ToolbarIconButton({ ); } -function RuntimeStrip({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode { +function RuntimeButtons({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode { return ( -
+ <> - -
+ /> + ); } @@ -139,33 +128,12 @@ function RuntimeIcon({ ); } -function ContextMeter({ context }: { context: ToolbarViewModel["context"] }): UiNode { - if (!context) return null; - return ( -
- {context.label} - - - -
- ); -} - function StatusButton({ model, actions }: { model: ToolbarViewModel; actions: ToolbarActions }): UiNode { return ( -