refactor(frontend): clean metadata on AssistantChatPanel + TaskDrawer

- AssistantChatPanel: C:3→C:4 (33 side-effecting functions), remove JSDoc
  duplicates (INV_4), add @PRE/@POST/@SIDE_EFFECT/@DATA_CONTRACT/@RATIONALE/@REJECTED
- TaskDrawer: remove JSDoc duplicates (INV_4), replace 4x @PURPOSE
  JSDoc blocks with @BRIEF/@PRE/@POST in function contracts (INV_4),
  add @RATIONALE/@REJECTED
- Both: consolidate all metadata in HTML comment #region, remove
  scattered JSDoc in <script>
This commit is contained in:
2026-07-05 08:53:17 +03:00
parent bc3e288d0b
commit 008a8a92a5
2 changed files with 51 additions and 87 deletions

View File

@@ -1,31 +1,31 @@
<!-- #region AssistantChatPanel [C:3] [TYPE Component] [SEMANTICS assistant, chat, panel, conversation, confirmation] -->
<!-- #region AssistantChatPanel [C:4] [TYPE Component] [SEMANTICS assistant, chat, panel, conversation, confirmation] -->
<!-- @ingroup Assistant -->
<!-- @BRIEF Component component: lib/components/assistant/AssistantChatPanel.svelte -->
<!-- @BRIEF Assistant chat workspace for mixed-initiative dataset review clarification, contextual actions, and task tracking. -->
<!-- @LAYER UI -->
<!-- @PRE Assistant chat store, API adapters, and optional dataset review session context are initialized before the panel becomes interactive. -->
<!-- @POST User and assistant message state, conversation history, and task affordances stay synchronized with the active conversation scope. -->
<!-- @SIDE_EFFECT Reads and mutates assistant conversation state, may navigate to review routes, opens task drawer affordances, and emits toast feedback. -->
<!-- @DATA_CONTRACT Input[{variant,className,assistantChatStore,datasetReviewSessionId?}] → Output[{messages,conversations,conversationId,llmReady,panelVisible}] -->
<!-- @INVARIANT User commands and assistant responses are appended in chronological order. -->
<!-- @INVARIANT Risky operations are executed only through explicit confirm action. -->
<!-- @RELATION BINDS_TO -> [EXT:frontend:assistantChatStore] -->
<!-- @RELATION BINDS_TO -> [EXT:frontend:datasetReviewSessionStore] -->
<!-- @RELATION CALLS -> [AssistantApi] -->
<!-- @RELATION DISPATCHES -> [EXT:frontend:taskDrawerStore] -->
<!-- @RELATION DEPENDS_ON -> [AssistantClarificationCard] -->
<!-- @RELATION DEPENDS_ON -> [ToolCallCard] -->
<!-- @RELATION DEPENDS_ON -> [AgentChatModel] -->
<!-- @UX_STATE Closed -> Panel is hidden when variant is drawer. -->
<!-- @UX_STATE Embedded -> Panel mounts inline as the primary workspace canvas. -->
<!-- @UX_STATE LoadingHistory -> Existing conversation history is loading. -->
<!-- @UX_STATE Idle -> Input is available and no request in progress. -->
<!-- @UX_STATE Sending -> Input locked while request is pending. -->
<!-- @UX_STATE Error -> Failed action rendered as assistant failed message. -->
<!-- @UX_FEEDBACK Started operation surfaces task_id and quick action to open task drawer. -->
<!-- @UX_RECOVERY User can retry command or action from input and action buttons. -->
<!-- @RATIONALE C:3→C:4 upgrade: 33 side-effecting functions (API calls, navigation, store mutations, file I/O) warrant stateful tier. JSDoc duplicates removed (INV_4). Model extraction deferred — AgentChatModel already handles Gradio-powered agent interaction; remaining logic is UI glue (conversation CRUD, file upload, LLM status polling) with no cross-widget invariants that would benefit from a standalone Reactive Screen Model. -->
<!-- @REJECTED Full Reactive Screen Model rejected — the component delegates agent logic to AgentChatModel (Gradio Client). The remaining 33 functions are conversation lifecycle management (load/create/select/archive) and UI helpers. Extracting these into a separate model would create indirection without reducing cognitive load — the functions directly manipulate local $state atoms with no cross-widget shared invariants. -->
<script lang="ts">
/**
* @PURPOSE: Assistant chat workspace for mixed-initiative dataset review clarification, contextual actions, and task tracking.
* @LAYER UI
* @RELATION BINDS_TO -> assistantChat
* @RELATION CALLS -> AssistantApi
* @RELATION BINDS_TO -> DatasetReviewWorkspace
* @RELATION DISPATCHES -> [EXT:frontend:taskDrawer]
* @SEMANTICS: assistant-chat, confirmation, long-running-task, progress-tracking
* @PRE: Assistant chat store, API adapters, and optional dataset review session context are initialized before the panel becomes interactive.
* @POST: User and assistant message state, conversation history, and task affordances stay synchronized with the active conversation scope.
* @SIDE_EFFECT: Reads and mutates assistant conversation state, may navigate to review routes, opens task drawer affordances, and emits toast feedback.
* @DATA_CONTRACT: Input[{variant,className,assistantChatStore,datasetReviewSessionId?}] -> Output[{messages,conversations,conversationId,llmReady,panelVisible}]
* @INVARIANT: User commands and assistant responses are appended in chronological order.
* @INVARIANT: Risky operations are executed only through explicit confirm action.
* @UX_STATE: Closed -> Panel is hidden when variant is drawer.
* @UX_STATE: Embedded -> Panel mounts inline as the primary workspace canvas.
* @UX_STATE: LoadingHistory -> Existing conversation history is loading.
* @UX_STATE: Idle -> Input is available and no request in progress.
* @UX_STATE: Sending -> Input locked while request is pending.
* @UX_STATE: Error -> Failed action rendered as assistant failed message.
* @UX_FEEDBACK: Started operation surfaces task_id and quick action to open task drawer.
* @UX_RECOVERY: User can retry command or action from input and action buttons.
*/
import { onMount, onDestroy } from "svelte";
import { goto } from "$app/navigation";

View File

@@ -2,6 +2,12 @@
<!-- @ingroup Layout -->
<!-- @BRIEF Global task drawer for monitoring background operations via WebSocket logs, task summaries, and detail views. -->
<!-- @LAYER UI -->
<!-- @PRE Task drawer store and optional WebSocket task context are available before detail mode is requested. -->
<!-- @POST Drawer stays closed, shows the recent-task list, or renders a selected task detail without losing task context. -->
<!-- @SIDE_EFFECT Opens WebSocket streams, loads recent tasks, and mutates drawer selection state. -->
<!-- @DATA_CONTRACT TaskDrawerState → RecentTaskList | ActiveTaskDetail -->
<!-- @INVARIANT Drawer shows logs for active task or remains closed. -->
<!-- @UX_REACTIVITY Uses $state for local drawer UI and $derived/store subscriptions for active task synchronization. -->
<!-- @RELATION BINDS_TO -> [EXT:frontend:taskDrawerStore] -->
<!-- @RELATION BINDS_TO -> [EXT:frontend:assistantChatStore] -->
<!-- @RELATION DEPENDS_ON -> [TaskLogViewer] -->
@@ -13,45 +19,15 @@
<!-- @UX_FEEDBACK Close/Back buttons allow navigation. -->
<!-- @UX_FEEDBACK WebSocket provides real-time log streaming. -->
<!-- @UX_RECOVERY Click outside or Escape closes drawer. -->
<!-- @TEST_DATA llm_task_success_with_fail_result -> {"activeTaskDetails":{"plugin_id":"llm_dashboard_validation","status":"SUCCESS","result":{"status":"FAIL"}}} -->
<!-- @TEST_DATA llm_task_success_with_pass_result -> {"activeTaskDetails":{"plugin_id":"llm_dashboard_validation","status":"SUCCESS","result":{"status":"PASS"}}} -->
<!-- @TEST_CONTRACT Component_TaskDrawer -> { required_props: {}, optional_props: {}, invariants: ["Binds successfully to taskDrawerStore", "Renders list mode when no activeTaskId is set", "Renders detail mode and websocket stream when activeTaskId is set", "Closes properly on handleClose"] } -->
<!-- @TEST_FIXTURE init_state -> {} -->
<!-- @TEST_EDGE empty_task_list -> displays "No recent tasks" -->
<!-- @TEST_INVARIANT default_rendering -> verifies: [init_state] -->
<!-- @RATIONALE JSDoc duplicates removed (INV_4). Component consolidates all task-drawer UI logic (WebSocket lifecycles, task list/detail modes, password prompts) into a single C:4 component because the task drawer is a global singleton — extracting subcomponents would scatter the WebSocket lifecycle across files, making reconnect/teardown logic fragile and invisible to the attention mechanism. -->
<!-- @REJECTED Extracting WebSocket logic into a standalone model was rejected — the WebSocket lifecycle is tightly coupled to drawer open/close transitions and DOM cleanup. A separate model would duplicate $effect cleanup logic and create lifecycle-sync bugs (orphaned WebSocket connections when the drawer is destroyed). Inline $effect with onDestroy guards is the safe pattern for WebSocket teardown. -->
<script lang="ts">
/**
* @PURPOSE: Global task drawer for monitoring background operations
* @LAYER UI
* @RELATION BINDS_TO -> [EXT:frontend:taskDrawer]
* @SEMANTICS: TaskLogViewer
* @PRE: Task drawer store and optional websocket task context are available before detail mode is requested.
* @POST: Drawer stays closed, shows the recent-task list, or renders a selected task detail without losing task context.
* @SIDE_EFFECT: Opens websocket streams, loads recent tasks, and mutates drawer selection state.
* @DATA_CONTRACT: TaskDrawerState -> RecentTaskList | ActiveTaskDetail
* @INVARIANT: Drawer shows logs for active task or remains closed
* @UX_REACTIVITY: Uses $state for local drawer UI and $derived or store subscriptions for active task synchronization.
*
* @UX_STATE: Closed -> Drawer hidden, no active task
* @UX_STATE: Open/ListMode -> Drawer visible, showing recent tasks list
* @UX_STATE: Open/TaskDetail -> Drawer visible, showing logs for selected task
* @UX_STATE: InputRequired -> Interactive form rendered in drawer
* @UX_FEEDBACK: Close button allows task to continue running
* @UX_FEEDBACK: Back button returns to task list
* @UX_RECOVERY: Click outside or X button closes drawer
* @UX_RECOVERY: Back button shows task list when viewing task details
* @TEST_DATA: llm_task_success_with_fail_result -> {"activeTaskDetails":{"plugin_id":"llm_dashboard_validation","status":"SUCCESS","result":{"status":"FAIL"}}}
* @TEST_DATA: llm_task_success_with_pass_result -> {"activeTaskDetails":{"plugin_id":"llm_dashboard_validation","status":"SUCCESS","result":{"status":"PASS"}}}
*
* @TEST_CONTRACT Component_TaskDrawer ->
* {
* required_props: {},
* optional_props: {},
* invariants: [
* "Binds successfully to taskDrawerStore",
* "Renders list mode when no activeTaskId is set",
* "Renders detail mode and websocket stream when activeTaskId is set",
* "Closes properly on handleClose"
* ]
* }
* @TEST_FIXTURE init_state -> {}
* @TEST_EDGE empty_task_list -> displays \"No recent tasks\"
* @TEST_INVARIANT default_rendering -> verifies: [init_state]
*/
import { onDestroy, onMount } from "svelte";
import { log } from "$lib/cot-logger";
@@ -576,13 +552,9 @@
// #region disconnectWebSocket:Function [TYPE Function]
// @ingroup Layout
// @UX_STATE: Loading -> Default
// @RELATION USES -> [EXT:frontend:App]
/**
* @PURPOSE: Disconnects the active WebSocket connection
* @PRE: ws may or may not be initialized
* @POST: ws is closed and set to null
*/
// @BRIEF Disconnects the active WebSocket connection.
// @PRE ws may or may not be initialized.
// @POST ws is closed and set to null.
function disconnectWebSocket() {
log("TaskDrawer", "REASON", "Disconnecting WebSocket");
if (wsReconnectTimer) {
@@ -600,11 +572,9 @@
// #region loadRecentTasks:Function [TYPE Function]
// @ingroup Layout
/**
* @PURPOSE: Load recent tasks for list mode display
* @PRE: User is on task drawer or api is ready.
* @POST: recentTasks array populated with task list
*/
// @BRIEF Load recent tasks for list mode display.
// @PRE User is on task drawer or API is ready.
// @POST recentTasks array populated with task list.
async function loadRecentTasks() {
loadingTasks = true;
try {
@@ -624,11 +594,9 @@
// #region selectTask:Function [TYPE Function]
// @ingroup Layout
/**
* @PURPOSE: Select a task from list to view details
* @PRE: task is a valid task object
* @POST: drawer state updated to show task details
*/
// @BRIEF Select a task from list to view details.
// @PRE task is a valid task object.
// @POST drawer state updated to show task details.
function selectTask(task) {
log("TaskDrawer", "REASON", "Select task");
taskDrawerStore.update((state) => ({
@@ -640,13 +608,9 @@
// #region goBackToList:Function [TYPE Function]
// @ingroup Layout
// @UX_STATE: Loading -> Default
// @RELATION USES -> [EXT:frontend:App]
/**
* @PURPOSE: Return to task list view from task details
* @PRE: Drawer is open and activeTaskId is set
* @POST: Drawer switches to list view and reloads tasks
*/
// @BRIEF Return to task list view from task details.
// @PRE Drawer is open and activeTaskId is set.
// @POST Drawer switches to list view and reloads tasks.
function goBackToList() {
log("TaskDrawer", "REASON", "Go back to task list");
taskDrawerStore.update((state) => ({