Compare commits

2 Commits

Author SHA1 Message Date
af74841765 semantic update 2026-02-20 10:41:15 +03:00
d7e4919d54 few shots update 2026-02-20 10:26:01 +03:00
23 changed files with 2024 additions and 1401 deletions

View File

@@ -73,6 +73,40 @@
- 📝 Recursive helper to write entity tree to Markdown with tier badges and enhanced details. - 📝 Recursive helper to write entity tree to Markdown with tier badges and enhanced details.
- ƒ **to_dict** (`Function`) `[TRIVIAL]` - ƒ **to_dict** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan) - 📝 Auto-detected function (orphan)
- 📦 **PluginExampleShot** (`Module`)
- 📝 Reference implementation of a plugin following GRACE standards.
- 🔗 IMPLEMENTS -> `[DEF:Std:Plugin]`
- ƒ **get_schema** (`Function`)
- 📝 Defines input validation schema.
- ƒ **execute** (`Function`)
- 📝 Core plugin logic with structured logging and progress reporting.
- ƒ **id** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **name** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **description** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **version** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 📦 **BackendRouteShot** (`Module`)
- 📝 Reference implementation of a task-based route using GRACE-Poly.
- 🔗 IMPLEMENTS -> `[DEF:Std:API_FastAPI]`
- ƒ **create_task** (`Function`)
- 📝 Create and start a new task using TaskManager. Non-blocking.
- 🔗 CALLS -> `task_manager.create_task`
- 🧩 **FrontendComponentShot** (`Component`)
- 📝 Reference implementation of a task-spawning component using
- 🏗️ Layer: UI
- 📥 Props: plugin_id: any, params: any
- ⬅️ READS_FROM `lib`
- ⬅️ READS_FROM `t`
- 📦 **DashboardTypes** (`Module`) `[TRIVIAL]`
- 📝 TypeScript interfaces for Dashboard entities
- 🏗️ Layer: Domain
- 🧩 **Counter** (`Component`) `[TRIVIAL]`
- 📝 Simple counter demo component
- 🏗️ Layer: UI
- ➡️ WRITES_TO `state`
- 📦 **stores_module** (`Module`) - 📦 **stores_module** (`Module`)
- 📝 Global state management using Svelte stores. - 📝 Global state management using Svelte stores.
- 🏗️ Layer: UI-State - 🏗️ Layer: UI-State
@@ -116,6 +150,11 @@
- 📝 Generic request wrapper. - 📝 Generic request wrapper.
- 📦 **api** (`Data`) - 📦 **api** (`Data`)
- 📝 API client object with specific methods. - 📝 API client object with specific methods.
- 📦 **Utils** (`Module`) `[TRIVIAL]`
- 📝 General utility functions (class merging)
- 🏗️ Layer: Infra
- ƒ **cn** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 🗄️ **authStore** (`Store`) - 🗄️ **authStore** (`Store`)
- 📝 Manages the global authentication state on the frontend. - 📝 Manages the global authentication state on the frontend.
- 🏗️ Layer: Feature - 🏗️ Layer: Feature
@@ -131,9 +170,9 @@
- 📝 Clears authentication state and storage. - 📝 Clears authentication state and storage.
- ƒ **setLoading** (`Function`) - ƒ **setLoading** (`Function`)
- 📝 Updates the loading state. - 📝 Updates the loading state.
- 📦 **debounce** (`Module`) `[TRIVIAL]` - 📦 **Debounce** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/lib/utils/debounce.js - 📝 Debounce utility for limiting function execution rate
- 🏗️ Layer: Unknown - 🏗️ Layer: Infra
- ƒ **debounce** (`Function`) `[TRIVIAL]` - ƒ **debounce** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan) - 📝 Auto-detected function (orphan)
- 🗄️ **taskDrawer** (`Store`) `[CRITICAL]` - 🗄️ **taskDrawer** (`Store`) `[CRITICAL]`
@@ -181,8 +220,9 @@
- 📝 Unit tests for sidebar store - 📝 Unit tests for sidebar store
- 🏗️ Layer: Domain (Tests) - 🏗️ Layer: Domain (Tests)
- ƒ **test_sidebar_initial_state** (`Function`) - ƒ **test_sidebar_initial_state** (`Function`)
- ƒ **test_toggleSidebar** (`Function`) - ƒ **test_toggleSidebar** (`Function`)
- ƒ **test_setActiveItem** (`Function`) - ƒ **test_setActiveItem** (`Function`)
- ƒ **test_mobile_functions** (`Function`)
- 📦 **frontend.src.lib.stores.__tests__.test_activity** (`Module`) - 📦 **frontend.src.lib.stores.__tests__.test_activity** (`Module`)
- 📝 Unit tests for activity store - 📝 Unit tests for activity store
- 🏗️ Layer: UI - 🏗️ Layer: UI
@@ -202,7 +242,9 @@
- 🧩 **Select** (`Component`) `[TRIVIAL]` - 🧩 **Select** (`Component`) `[TRIVIAL]`
- 📝 Standardized dropdown selection component. - 📝 Standardized dropdown selection component.
- 🏗️ Layer: Atom - 🏗️ Layer: Atom
- 📥 Props: label: string , value: string | number , disabled: boolean - ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- 📦 **ui** (`Module`) `[TRIVIAL]` - 📦 **ui** (`Module`) `[TRIVIAL]`
- 📝 Central export point for standardized UI components. - 📝 Central export point for standardized UI components.
- 🏗️ Layer: Atom - 🏗️ Layer: Atom
@@ -210,21 +252,26 @@
- 🧩 **PageHeader** (`Component`) `[TRIVIAL]` - 🧩 **PageHeader** (`Component`) `[TRIVIAL]`
- 📝 Standardized page header with title and action area. - 📝 Standardized page header with title and action area.
- 🏗️ Layer: Atom - 🏗️ Layer: Atom
- 📥 Props: title: string - ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `props`
- 🧩 **Card** (`Component`) `[TRIVIAL]` - 🧩 **Card** (`Component`) `[TRIVIAL]`
- 📝 Standardized container with padding and elevation. - 📝 Standardized container with padding and elevation.
- 🏗️ Layer: Atom - 🏗️ Layer: Atom
- 📥 Props: title: string - ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `props`
- 🧩 **Button** (`Component`) `[TRIVIAL]` - 🧩 **Button** (`Component`) `[TRIVIAL]`
- 📝 Define component interface and default values. - 📝 Define component interface and default values (Svelte 5 Runes).
- 🏗️ Layer: Atom - 🏗️ Layer: Atom
- 🔒 Invariant: Supports accessible labels and keyboard navigation. - 🔒 Invariant: Supports accessible labels and keyboard navigation.
- 📥 Props: isLoading: boolean , disabled: boolean - ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `props`
- 🧩 **Input** (`Component`) `[TRIVIAL]` - 🧩 **Input** (`Component`) `[TRIVIAL]`
- 📝 Standardized text input component with label and error handling. - 📝 Standardized text input component with label and error handling.
- 🏗️ Layer: Atom - 🏗️ Layer: Atom
- 🔒 Invariant: Consistent spacing and focus states. - 🔒 Invariant: Consistent spacing and focus states.
- 📥 Props: label: string , value: string , placeholder: string , error: string , disabled: boolean - ⬅️ READS_FROM `lib`
- ➡️ WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- 🧩 **LanguageSwitcher** (`Component`) `[TRIVIAL]` - 🧩 **LanguageSwitcher** (`Component`) `[TRIVIAL]`
- 📝 Dropdown component to switch between supported languages. - 📝 Dropdown component to switch between supported languages.
- 🏗️ Layer: Atom - 🏗️ Layer: Atom
@@ -293,10 +340,9 @@
- 📝 Display page hierarchy navigation - 📝 Display page hierarchy navigation
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 🔒 Invariant: Always shows current page path - 🔒 Invariant: Always shows current page path
- 📥 Props: maxVisible: any
- ⬅️ READS_FROM `app` - ⬅️ READS_FROM `app`
- ⬅️ READS_FROM `lib` - ⬅️ READS_FROM `lib`
- READS_FROM `page` - WRITES_TO `props`
- 📦 **Breadcrumbs** (`Module`) `[TRIVIAL]` - 📦 **Breadcrumbs** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/lib/components/layout/Breadcrumbs.svelte - 📝 Auto-generated module for frontend/src/lib/components/layout/Breadcrumbs.svelte
- 🏗️ Layer: Unknown - 🏗️ Layer: Unknown
@@ -328,6 +374,12 @@
- 📝 Auto-detected function (orphan) - 📝 Auto-detected function (orphan)
- ƒ **disconnectWebSocket** (`Function`) `[TRIVIAL]` - ƒ **disconnectWebSocket** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan) - 📝 Auto-detected function (orphan)
- 📦 **ErrorPage** (`Page`)
- 📝 Global error page displaying HTTP status and messages
- 🏗️ Layer: UI
- 📦 **RootLayoutConfig** (`Module`) `[TRIVIAL]`
- 📝 Root layout configuration (SPA mode)
- 🏗️ Layer: Infra
- 📦 **HomePage** (`Page`) `[CRITICAL]` - 📦 **HomePage** (`Page`) `[CRITICAL]`
- 📝 Redirect to Dashboard Hub as per UX requirements - 📝 Redirect to Dashboard Hub as per UX requirements
- 🏗️ Layer: UI - 🏗️ Layer: UI
@@ -692,8 +744,9 @@
- 🧩 **PasswordPrompt** (`Component`) - 🧩 **PasswordPrompt** (`Component`)
- 📝 A modal component to prompt the user for database passwords when a migration task is paused. - 📝 A modal component to prompt the user for database passwords when a migration task is paused.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 📥 Props: show: any, databases: any, errorMessage: any
- ⚡ Events: cancel, resume - ⚡ Events: cancel, resume
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `effect`
- ƒ **handleSubmit** (`Function`) - ƒ **handleSubmit** (`Function`)
- 📝 Validates and dispatches the passwords to resume the task. - 📝 Validates and dispatches the passwords to resume the task.
- ƒ **handleCancel** (`Function`) - ƒ **handleCancel** (`Function`)
@@ -703,6 +756,7 @@
- 🏗️ Layer: Feature - 🏗️ Layer: Feature
- 🔒 Invariant: Each source database can be mapped to one target database. - 🔒 Invariant: Each source database can be mapped to one target database.
- ⚡ Events: update - ⚡ Events: update
- ➡️ WRITES_TO `props`
- ƒ **updateMapping** (`Function`) - ƒ **updateMapping** (`Function`)
- 📝 Updates a mapping for a specific source database. - 📝 Updates a mapping for a specific source database.
- ƒ **getSuggestion** (`Function`) - ƒ **getSuggestion** (`Function`)
@@ -711,13 +765,12 @@
- 📝 Displays detailed logs for a specific task inline or in a modal using TaskLogPanel. - 📝 Displays detailed logs for a specific task inline or in a modal using TaskLogPanel.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 🔒 Invariant: Real-time logs are always appended without duplicates. - 🔒 Invariant: Real-time logs are always appended without duplicates.
- 📥 Props: show: any, inline: any, taskId: any, taskStatus: any, realTimeLogs: any
- ⚡ Events: close - ⚡ Events: close
- READS_FROM `t` - WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `state`
- 📦 **handleRealTimeLogs** (`Action`) - 📦 **handleRealTimeLogs** (`Action`)
- 📝 Append real-time logs as they arrive from WebSocket, preventing duplicates */
- ƒ **fetchLogs** (`Function`) - ƒ **fetchLogs** (`Function`)
- 📝 Fetches logs for the current task from API (polling fallback).
- 📦 **TaskLogViewer** (`Module`) `[TRIVIAL]` - 📦 **TaskLogViewer** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/components/TaskLogViewer.svelte - 📝 Auto-generated module for frontend/src/components/TaskLogViewer.svelte
- 🏗️ Layer: Unknown - 🏗️ Layer: Unknown
@@ -732,8 +785,8 @@
- 📝 Prompts the user to provide a database mapping when one is missing during migration. - 📝 Prompts the user to provide a database mapping when one is missing during migration.
- 🏗️ Layer: Feature - 🏗️ Layer: Feature
- 🔒 Invariant: Modal blocks migration progress until resolved or cancelled. - 🔒 Invariant: Modal blocks migration progress until resolved or cancelled.
- 📥 Props: show: boolean , sourceDbName: string , sourceDbUuid: string
- ⚡ Events: cancel, resolve - ⚡ Events: cancel, resolve
- ➡️ WRITES_TO `props`
- ƒ **resolve** (`Function`) - ƒ **resolve** (`Function`)
- 📝 Dispatches the resolution event with the selected mapping. - 📝 Dispatches the resolution event with the selected mapping.
- ƒ **cancel** (`Function`) - ƒ **cancel** (`Function`)
@@ -742,10 +795,10 @@
- 📝 Displays a grid of dashboards with selection and pagination. - 📝 Displays a grid of dashboards with selection and pagination.
- 🏗️ Layer: Component - 🏗️ Layer: Component
- 🔒 Invariant: Selected IDs must be a subset of available dashboards. - 🔒 Invariant: Selected IDs must be a subset of available dashboards.
- 📥 Props: dashboards: DashboardMetadata[] , selectedIds: number[] , environmentId: string
- ⚡ Events: selectionChanged - ⚡ Events: selectionChanged
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `derived`
- ➡️ WRITES_TO `t` - ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t`
- ƒ **handleValidate** (`Function`) - ƒ **handleValidate** (`Function`)
- 📝 Triggers dashboard validation task. - 📝 Triggers dashboard validation task.
- ƒ **handleSort** (`Function`) - ƒ **handleSort** (`Function`)
@@ -816,8 +869,8 @@
- 🧩 **TaskList** (`Component`) - 🧩 **TaskList** (`Component`)
- 📝 Displays a list of tasks with their status and execution details. - 📝 Displays a list of tasks with their status and execution details.
- 🏗️ Layer: Component - 🏗️ Layer: Component
- 📥 Props: tasks: Array<any> , loading: boolean
- ⚡ Events: select - ⚡ Events: select
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t` - ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t` - ⬅️ READS_FROM `t`
- ƒ **getStatusColor** (`Function`) - ƒ **getStatusColor** (`Function`)
@@ -829,8 +882,8 @@
- 🧩 **DynamicForm** (`Component`) - 🧩 **DynamicForm** (`Component`)
- 📝 Generates a form dynamically based on a JSON schema. - 📝 Generates a form dynamically based on a JSON schema.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 📥 Props: schema: any
- ⚡ Events: submit - ⚡ Events: submit
- ➡️ WRITES_TO `props`
- ƒ **handleSubmit** (`Function`) - ƒ **handleSubmit** (`Function`)
- 📝 Dispatches the submit event with the form data. - 📝 Dispatches the submit event with the form data.
- ƒ **initializeForm** (`Function`) - ƒ **initializeForm** (`Function`)
@@ -839,8 +892,8 @@
- 📝 Provides a UI component for selecting source and target environments. - 📝 Provides a UI component for selecting source and target environments.
- 🏗️ Layer: Feature - 🏗️ Layer: Feature
- 🔒 Invariant: Source and target environments must be selectable from the list of configured environments. - 🔒 Invariant: Source and target environments must be selectable from the list of configured environments.
- 📥 Props: label: string , selectedId: string
- ⚡ Events: change - ⚡ Events: change
- ➡️ WRITES_TO `props`
- ƒ **handleSelect** (`Function`) - ƒ **handleSelect** (`Function`)
- 📝 Dispatches the selection change event. - 📝 Dispatches the selection change event.
- 🧩 **ProtectedRoute** (`Component`) `[TRIVIAL]` - 🧩 **ProtectedRoute** (`Component`) `[TRIVIAL]`
@@ -850,11 +903,13 @@
- ⬅️ READS_FROM `app` - ⬅️ READS_FROM `app`
- ⬅️ READS_FROM `auth` - ⬅️ READS_FROM `auth`
- 🧩 **TaskLogPanel** (`Component`) - 🧩 **TaskLogPanel** (`Component`)
- 📝 Component properties and state. - 📝 Combines log filtering and display into a single cohesive dark-themed panel.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 🔒 Invariant: Must always display logs in chronological order and respect auto-scroll preference. - 🔒 Invariant: Must always display logs in chronological order and respect auto-scroll preference.
- 📥 Props: logs: any, autoScroll: any
- ⚡ Events: filterChange - ⚡ Events: filterChange
- ➡️ WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `state`
- 📦 **TaskLogPanel** (`Module`) `[TRIVIAL]` - 📦 **TaskLogPanel** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/components/tasks/TaskLogPanel.svelte - 📝 Auto-generated module for frontend/src/components/tasks/TaskLogPanel.svelte
- 🏗️ Layer: Unknown - 🏗️ Layer: Unknown
@@ -869,7 +924,9 @@
- 🧩 **LogFilterBar** (`Component`) - 🧩 **LogFilterBar** (`Component`)
- 📝 Compact filter toolbar for logs — level, source, and text search in a single dense row. - 📝 Compact filter toolbar for logs — level, source, and text search in a single dense row.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 📥 Props: availableSources: any, selectedLevel: any, selectedSource: any, searchText: any - ➡️ WRITES_TO `bindable`
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `derived`
- 📦 **LogFilterBar** (`Module`) `[TRIVIAL]` - 📦 **LogFilterBar** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/components/tasks/LogFilterBar.svelte - 📝 Auto-generated module for frontend/src/components/tasks/LogFilterBar.svelte
- 🏗️ Layer: Unknown - 🏗️ Layer: Unknown
@@ -884,21 +941,15 @@
- 🧩 **LogEntryRow** (`Component`) - 🧩 **LogEntryRow** (`Component`)
- 📝 Renders a single log entry with stacked layout optimized for narrow drawer panels. - 📝 Renders a single log entry with stacked layout optimized for narrow drawer panels.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 📥 Props: log: any, showSource: any - ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `derived`
- ƒ **formatTime** (`Function`) - ƒ **formatTime** (`Function`)
- 📝 Format ISO timestamp to HH:MM:SS */ - 📝 Format ISO timestamp to HH:MM:SS */
- 📦 **LogEntryRow** (`Module`) `[TRIVIAL]`
- 📝 Auto-generated module for frontend/src/components/tasks/LogEntryRow.svelte
- 🏗️ Layer: Unknown
- ƒ **getLevelClass** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- ƒ **getSourceClass** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 🧩 **FileList** (`Component`) - 🧩 **FileList** (`Component`)
- 📝 Displays a table of files with metadata and actions. - 📝 Displays a table of files with metadata and actions.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 📥 Props: files: any
- ⚡ Events: delete, navigate - ⚡ Events: delete, navigate
- ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t` - ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t` - ⬅️ READS_FROM `t`
- ƒ **isDirectory** (`Function`) - ƒ **isDirectory** (`Function`)
@@ -911,6 +962,7 @@
- 📝 Provides a form for uploading files to a specific category. - 📝 Provides a form for uploading files to a specific category.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- ⚡ Events: uploaded - ⚡ Events: uploaded
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `t` - ⬅️ READS_FROM `t`
- ➡️ WRITES_TO `t` - ➡️ WRITES_TO `t`
- ƒ **handleUpload** (`Function`) - ƒ **handleUpload** (`Function`)
@@ -964,7 +1016,7 @@
- 🧩 **CommitHistory** (`Component`) - 🧩 **CommitHistory** (`Component`)
- 📝 Displays the commit history for a specific dashboard. - 📝 Displays the commit history for a specific dashboard.
- 🏗️ Layer: Component - 🏗️ Layer: Component
- 📥 Props: dashboardId: any - ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `t` - ⬅️ READS_FROM `t`
- ➡️ WRITES_TO `t` - ➡️ WRITES_TO `t`
- ƒ **onMount** (`Function`) - ƒ **onMount** (`Function`)
@@ -975,8 +1027,9 @@
- 📝 Modal for deploying a dashboard to a target environment. - 📝 Modal for deploying a dashboard to a target environment.
- 🏗️ Layer: Component - 🏗️ Layer: Component
- 🔒 Invariant: Cannot deploy without a selected environment. - 🔒 Invariant: Cannot deploy without a selected environment.
- 📥 Props: dashboardId: any, show: any
- ⚡ Events: deploy - ⚡ Events: deploy
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `effect`
- 📦 **loadStatus** (`Watcher`) - 📦 **loadStatus** (`Watcher`)
- ƒ **loadEnvironments** (`Function`) - ƒ **loadEnvironments** (`Function`)
- 📝 Fetch available environments from API. - 📝 Fetch available environments from API.
@@ -986,8 +1039,8 @@
- 📝 UI for resolving merge conflicts (Keep Mine / Keep Theirs). - 📝 UI for resolving merge conflicts (Keep Mine / Keep Theirs).
- 🏗️ Layer: Component - 🏗️ Layer: Component
- 🔒 Invariant: User must resolve all conflicts before saving. - 🔒 Invariant: User must resolve all conflicts before saving.
- 📥 Props: conflicts: any, show: any
- ⚡ Events: resolve - ⚡ Events: resolve
- ➡️ WRITES_TO `props`
- ƒ **resolve** (`Function`) - ƒ **resolve** (`Function`)
- 📝 Set resolution strategy for a file. - 📝 Set resolution strategy for a file.
- ƒ **handleSave** (`Function`) - ƒ **handleSave** (`Function`)
@@ -995,8 +1048,9 @@
- 🧩 **CommitModal** (`Component`) - 🧩 **CommitModal** (`Component`)
- 📝 Модальное окно для создания коммита с просмотром изменений (diff). - 📝 Модальное окно для создания коммита с просмотром изменений (diff).
- 🏗️ Layer: Component - 🏗️ Layer: Component
- 📥 Props: dashboardId: any, show: any
- ⚡ Events: commit - ⚡ Events: commit
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `effect`
- ƒ **handleGenerateMessage** (`Function`) - ƒ **handleGenerateMessage** (`Function`)
- 📝 Generates a commit message using LLM. - 📝 Generates a commit message using LLM.
- ƒ **loadStatus** (`Function`) - ƒ **loadStatus** (`Function`)
@@ -1006,8 +1060,8 @@
- 🧩 **BranchSelector** (`Component`) - 🧩 **BranchSelector** (`Component`)
- 📝 UI для выбора и создания веток Git. - 📝 UI для выбора и создания веток Git.
- 🏗️ Layer: Component - 🏗️ Layer: Component
- 📥 Props: dashboardId: any, currentBranch: any
- ⚡ Events: change - ⚡ Events: change
- ➡️ WRITES_TO `props`
- ⬅️ READS_FROM `t` - ⬅️ READS_FROM `t`
- ƒ **onMount** (`Function`) - ƒ **onMount** (`Function`)
- 📝 Load branches when component is mounted. - 📝 Load branches when component is mounted.
@@ -1022,7 +1076,7 @@
- 🧩 **GitManager** (`Component`) - 🧩 **GitManager** (`Component`)
- 📝 Центральный компонент для управления Git-операциями конкретного дашборда. - 📝 Центральный компонент для управления Git-операциями конкретного дашборда.
- 🏗️ Layer: Component - 🏗️ Layer: Component
- 📥 Props: dashboardId: any, dashboardTitle: any, show: any - ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t` - ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t` - ⬅️ READS_FROM `t`
- ƒ **checkStatus** (`Function`) - ƒ **checkStatus** (`Function`)
@@ -1038,7 +1092,7 @@
- 🧩 **DocPreview** (`Component`) - 🧩 **DocPreview** (`Component`)
- 📝 UI component for previewing generated dataset documentation before saving. - 📝 UI component for previewing generated dataset documentation before saving.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 📥 Props: documentation: any, onSave: any, onCancel: any - ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t` - ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t` - ⬅️ READS_FROM `t`
- 📦 **DocPreview** (`Module`) `[TRIVIAL]` - 📦 **DocPreview** (`Module`) `[TRIVIAL]`
@@ -1049,7 +1103,7 @@
- 🧩 **ProviderConfig** (`Component`) - 🧩 **ProviderConfig** (`Component`)
- 📝 UI form for managing LLM provider configurations. - 📝 UI form for managing LLM provider configurations.
- 🏗️ Layer: UI - 🏗️ Layer: UI
- 📥 Props: providers: any, onSave: any - ➡️ WRITES_TO `props`
- ➡️ WRITES_TO `t` - ➡️ WRITES_TO `t`
- ⬅️ READS_FROM `t` - ⬅️ READS_FROM `t`
- 📦 **ProviderConfig** (`Module`) `[TRIVIAL]` - 📦 **ProviderConfig** (`Module`) `[TRIVIAL]`
@@ -1099,14 +1153,14 @@
- 📝 Provides a WebSocket endpoint for real-time log streaming of a task with server-side filtering. - 📝 Provides a WebSocket endpoint for real-time log streaming of a task with server-side filtering.
- 📦 **StaticFiles** (`Mount`) - 📦 **StaticFiles** (`Mount`)
- 📝 Mounts the frontend build directory to serve static assets. - 📝 Mounts the frontend build directory to serve static assets.
- ƒ **serve_spa** (`Function`)
- 📝 Serves the SPA frontend for any path not matched by API routes.
- ƒ **read_root** (`Function`) - ƒ **read_root** (`Function`)
- 📝 A simple root endpoint to confirm that the API is running when frontend is missing. - 📝 A simple root endpoint to confirm that the API is running when frontend is missing.
- ƒ **network_error_handler** (`Function`) `[TRIVIAL]` - ƒ **network_error_handler** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan) - 📝 Auto-detected function (orphan)
- ƒ **matches_filters** (`Function`) `[TRIVIAL]` - ƒ **matches_filters** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan) - 📝 Auto-detected function (orphan)
- ƒ **serve_spa** (`Function`) `[TRIVIAL]`
- 📝 Auto-detected function (orphan)
- 📦 **Dependencies** (`Module`) - 📦 **Dependencies** (`Module`)
- 📝 Manages creation and provision of shared application dependencies, such as PluginLoader and TaskManager, to avoid circular imports. - 📝 Manages creation and provision of shared application dependencies, such as PluginLoader and TaskManager, to avoid circular imports.
- 🏗️ Layer: Core - 🏗️ Layer: Core

View File

@@ -1,7 +1,7 @@
# [DEF:Project_Knowledge_Map:Root] # [DEF:Project_Knowledge_Map:Root]
# @TIER: CRITICAL # @TIER: CRITICAL
# @PURPOSE: Global navigation map for AI-Agent (GRACE Knowledge Graph). # @PURPOSE: Global navigation map for AI-Agent (GRACE Knowledge Graph).
# @LAST_UPDATE: 2026-02-19 # @LAST_UPDATE: 2026-02-20
## 1. SYSTEM STANDARDS (Rules of the Game) ## 1. SYSTEM STANDARDS (Rules of the Game)
Strict policies and formatting rules. Strict policies and formatting rules.
@@ -26,6 +26,8 @@ Use these for code generation (Style Transfer).
* Ref: `.ai/shots/frontend_component.svelte` -> `[DEF:Shot:Svelte_Component]` * Ref: `.ai/shots/frontend_component.svelte` -> `[DEF:Shot:Svelte_Component]`
* **Plugin Module:** Reference implementation of a task plugin. * **Plugin Module:** Reference implementation of a task plugin.
* Ref: `.ai/shots/plugin_example.py` -> `[DEF:Shot:Plugin_Example]` * Ref: `.ai/shots/plugin_example.py` -> `[DEF:Shot:Plugin_Example]`
* **Critical Module:** Core banking transaction processor with ACID guarantees.
* Ref: `.ai/shots/critical_module.py` -> `[DEF:Shot:Critical_Module]`
## 3. DOMAIN MAP (Modules) ## 3. DOMAIN MAP (Modules)
* **Project Map:** `.ai/PROJECT_MAP.md` -> `[DEF:Project_Map]` * **Project Map:** `.ai/PROJECT_MAP.md` -> `[DEF:Project_Map]`

View File

@@ -1,14 +1,18 @@
# [DEF:Shot:FastAPI_Route:Example] # [DEF:BackendRouteShot:Module]
# @TIER: STANDARD
# @SEMANTICS: Route, Task, API, Async
# @PURPOSE: Reference implementation of a task-based route using GRACE-Poly. # @PURPOSE: Reference implementation of a task-based route using GRACE-Poly.
# @LAYER: Interface (API)
# @RELATION: IMPLEMENTS -> [DEF:Std:API_FastAPI] # @RELATION: IMPLEMENTS -> [DEF:Std:API_FastAPI]
# @INVARIANT: TaskManager must be available in dependency graph.
from typing import List, Dict, Any, Optional from typing import Dict, Any
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel from pydantic import BaseModel
from ...core.logger import belief_scope from ...core.logger import belief_scope
from ...core.task_manager import TaskManager, Task from ...core.task_manager import TaskManager, Task
from ...core.config_manager import ConfigManager from ...core.config_manager import ConfigManager
from ...dependencies import get_task_manager, get_config_manager, has_permission, get_current_user from ...dependencies import get_task_manager, get_config_manager, get_current_user
router = APIRouter() router = APIRouter()
@@ -21,37 +25,41 @@ class CreateTaskRequest(BaseModel):
# @PURPOSE: Create and start a new task using TaskManager. Non-blocking. # @PURPOSE: Create and start a new task using TaskManager. Non-blocking.
# @PARAM: request (CreateTaskRequest) - Plugin and params. # @PARAM: request (CreateTaskRequest) - Plugin and params.
# @PARAM: task_manager (TaskManager) - Async task executor. # @PARAM: task_manager (TaskManager) - Async task executor.
# @PARAM: config (ConfigManager) - Centralized configuration. # @PRE: plugin_id must match a registered plugin.
# @PRE: plugin_id must exist; config must be initialized.
# @POST: A new task is spawned; Task ID returned immediately. # @POST: A new task is spawned; Task ID returned immediately.
# @SIDE_EFFECT: Writes to DB, Trigger background worker.
async def create_task( async def create_task(
request: CreateTaskRequest, request: CreateTaskRequest,
task_manager: TaskManager = Depends(get_task_manager), task_manager: TaskManager = Depends(get_task_manager),
config: ConfigManager = Depends(get_config_manager), config: ConfigManager = Depends(get_config_manager),
current_user = Depends(get_current_user) current_user = Depends(get_current_user)
): ):
# RBAC: Dynamic permission check # Context Logging
has_permission(f"plugin:{request.plugin_id}", "EXECUTE")(current_user)
with belief_scope("create_task"): with belief_scope("create_task"):
try: try:
# 1. Action: Resolve setting using ConfigManager (Example) # 1. Action: Configuration Resolution
timeout = config.get("TASKS_DEFAULT_TIMEOUT", 3600) timeout = config.get("TASKS_DEFAULT_TIMEOUT", 3600)
# 2. Action: Spawn async task via TaskManager # 2. Action: Spawn async task
# @RELATION: CALLS -> task_manager.create_task # @RELATION: CALLS -> task_manager.create_task
task = await task_manager.create_task( task = await task_manager.create_task(
plugin_id=request.plugin_id, plugin_id=request.plugin_id,
params={**request.params, "timeout": timeout} params={**request.params, "timeout": timeout}
) )
return task return task
except ValueError as e:
# 3. Recovery: Domain logic error mapping
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e)
)
except Exception as e: except Exception as e:
# Evaluation: Proper error mapping and logging # @UX_STATE: Error feedback -> 500 Internal Error
# @UX_STATE: Error feedback to frontend
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Task creation failed: {str(e)}" detail="Internal Task Spawning Error"
) )
# [/DEF:create_task:Function] # [/DEF:create_task:Function]
# [/DEF:Shot:FastAPI_Route] # [/DEF:BackendRouteShot:Module]

View File

@@ -0,0 +1,79 @@
# [DEF:TransactionCore:Module]
# @TIER: CRITICAL
# @SEMANTICS: Finance, ACID, Transfer, Ledger
# @PURPOSE: Core banking transaction processor with ACID guarantees.
# @LAYER: Domain (Core)
# @RELATION: DEPENDS_ON -> [DEF:Infra:PostgresDB]
# @RELATION: DEPENDS_ON -> [DEF:Infra:AuditLog]
# @INVARIANT: Total system balance must remain constant (Double-Entry Bookkeeping).
# @INVARIANT: Negative transfers are strictly forbidden.
# @TEST_DATA: sufficient_funds -> {"from": "acc_A", "to": "acc_B", "amt": 100.00}
# @TEST_DATA: insufficient_funds -> {"from": "acc_empty", "to": "acc_B", "amt": 1000.00}
# @TEST_DATA: concurrency_lock -> {./fixtures/transactions.json#race_condition}
from decimal import Decimal
from typing import NamedTuple
from ...core.logger import belief_scope
from ...core.db import atomic_transaction, get_balance, update_balance
from ...core.exceptions import BusinessRuleViolation
class TransferResult(NamedTuple):
tx_id: str
status: str
new_balance: Decimal
# [DEF:execute_transfer:Function]
# @PURPOSE: Atomically move funds between accounts with audit trails.
# @PARAM: sender_id (str) - Source account.
# @PARAM: receiver_id (str) - Destination account.
# @PARAM: amount (Decimal) - Positive amount to transfer.
# @PRE: amount > 0; sender != receiver; sender_balance >= amount.
# @POST: sender_balance -= amount; receiver_balance += amount; Audit Record Created.
# @SIDE_EFFECT: Database mutation (Rows locked), Audit IO.
#
# @UX_STATE: Success -> Returns 200 OK + Transaction Receipt.
# @UX_STATE: Error(LowBalance) -> 422 Unprocessable -> UI shows "Top-up needed" modal.
# @UX_STATE: Error(System) -> 500 Internal -> UI shows "Retry later" toast.
def execute_transfer(sender_id: str, receiver_id: str, amount: Decimal) -> TransferResult:
# Guard: Input Validation
if amount <= Decimal("0.00"):
raise BusinessRuleViolation("Transfer amount must be positive.")
if sender_id == receiver_id:
raise BusinessRuleViolation("Cannot transfer to self.")
with belief_scope("execute_transfer") as context:
context.logger.info("Initiating transfer", data={"from": sender_id, "to": receiver_id})
try:
# 1. Action: Atomic DB Transaction
# @RELATION: CALLS -> atomic_transaction
with atomic_transaction():
# Guard: State Validation (Strict)
current_balance = get_balance(sender_id, for_update=True)
if current_balance < amount:
# @UX_FEEDBACK: Triggers specific UI flow for insufficient funds
context.logger.warn("Insufficient funds", data={"balance": current_balance})
raise BusinessRuleViolation("INSUFFICIENT_FUNDS")
# 2. Action: Mutation
new_src_bal = update_balance(sender_id, -amount)
new_dst_bal = update_balance(receiver_id, +amount)
# 3. Action: Audit
tx_id = context.audit.log_transfer(sender_id, receiver_id, amount)
context.logger.info("Transfer committed", data={"tx_id": tx_id})
return TransferResult(tx_id, "COMPLETED", new_src_bal)
except BusinessRuleViolation as e:
# Logic: Explicit re-raise for UI mapping
raise e
except Exception as e:
# Logic: Catch-all safety net
context.logger.error("Critical Transfer Failure", error=e)
raise RuntimeError("TRANSACTION_ABORTED") from e
# [/DEF:execute_transfer:Function]
# [/DEF:TransactionCore:Module]

View File

@@ -1,19 +1,23 @@
<!-- [DEF:Shot:Svelte_Component:Example] --> <!-- [DEF:FrontendComponentShot:Component] -->
# @PURPOSE: Reference implementation of a task-spawning component using Constitution rules.
# @RELATION: IMPLEMENTS -> [DEF:Std:UI_Svelte]
<script> <script>
/** /**
* @TIER: STANDARD * @TIER: CRITICAL
* @PURPOSE: Action button to spawn a new task. * @SEMANTICS: Task, Button, Action, UX
* @LAYER: UI * @PURPOSE: Action button to spawn a new task with full UX feedback cycle.
* @SEMANTICS: Task, Creation, Button * @LAYER: UI (Presentation)
* @RELATION: CALLS -> postApi * @RELATION: CALLS -> postApi
* @INVARIANT: Must prevent double-submission while loading.
* *
* @UX_STATE: Idle -> Button enabled with primary color. * @TEST_DATA: idle_state -> {"isLoading": false}
* @UX_STATE: Loading -> Button disabled with spinner while postApi resolves. * @TEST_DATA: loading_state -> {"isLoading": true}
* @UX_FEEDBACK: toast.success on completion; toast.error on failure. *
* @UX_TEST: Idle -> {click: spawnTask, expected: loading state then success} * @UX_STATE: Idle -> Button enabled, primary color.
* @UX_STATE: Loading -> Button disabled, spinner visible.
* @UX_STATE: Error -> Toast notification triggers.
*
* @UX_FEEDBACK: Toast success/error.
* @UX_TEST: Idle -> {click: spawnTask, expected: isLoading=true}
* @UX_TEST: Success -> {api_resolve: 200, expected: toast.success called}
*/ */
import { postApi } from "$lib/api.js"; import { postApi } from "$lib/api.js";
import { t } from "$lib/i18n"; import { t } from "$lib/i18n";
@@ -24,40 +28,43 @@
let isLoading = false; let isLoading = false;
async def spawnTask() { // [DEF:spawnTask:Function]
async function spawnTask() {
isLoading = true; isLoading = true;
console.log("[FrontendComponentShot][Loading] Spawning task...");
try { try {
// 1. Action: Constitution Rule - MUST use postApi wrapper // 1. Action: API Call
const response = await postApi("/api/tasks", { const response = await postApi("/api/tasks", {
plugin_id, plugin_id,
params params
}); });
// 2. Feedback: UX state management // 2. Feedback: Success
if (response.task_id) { if (response.task_id) {
console.log("[FrontendComponentShot][Success] Task created.");
toast.success($t.tasks.spawned_success); toast.success($t.tasks.spawned_success);
} }
} catch (error) { } catch (error) {
// 3. Recovery: Evaluation & UI reporting // 3. Recovery: User notification
console.log("[FrontendComponentShot][Error] Failed:", error);
toast.error(`${$t.errors.task_failed}: ${error.message}`); toast.error(`${$t.errors.task_failed}: ${error.message}`);
} finally { } finally {
isLoading = false; isLoading = false;
} }
} }
// [/DEF:spawnTask:Function]
</script> </script>
<button <button
on:click={spawnTask} on:click={spawnTask}
disabled={isLoading} disabled={isLoading}
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg flex items-center gap-2" class="btn-primary flex items-center gap-2"
aria-busy={isLoading}
> >
{#if isLoading} {#if isLoading}
<span class="animate-spin text-sm">🌀</span> <span class="animate-spin" aria-label="Loading">🌀</span>
{/if} {/if}
<span>{$t.actions.start_task}</span> <span>{$t.actions.start_task}</span>
</button> </button>
<!-- [/DEF:FrontendComponentShot:Component] -->
<style>
/* Local styles minimized as per Constitution Rule III */
</style>
<!-- [/DEF:Shot:Svelte_Component] -->

View File

@@ -1,6 +1,10 @@
# [DEF:Shot:Plugin_Example:Example] # [DEF:PluginExampleShot:Module]
# @TIER: STANDARD
# @SEMANTICS: Plugin, Core, Extension
# @PURPOSE: Reference implementation of a plugin following GRACE standards. # @PURPOSE: Reference implementation of a plugin following GRACE standards.
# @RELATION: IMPLEMENTS -> [DEF:Std:Plugin] # @LAYER: Domain (Business Logic)
# @RELATION: INHERITS -> PluginBase
# @INVARIANT: get_schema must return valid JSON Schema.
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from ..core.plugin_base import PluginBase from ..core.plugin_base import PluginBase
@@ -11,28 +15,15 @@ class ExamplePlugin(PluginBase):
def id(self) -> str: def id(self) -> str:
return "example-plugin" return "example-plugin"
@property
def name(self) -> str:
return "Example Plugin"
@property
def description(self) -> str:
return "A simple plugin that demonstrates structured logging and progress tracking."
@property
def version(self) -> str:
return "1.0.0"
# [DEF:get_schema:Function] # [DEF:get_schema:Function]
# @PURPOSE: Defines input validation schema. # @PURPOSE: Defines input validation schema.
# @POST: Returns dict compliant with JSON Schema draft 7.
def get_schema(self) -> Dict[str, Any]: def get_schema(self) -> Dict[str, Any]:
return { return {
"type": "object", "type": "object",
"properties": { "properties": {
"message": { "message": {
"type": "string", "type": "string",
"title": "Message",
"description": "A message to log.",
"default": "Hello, GRACE!", "default": "Hello, GRACE!",
} }
}, },
@@ -41,27 +32,33 @@ class ExamplePlugin(PluginBase):
# [/DEF:get_schema:Function] # [/DEF:get_schema:Function]
# [DEF:execute:Function] # [DEF:execute:Function]
# @PURPOSE: Core plugin logic with structured logging and progress reporting. # @PURPOSE: Core plugin logic with structured logging and scope isolation.
# @PARAM: params (Dict) - Validated input parameters. # @PARAM: params (Dict) - Validated input parameters.
# @PARAM: context (TaskContext) - Execution context with logging and progress tools. # @PARAM: context (TaskContext) - Execution tools (log, progress).
async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None): # @SIDE_EFFECT: Emits logs to centralized system.
message = params["message"] async def execute(self, params: Dict, context: Optional = None):
message = params
# 1. Action: Structured Logging with Source Attribution # 1. Action: System-level tracing (Rule VI)
if context: with belief_scope("example_plugin_exec") as b_scope:
log = context.logger.with_source("example_plugin") if context:
log.info(f"Starting execution with message: {message}") # Task Logs: Пишем в пользовательский контекст выполнения задачи
# @RELATION: BINDS_TO -> context.logger
# 2. Action: Progress Reporting log = context.logger.with_source("example_plugin")
log.progress("Processing step 1...", percent=25)
# Simulating some async work... b_scope.logger.info("Using provided TaskContext") # System log
# await some_async_op() log.info("Starting execution", data={"msg": message}) # Task log
log.progress("Processing step 2...", percent=75) # 2. Action: Progress Reporting
log.info("Execution completed successfully.") log.progress("Processing...", percent=50)
else:
# Fallback for manual/standalone execution # 3. Action: Finalize
print(f"Standalone execution: {message}") log.info("Execution completed.")
else:
# Standalone Fallback: Замыкаемся на системный scope
b_scope.logger.warning("No TaskContext provided. Running standalone.")
b_scope.logger.info("Standalone execution", data={"msg": message})
print(f"Standalone: {message}")
# [/DEF:execute:Function] # [/DEF:execute:Function]
# [/DEF:Shot:Plugin_Example] # [/DEF:PluginExampleShot:Module]

Binary file not shown.

View File

@@ -241,6 +241,10 @@ frontend_path = project_root / "frontend" / "build"
if frontend_path.exists(): if frontend_path.exists():
app.mount("/_app", StaticFiles(directory=str(frontend_path / "_app")), name="static") app.mount("/_app", StaticFiles(directory=str(frontend_path / "_app")), name="static")
# [DEF:serve_spa:Function]
# @PURPOSE: Serves the SPA frontend for any path not matched by API routes.
# @PRE: frontend_path exists.
# @POST: Returns the requested file or index.html.
@app.get("/{file_path:path}", include_in_schema=False) @app.get("/{file_path:path}", include_in_schema=False)
async def serve_spa(file_path: str): async def serve_spa(file_path: str):
# Only serve SPA for non-API paths # Only serve SPA for non-API paths

View File

@@ -18,3 +18,4 @@ def __getattr__(name):
from .resource_service import ResourceService from .resource_service import ResourceService
return ResourceService return ResourceService
raise AttributeError(f"module {__name__!r} has no attribute {name!r}") raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
# [/DEF:backend.src.services:Module]

Binary file not shown.

View File

@@ -13,8 +13,8 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
let passwords = {}; let passwords = $state({});
let submitting = false; let submitting = $state(false);
// [DEF:handleSubmit:Function] // [DEF:handleSubmit:Function]
// @PURPOSE: Validates and dispatches the passwords to resume the task. // @PURPOSE: Validates and dispatches the passwords to resume the task.
@@ -69,7 +69,7 @@
<div <div
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true" aria-hidden="true"
on:click={handleCancel} onclick={handleCancel}
></div> ></div>
<span <span
@@ -126,7 +126,7 @@
{/if} {/if}
<form <form
on:submit|preventDefault={handleSubmit} onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}
class="space-y-4" class="space-y-4"
> >
{#each databases as dbName} {#each databases as dbName}
@@ -158,7 +158,7 @@
<button <button
type="button" type="button"
class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm disabled:opacity-50"
on:click={handleSubmit} onclick={handleSubmit}
disabled={submitting} disabled={submitting}
> >
{submitting ? "Resuming..." : "Resume Migration"} {submitting ? "Resuming..." : "Resume Migration"}
@@ -166,7 +166,7 @@
<button <button
type="button" type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"
on:click={handleCancel} onclick={handleCancel}
disabled={submitting} disabled={submitting}
> >
Cancel Cancel

View File

@@ -123,7 +123,7 @@
<span>{error}</span> <span>{error}</span>
<button <button
class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded-md px-3 py-1 text-xs cursor-pointer transition-all hover:bg-terminal-border hover:text-terminal-text-bright" class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded-md px-3 py-1 text-xs cursor-pointer transition-all hover:bg-terminal-border hover:text-terminal-text-bright"
on:click={handleRefresh}>Retry</button onclick={handleRefresh}>Retry</button
> >
</div> </div>
{:else} {:else}
@@ -149,11 +149,11 @@
<div <div
class="fixed inset-0 bg-gray-500/75 transition-opacity" class="fixed inset-0 bg-gray-500/75 transition-opacity"
aria-hidden="true" aria-hidden="true"
on:click={() => { onclick={() => {
show = false; show = false;
dispatch("close"); dispatch("close");
}} }}
on:keydown={(e) => e.key === "Escape" && (show = false)} onkeydown={(e) => e.key === "Escape" && (show = false)}
role="presentation" role="presentation"
></div> ></div>
@@ -170,7 +170,7 @@
</h3> </h3>
<button <button
class="text-gray-500 hover:text-gray-300" class="text-gray-500 hover:text-gray-300"
on:click={() => { onclick={() => {
show = false; show = false;
dispatch("close"); dispatch("close");
}} }}

View File

@@ -7,67 +7,74 @@
--> -->
<script> <script>
import { onMount } from 'svelte'; import { onMount } from "svelte";
import { t } from '../../lib/i18n'; import { t } from "../../lib/i18n";
import { requestApi } from '../../lib/api'; import { requestApi } from "../../lib/api";
/** @type {Array} */ /** @type {Array} */
let { let { providers = [], onSave = () => {} } = $props();
provider,
config = {},
} = $props();
let editingProvider = null; let editingProvider = null;
let showForm = false; let showForm = false;
let formData = { let formData = {
name: '', name: "",
provider_type: 'openai', provider_type: "openai",
base_url: 'https://api.openai.com/v1', base_url: "https://api.openai.com/v1",
api_key: '', api_key: "",
default_model: 'gpt-4o', default_model: "gpt-4o",
is_active: true is_active: true,
}; };
let testStatus = { type: '', message: '' }; let testStatus = { type: "", message: "" };
let isTesting = false; let isTesting = false;
function resetForm() { function resetForm() {
formData = { formData = {
name: '', name: "",
provider_type: 'openai', provider_type: "openai",
base_url: 'https://api.openai.com/v1', base_url: "https://api.openai.com/v1",
api_key: '', api_key: "",
default_model: 'gpt-4o', default_model: "gpt-4o",
is_active: true is_active: true,
}; };
editingProvider = null; editingProvider = null;
testStatus = { type: '', message: '' }; testStatus = { type: "", message: "" };
} }
function handleEdit(provider) { function handleEdit(provider) {
editingProvider = provider; editingProvider = provider;
formData = { ...provider, api_key: '' }; // Don't populate key for security formData = { ...provider, api_key: "" }; // Don't populate key for security
showForm = true; showForm = true;
} }
async function testConnection() { async function testConnection() {
console.log("[ProviderConfig][Action] Testing connection", formData); console.log("[ProviderConfig][Action] Testing connection", formData);
isTesting = true; isTesting = true;
testStatus = { type: 'info', message: $t.llm.testing }; testStatus = { type: "info", message: $t.llm.testing };
try { try {
const endpoint = editingProvider ? `/llm/providers/${editingProvider.id}/test` : '/llm/providers/test'; const endpoint = editingProvider
const result = await requestApi(endpoint, 'POST', formData); ? `/llm/providers/${editingProvider.id}/test`
: "/llm/providers/test";
const result = await requestApi(endpoint, "POST", formData);
if (result.success) { if (result.success) {
testStatus = { type: 'success', message: $t.llm.connection_success }; testStatus = { type: "success", message: $t.llm.connection_success };
} else { } else {
testStatus = { type: 'error', message: $t.llm.connection_failed.replace('{error}', result.error || 'Unknown error') }; testStatus = {
type: "error",
message: $t.llm.connection_failed.replace(
"{error}",
result.error || "Unknown error",
),
};
} }
} catch (err) { } catch (err) {
testStatus = { type: 'error', message: $t.llm.connection_failed.replace('{error}', err.message) }; testStatus = {
type: "error",
message: $t.llm.connection_failed.replace("{error}", err.message),
};
} finally { } finally {
isTesting = false; isTesting = false;
} }
@@ -75,8 +82,10 @@
async function handleSubmit() { async function handleSubmit() {
console.log("[ProviderConfig][Action] Submitting provider config"); console.log("[ProviderConfig][Action] Submitting provider config");
const method = editingProvider ? 'PUT' : 'POST'; const method = editingProvider ? "PUT" : "POST";
const endpoint = editingProvider ? `/llm/providers/${editingProvider.id}` : '/llm/providers'; const endpoint = editingProvider
? `/llm/providers/${editingProvider.id}`
: "/llm/providers";
// When editing, only include api_key if user entered a new one // When editing, only include api_key if user entered a new one
const submitData = { ...formData }; const submitData = { ...formData };
@@ -97,9 +106,9 @@
async function toggleActive(provider) { async function toggleActive(provider) {
try { try {
await requestApi(`/llm/providers/${provider.id}`, 'PUT', { await requestApi(`/llm/providers/${provider.id}`, "PUT", {
...provider, ...provider,
is_active: !provider.is_active is_active: !provider.is_active,
}); });
onSave(); onSave();
} catch (err) { } catch (err) {
@@ -111,28 +120,53 @@
<div class="p-4"> <div class="p-4">
<div class="flex justify-between items-center mb-6"> <div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-bold">{$t.llm.providers_title}</h2> <h2 class="text-xl font-bold">{$t.llm.providers_title}</h2>
<button <button
class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition" class="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition"
on:click={() => { resetForm(); showForm = true; }} on:click={() => {
resetForm();
showForm = true;
}}
> >
{$t.llm.add_provider} {$t.llm.add_provider}
</button> </button>
</div> </div>
{#if showForm} {#if showForm}
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"
>
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-md"> <div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-md">
<h3 class="text-lg font-semibold mb-4">{editingProvider ? $t.llm.edit_provider : $t.llm.new_provider}</h3> <h3 class="text-lg font-semibold mb-4">
{editingProvider ? $t.llm.edit_provider : $t.llm.new_provider}
</h3>
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<label for="provider-name" class="block text-sm font-medium text-gray-700">{$t.llm.name}</label> <label
<input id="provider-name" type="text" bind:value={formData.name} class="mt-1 block w-full border rounded-md p-2" placeholder="e.g. My OpenAI" /> for="provider-name"
class="block text-sm font-medium text-gray-700"
>{$t.llm.name}</label
>
<input
id="provider-name"
type="text"
bind:value={formData.name}
class="mt-1 block w-full border rounded-md p-2"
placeholder="e.g. My OpenAI"
/>
</div> </div>
<div> <div>
<label for="provider-type" class="block text-sm font-medium text-gray-700">{$t.llm.type}</label> <label
<select id="provider-type" bind:value={formData.provider_type} class="mt-1 block w-full border rounded-md p-2"> for="provider-type"
class="block text-sm font-medium text-gray-700"
>{$t.llm.type}</label
>
<select
id="provider-type"
bind:value={formData.provider_type}
class="mt-1 block w-full border rounded-md p-2"
>
<option value="openai">OpenAI</option> <option value="openai">OpenAI</option>
<option value="openrouter">OpenRouter</option> <option value="openrouter">OpenRouter</option>
<option value="kilo">Kilo</option> <option value="kilo">Kilo</option>
@@ -140,47 +174,88 @@
</div> </div>
<div> <div>
<label for="provider-base-url" class="block text-sm font-medium text-gray-700">{$t.llm.base_url}</label> <label
<input id="provider-base-url" type="text" bind:value={formData.base_url} class="mt-1 block w-full border rounded-md p-2" /> for="provider-base-url"
class="block text-sm font-medium text-gray-700"
>{$t.llm.base_url}</label
>
<input
id="provider-base-url"
type="text"
bind:value={formData.base_url}
class="mt-1 block w-full border rounded-md p-2"
/>
</div> </div>
<div> <div>
<label for="provider-api-key" class="block text-sm font-medium text-gray-700">{$t.llm.api_key}</label> <label
<input id="provider-api-key" type="password" bind:value={formData.api_key} class="mt-1 block w-full border rounded-md p-2" placeholder={editingProvider ? "••••••••" : "sk-..."} /> for="provider-api-key"
class="block text-sm font-medium text-gray-700"
>{$t.llm.api_key}</label
>
<input
id="provider-api-key"
type="password"
bind:value={formData.api_key}
class="mt-1 block w-full border rounded-md p-2"
placeholder={editingProvider ? "••••••••" : "sk-..."}
/>
</div> </div>
<div> <div>
<label for="provider-default-model" class="block text-sm font-medium text-gray-700">{$t.llm.default_model}</label> <label
<input id="provider-default-model" type="text" bind:value={formData.default_model} class="mt-1 block w-full border rounded-md p-2" placeholder="gpt-4o" /> for="provider-default-model"
class="block text-sm font-medium text-gray-700"
>{$t.llm.default_model}</label
>
<input
id="provider-default-model"
type="text"
bind:value={formData.default_model}
class="mt-1 block w-full border rounded-md p-2"
placeholder="gpt-4o"
/>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<input id="provider-active" type="checkbox" bind:checked={formData.is_active} class="mr-2" /> <input
<label for="provider-active" class="text-sm font-medium text-gray-700">{$t.llm.active}</label> id="provider-active"
type="checkbox"
bind:checked={formData.is_active}
class="mr-2"
/>
<label
for="provider-active"
class="text-sm font-medium text-gray-700">{$t.llm.active}</label
>
</div> </div>
</div> </div>
{#if testStatus.message} {#if testStatus.message}
<div class={`mt-4 p-2 rounded text-sm ${testStatus.type === 'success' ? 'bg-green-100 text-green-800' : testStatus.type === 'error' ? 'bg-red-100 text-red-800' : 'bg-blue-100 text-blue-800'}`}> <div
class={`mt-4 p-2 rounded text-sm ${testStatus.type === "success" ? "bg-green-100 text-green-800" : testStatus.type === "error" ? "bg-red-100 text-red-800" : "bg-blue-100 text-blue-800"}`}
>
{testStatus.message} {testStatus.message}
</div> </div>
{/if} {/if}
<div class="mt-6 flex justify-between gap-2"> <div class="mt-6 flex justify-between gap-2">
<button <button
class="px-4 py-2 border rounded hover:bg-gray-50 flex-1" class="px-4 py-2 border rounded hover:bg-gray-50 flex-1"
on:click={() => { showForm = false; }} on:click={() => {
showForm = false;
}}
> >
{$t.llm.cancel} {$t.llm.cancel}
</button> </button>
<button <button
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex-1" class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex-1"
disabled={isTesting} disabled={isTesting}
on:click={testConnection} on:click={testConnection}
> >
{isTesting ? $t.llm.testing : $t.llm.test} {isTesting ? $t.llm.testing : $t.llm.test}
</button> </button>
<button <button
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex-1" class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex-1"
on:click={handleSubmit} on:click={handleSubmit}
> >
@@ -193,37 +268,45 @@
<div class="grid gap-4"> <div class="grid gap-4">
{#each providers as provider} {#each providers as provider}
<div class="border rounded-lg p-4 flex justify-between items-center bg-white shadow-sm"> <div
class="border rounded-lg p-4 flex justify-between items-center bg-white shadow-sm"
>
<div> <div>
<div class="font-bold flex items-center gap-2"> <div class="font-bold flex items-center gap-2">
{provider.name} {provider.name}
<span class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}> <span
{provider.is_active ? $t.llm.active : 'Inactive'} class={`text-xs px-2 py-0.5 rounded-full ${provider.is_active ? "bg-green-100 text-green-800" : "bg-gray-100 text-gray-800"}`}
>
{provider.is_active ? $t.llm.active : "Inactive"}
</span> </span>
</div> </div>
<div class="text-sm text-gray-500">{provider.provider_type}{provider.default_model}</div> <div class="text-sm text-gray-500">
{provider.provider_type}{provider.default_model}
</div>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<button <button
class="text-sm text-blue-600 hover:underline" class="text-sm text-blue-600 hover:underline"
on:click={() => handleEdit(provider)} on:click={() => handleEdit(provider)}
> >
{$t.common.edit} {$t.common.edit}
</button> </button>
<button <button
class={`text-sm ${provider.is_active ? 'text-orange-600' : 'text-green-600'} hover:underline`} class={`text-sm ${provider.is_active ? "text-orange-600" : "text-green-600"} hover:underline`}
on:click={() => toggleActive(provider)} on:click={() => toggleActive(provider)}
> >
{provider.is_active ? 'Deactivate' : 'Activate'} {provider.is_active ? "Deactivate" : "Activate"}
</button> </button>
</div> </div>
</div> </div>
{:else} {:else}
<div class="text-center py-8 text-gray-500 border-2 border-dashed rounded-lg"> <div
class="text-center py-8 text-gray-500 border-2 border-dashed rounded-lg"
>
{$t.llm.no_providers} {$t.llm.no_providers}
</div> </div>
{/each} {/each}
</div> </div>
</div> </div>
<!-- [/DEF:ProviderConfig:Component] --> <!-- [/DEF:ProviderConfig:Component] -->

View File

@@ -74,7 +74,7 @@
class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded px-2 py-[0.3125rem] text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:border-primary-ring" class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded px-2 py-[0.3125rem] text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:border-primary-ring"
style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E&quot;); background-repeat: no-repeat; background-position: right 0.375rem center;" style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E&quot;); background-repeat: no-repeat; background-position: right 0.375rem center;"
value={selectedLevel} value={selectedLevel}
on:change={handleLevelChange} onchange={handleLevelChange}
aria-label="Filter by level" aria-label="Filter by level"
> >
{#each levelOptions as option} {#each levelOptions as option}
@@ -86,7 +86,7 @@
class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded px-2 py-[0.3125rem] text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:border-primary-ring" class="bg-terminal-surface text-terminal-text-subtle border border-terminal-border rounded px-2 py-[0.3125rem] text-xs cursor-pointer shrink-0 appearance-none pr-6 focus:outline-none focus:border-primary-ring"
style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E&quot;); background-repeat: no-repeat; background-position: right 0.375rem center;" style="background-image: url(&quot;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E&quot;); background-repeat: no-repeat; background-position: right 0.375rem center;"
value={selectedSource} value={selectedSource}
on:change={handleSourceChange} onchange={handleSourceChange}
aria-label="Filter by source" aria-label="Filter by source"
> >
<option value="">All Sources</option> <option value="">All Sources</option>
@@ -114,7 +114,7 @@
class="w-full bg-terminal-surface text-terminal-text-bright border border-terminal-border rounded py-[0.3125rem] px-2 pl-7 text-xs placeholder:text-terminal-text-muted focus:outline-none focus:border-primary-ring" class="w-full bg-terminal-surface text-terminal-text-bright border border-terminal-border rounded py-[0.3125rem] px-2 pl-7 text-xs placeholder:text-terminal-text-muted focus:outline-none focus:border-primary-ring"
placeholder="Search..." placeholder="Search..."
value={searchText} value={searchText}
on:input={handleSearchChange} oninput={handleSearchChange}
aria-label="Search logs" aria-label="Search logs"
/> />
</div> </div>
@@ -123,7 +123,7 @@
{#if hasActiveFilters} {#if hasActiveFilters}
<button <button
class="flex items-center justify-center p-[0.3125rem] bg-transparent border border-terminal-border rounded text-terminal-text-subtle shrink-0 cursor-pointer transition-all hover:text-log-error hover:border-log-error hover:bg-log-error/10" class="flex items-center justify-center p-[0.3125rem] bg-transparent border border-terminal-border rounded text-terminal-text-subtle shrink-0 cursor-pointer transition-all hover:text-log-error hover:border-log-error hover:bg-log-error/10"
on:click={clearFilters} onclick={clearFilters}
aria-label="Clear filters" aria-label="Clear filters"
> >
<svg <svg

View File

@@ -134,7 +134,7 @@
<button <button
class="flex items-center gap-1.5 bg-transparent border-none text-terminal-text-muted text-[0.6875rem] cursor-pointer py-px px-1.5 rounded transition-all hover:bg-terminal-surface hover:text-terminal-text-subtle class="flex items-center gap-1.5 bg-transparent border-none text-terminal-text-muted text-[0.6875rem] cursor-pointer py-px px-1.5 rounded transition-all hover:bg-terminal-surface hover:text-terminal-text-subtle
{autoScroll ? 'text-terminal-accent' : ''}" {autoScroll ? 'text-terminal-accent' : ''}"
on:click={toggleAutoScroll} onclick={toggleAutoScroll}
aria-label="Toggle auto-scroll" aria-label="Toggle auto-scroll"
> >
{#if autoScroll} {#if autoScroll}

View File

@@ -1,10 +1,17 @@
<!-- [DEF:Counter:Component] -->
<!--
@TIER: TRIVIAL
@PURPOSE: Simple counter demo component
@LAYER: UI
-->
<script> <script>
let count = $state(0) let count = $state(0);
const increment = () => { const increment = () => {
count += 1 count += 1;
} };
</script> </script>
<button onclick={increment}> <button onclick={increment}>
count is {count} count is {count}
</button> </button>
<!-- [/DEF:Counter:Component] -->

View File

@@ -21,13 +21,14 @@ describe('SidebarStore', () => {
describe('initial state', () => { describe('initial state', () => {
it('should have default values when no localStorage', () => { it('should have default values when no localStorage', () => {
const state = get(sidebarStore); const state = get(sidebarStore);
expect(state.isExpanded).toBe(true); expect(state.isExpanded).toBe(true);
expect(state.activeCategory).toBe('dashboards'); expect(state.activeCategory).toBe('dashboards');
expect(state.activeItem).toBe('/dashboards'); expect(state.activeItem).toBe('/dashboards');
expect(state.isMobileOpen).toBe(false); expect(state.isMobileOpen).toBe(false);
}); });
}); });
// [/DEF:test_sidebar_initial_state:Function]
// [DEF:test_toggleSidebar:Function] // [DEF:test_toggleSidebar:Function]
// @TEST: toggleSidebar toggles isExpanded state // @TEST: toggleSidebar toggles isExpanded state
@@ -37,9 +38,9 @@ describe('SidebarStore', () => {
it('should toggle isExpanded from true to false', () => { it('should toggle isExpanded from true to false', () => {
const initialState = get(sidebarStore); const initialState = get(sidebarStore);
expect(initialState.isExpanded).toBe(true); expect(initialState.isExpanded).toBe(true);
toggleSidebar(); toggleSidebar();
const newState = get(sidebarStore); const newState = get(sidebarStore);
expect(newState.isExpanded).toBe(false); expect(newState.isExpanded).toBe(false);
}); });
@@ -47,11 +48,12 @@ describe('SidebarStore', () => {
it('should toggle isExpanded from false to true', () => { it('should toggle isExpanded from false to true', () => {
toggleSidebar(); // Now false toggleSidebar(); // Now false
toggleSidebar(); // Should be true again toggleSidebar(); // Should be true again
const state = get(sidebarStore); const state = get(sidebarStore);
expect(state.isExpanded).toBe(true); expect(state.isExpanded).toBe(true);
}); });
}); });
// [/DEF:test_toggleSidebar:Function]
// [DEF:test_setActiveItem:Function] // [DEF:test_setActiveItem:Function]
// @TEST: setActiveItem updates activeCategory and activeItem // @TEST: setActiveItem updates activeCategory and activeItem
@@ -60,7 +62,7 @@ describe('SidebarStore', () => {
describe('setActiveItem', () => { describe('setActiveItem', () => {
it('should update activeCategory and activeItem', () => { it('should update activeCategory and activeItem', () => {
setActiveItem('datasets', '/datasets'); setActiveItem('datasets', '/datasets');
const state = get(sidebarStore); const state = get(sidebarStore);
expect(state.activeCategory).toBe('datasets'); expect(state.activeCategory).toBe('datasets');
expect(state.activeItem).toBe('/datasets'); expect(state.activeItem).toBe('/datasets');
@@ -68,12 +70,13 @@ describe('SidebarStore', () => {
it('should update to admin category', () => { it('should update to admin category', () => {
setActiveItem('admin', '/settings'); setActiveItem('admin', '/settings');
const state = get(sidebarStore); const state = get(sidebarStore);
expect(state.activeCategory).toBe('admin'); expect(state.activeCategory).toBe('admin');
expect(state.activeItem).toBe('/settings'); expect(state.activeItem).toBe('/settings');
}); });
}); });
// [/DEF:test_setActiveItem:Function]
// [DEF:test_mobile_functions:Function] // [DEF:test_mobile_functions:Function]
// @TEST: Mobile functions correctly update isMobileOpen // @TEST: Mobile functions correctly update isMobileOpen
@@ -82,7 +85,7 @@ describe('SidebarStore', () => {
describe('mobile functions', () => { describe('mobile functions', () => {
it('should set isMobileOpen to true with setMobileOpen', () => { it('should set isMobileOpen to true with setMobileOpen', () => {
setMobileOpen(true); setMobileOpen(true);
const state = get(sidebarStore); const state = get(sidebarStore);
expect(state.isMobileOpen).toBe(true); expect(state.isMobileOpen).toBe(true);
}); });
@@ -90,7 +93,7 @@ describe('SidebarStore', () => {
it('should set isMobileOpen to false with closeMobile', () => { it('should set isMobileOpen to false with closeMobile', () => {
setMobileOpen(true); setMobileOpen(true);
closeMobile(); closeMobile();
const state = get(sidebarStore); const state = get(sidebarStore);
expect(state.isMobileOpen).toBe(false); expect(state.isMobileOpen).toBe(false);
}); });
@@ -98,18 +101,19 @@ describe('SidebarStore', () => {
it('should toggle isMobileOpen with toggleMobileSidebar', () => { it('should toggle isMobileOpen with toggleMobileSidebar', () => {
const initialState = get(sidebarStore); const initialState = get(sidebarStore);
const initialMobileOpen = initialState.isMobileOpen; const initialMobileOpen = initialState.isMobileOpen;
toggleMobileSidebar(); toggleMobileSidebar();
const state1 = get(sidebarStore); const state1 = get(sidebarStore);
expect(state1.isMobileOpen).toBe(!initialMobileOpen); expect(state1.isMobileOpen).toBe(!initialMobileOpen);
toggleMobileSidebar(); toggleMobileSidebar();
const state2 = get(sidebarStore); const state2 = get(sidebarStore);
expect(state2.isMobileOpen).toBe(initialMobileOpen); expect(state2.isMobileOpen).toBe(initialMobileOpen);
}); });
}); });
// [/DEF:test_mobile_functions:Function]
}); });
// [/DEF:frontend.src.lib.stores.__tests__.sidebar:Module] // [/DEF:frontend.src.lib.stores.__tests__.sidebar:Module]

View File

@@ -1,4 +1,9 @@
// [DEF:Utils:Module]
/** /**
* @TIER: TRIVIAL
* @PURPOSE: General utility functions (class merging)
* @LAYER: Infra
*
* Merges class names into a single string. * Merges class names into a single string.
* @param {...(string | undefined | null | false)} inputs * @param {...(string | undefined | null | false)} inputs
* @returns {string} * @returns {string}
@@ -6,3 +11,4 @@
export function cn(...inputs) { export function cn(...inputs) {
return inputs.filter(Boolean).join(" "); return inputs.filter(Boolean).join(" ");
} }
// [/DEF:Utils:Module]

View File

@@ -1,4 +1,9 @@
// [DEF:Debounce:Module]
/** /**
* @TIER: TRIVIAL
* @PURPOSE: Debounce utility for limiting function execution rate
* @LAYER: Infra
*
* Debounce utility function * Debounce utility function
* Delays the execution of a function until a specified time has passed since the last call * Delays the execution of a function until a specified time has passed since the last call
* *
@@ -17,3 +22,4 @@ export function debounce(func, wait) {
timeout = setTimeout(later, wait); timeout = setTimeout(later, wait);
}; };
} }
// [/DEF:Debounce:Module]

View File

@@ -1,11 +1,24 @@
<!-- [DEF:ErrorPage:Page] -->
<script> <script>
import { page } from '$app/stores'; /**
* @TIER: STANDARD
* @PURPOSE: Global error page displaying HTTP status and messages
* @LAYER: UI
* @UX_STATE: Error -> Displays error code and message with home link
*/
import { page } from "$app/stores";
</script> </script>
<div class="container mx-auto p-4 text-center mt-20"> <div class="container mx-auto p-4 text-center mt-20">
<h1 class="text-6xl font-bold text-gray-800 mb-4">{$page.status}</h1> <h1 class="text-6xl font-bold text-gray-800 mb-4">{$page.status}</h1>
<p class="text-2xl text-gray-600 mb-8">{$page.error?.message || 'Page not found'}</p> <p class="text-2xl text-gray-600 mb-8">
<a href="/" class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors"> {$page.error?.message || "Page not found"}
</p>
<a
href="/"
class="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 transition-colors"
>
Back to Dashboard Back to Dashboard
</a> </a>
</div> </div>
<!-- [/DEF:ErrorPage:Page] -->

View File

@@ -1,2 +1,9 @@
// [DEF:RootLayoutConfig:Module]
/**
* @TIER: TRIVIAL
* @PURPOSE: Root layout configuration (SPA mode)
* @LAYER: Infra
*/
export const ssr = false; export const ssr = false;
export const prerender = false; export const prerender = false;
// [/DEF:RootLayoutConfig:Module]

View File

@@ -1,3 +1,9 @@
// [DEF:DashboardTypes:Module]
/**
* @TIER: TRIVIAL
* @PURPOSE: TypeScript interfaces for Dashboard entities
* @LAYER: Domain
*/
export interface DashboardMetadata { export interface DashboardMetadata {
id: number; id: number;
title: string; title: string;
@@ -10,4 +16,5 @@ export interface DashboardSelection {
source_env_id: string; source_env_id: string;
target_env_id: string; target_env_id: string;
replace_db_config?: boolean; replace_db_config?: boolean;
} }
// [/DEF:DashboardTypes:Module]

File diff suppressed because it is too large Load Diff