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

View File

@@ -1,7 +1,7 @@
# [DEF:Project_Knowledge_Map:Root]
# @TIER: CRITICAL
# @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)
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]`
* **Plugin Module:** Reference implementation of a task plugin.
* 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)
* **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.
# @LAYER: Interface (API)
# @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 pydantic import BaseModel
from ...core.logger import belief_scope
from ...core.task_manager import TaskManager, Task
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()
@@ -21,37 +25,41 @@ class CreateTaskRequest(BaseModel):
# @PURPOSE: Create and start a new task using TaskManager. Non-blocking.
# @PARAM: request (CreateTaskRequest) - Plugin and params.
# @PARAM: task_manager (TaskManager) - Async task executor.
# @PARAM: config (ConfigManager) - Centralized configuration.
# @PRE: plugin_id must exist; config must be initialized.
# @PRE: plugin_id must match a registered plugin.
# @POST: A new task is spawned; Task ID returned immediately.
# @SIDE_EFFECT: Writes to DB, Trigger background worker.
async def create_task(
request: CreateTaskRequest,
task_manager: TaskManager = Depends(get_task_manager),
config: ConfigManager = Depends(get_config_manager),
current_user = Depends(get_current_user)
):
# RBAC: Dynamic permission check
has_permission(f"plugin:{request.plugin_id}", "EXECUTE")(current_user)
# Context Logging
with belief_scope("create_task"):
try:
# 1. Action: Resolve setting using ConfigManager (Example)
# 1. Action: Configuration Resolution
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
task = await task_manager.create_task(
plugin_id=request.plugin_id,
params={**request.params, "timeout": timeout}
)
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:
# Evaluation: Proper error mapping and logging
# @UX_STATE: Error feedback to frontend
# @UX_STATE: Error feedback -> 500 Internal Error
raise HTTPException(
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: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] -->
# @PURPOSE: Reference implementation of a task-spawning component using Constitution rules.
# @RELATION: IMPLEMENTS -> [DEF:Std:UI_Svelte]
<!-- [DEF:FrontendComponentShot:Component] -->
<script>
/**
* @TIER: STANDARD
* @PURPOSE: Action button to spawn a new task.
* @LAYER: UI
* @SEMANTICS: Task, Creation, Button
* @TIER: CRITICAL
* @SEMANTICS: Task, Button, Action, UX
* @PURPOSE: Action button to spawn a new task with full UX feedback cycle.
* @LAYER: UI (Presentation)
* @RELATION: CALLS -> postApi
* @INVARIANT: Must prevent double-submission while loading.
*
* @UX_STATE: Idle -> Button enabled with primary color.
* @UX_STATE: Loading -> Button disabled with spinner while postApi resolves.
* @UX_FEEDBACK: toast.success on completion; toast.error on failure.
* @UX_TEST: Idle -> {click: spawnTask, expected: loading state then success}
* @TEST_DATA: idle_state -> {"isLoading": false}
* @TEST_DATA: loading_state -> {"isLoading": true}
*
* @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 { t } from "$lib/i18n";
@@ -24,40 +28,43 @@
let isLoading = false;
async def spawnTask() {
// [DEF:spawnTask:Function]
async function spawnTask() {
isLoading = true;
console.log("[FrontendComponentShot][Loading] Spawning task...");
try {
// 1. Action: Constitution Rule - MUST use postApi wrapper
// 1. Action: API Call
const response = await postApi("/api/tasks", {
plugin_id,
params
});
// 2. Feedback: UX state management
// 2. Feedback: Success
if (response.task_id) {
console.log("[FrontendComponentShot][Success] Task created.");
toast.success($t.tasks.spawned_success);
}
} 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}`);
} finally {
isLoading = false;
}
}
// [/DEF:spawnTask:Function]
</script>
<button
<button
on:click={spawnTask}
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}
<span class="animate-spin text-sm">🌀</span>
<span class="animate-spin" aria-label="Loading">🌀</span>
{/if}
<span>{$t.actions.start_task}</span>
</button>
<style>
/* Local styles minimized as per Constitution Rule III */
</style>
<!-- [/DEF:Shot:Svelte_Component] -->
<!-- [/DEF:FrontendComponentShot: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.
# @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 ..core.plugin_base import PluginBase
@@ -11,28 +15,15 @@ class ExamplePlugin(PluginBase):
def id(self) -> str:
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]
# @PURPOSE: Defines input validation schema.
# @POST: Returns dict compliant with JSON Schema draft 7.
def get_schema(self) -> Dict[str, Any]:
return {
"type": "object",
"properties": {
"message": {
"type": "string",
"title": "Message",
"description": "A message to log.",
"default": "Hello, GRACE!",
}
},
@@ -41,27 +32,33 @@ class ExamplePlugin(PluginBase):
# [/DEF:get_schema: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: context (TaskContext) - Execution context with logging and progress tools.
async def execute(self, params: Dict[str, Any], context: Optional[TaskContext] = None):
message = params["message"]
# @PARAM: context (TaskContext) - Execution tools (log, progress).
# @SIDE_EFFECT: Emits logs to centralized system.
async def execute(self, params: Dict, context: Optional = None):
message = params
# 1. Action: Structured Logging with Source Attribution
if context:
log = context.logger.with_source("example_plugin")
log.info(f"Starting execution with message: {message}")
# 2. Action: Progress Reporting
log.progress("Processing step 1...", percent=25)
# Simulating some async work...
# await some_async_op()
log.progress("Processing step 2...", percent=75)
log.info("Execution completed successfully.")
else:
# Fallback for manual/standalone execution
print(f"Standalone execution: {message}")
# 1. Action: System-level tracing (Rule VI)
with belief_scope("example_plugin_exec") as b_scope:
if context:
# Task Logs: Пишем в пользовательский контекст выполнения задачи
# @RELATION: BINDS_TO -> context.logger
log = context.logger.with_source("example_plugin")
b_scope.logger.info("Using provided TaskContext") # System log
log.info("Starting execution", data={"msg": message}) # Task log
# 2. Action: Progress Reporting
log.progress("Processing...", percent=50)
# 3. Action: Finalize
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: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():
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)
async def serve_spa(file_path: str):
# Only serve SPA for non-API paths

View File

@@ -18,3 +18,4 @@ def __getattr__(name):
from .resource_service import ResourceService
return ResourceService
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();
let passwords = {};
let submitting = false;
let passwords = $state({});
let submitting = $state(false);
// [DEF:handleSubmit:Function]
// @PURPOSE: Validates and dispatches the passwords to resume the task.
@@ -69,7 +69,7 @@
<div
class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true"
on:click={handleCancel}
onclick={handleCancel}
></div>
<span
@@ -126,7 +126,7 @@
{/if}
<form
on:submit|preventDefault={handleSubmit}
onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}
class="space-y-4"
>
{#each databases as dbName}
@@ -158,7 +158,7 @@
<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"
on:click={handleSubmit}
onclick={handleSubmit}
disabled={submitting}
>
{submitting ? "Resuming..." : "Resume Migration"}
@@ -166,7 +166,7 @@
<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"
on:click={handleCancel}
onclick={handleCancel}
disabled={submitting}
>
Cancel

View File

@@ -123,7 +123,7 @@
<span>{error}</span>
<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"
on:click={handleRefresh}>Retry</button
onclick={handleRefresh}>Retry</button
>
</div>
{:else}
@@ -149,11 +149,11 @@
<div
class="fixed inset-0 bg-gray-500/75 transition-opacity"
aria-hidden="true"
on:click={() => {
onclick={() => {
show = false;
dispatch("close");
}}
on:keydown={(e) => e.key === "Escape" && (show = false)}
onkeydown={(e) => e.key === "Escape" && (show = false)}
role="presentation"
></div>
@@ -170,7 +170,7 @@
</h3>
<button
class="text-gray-500 hover:text-gray-300"
on:click={() => {
onclick={() => {
show = false;
dispatch("close");
}}

View File

@@ -7,67 +7,74 @@
-->
<script>
import { onMount } from 'svelte';
import { t } from '../../lib/i18n';
import { requestApi } from '../../lib/api';
import { onMount } from "svelte";
import { t } from "../../lib/i18n";
import { requestApi } from "../../lib/api";
/** @type {Array} */
let {
provider,
config = {},
} = $props();
let { providers = [], onSave = () => {} } = $props();
let editingProvider = null;
let showForm = false;
let formData = {
name: '',
provider_type: 'openai',
base_url: 'https://api.openai.com/v1',
api_key: '',
default_model: 'gpt-4o',
is_active: true
name: "",
provider_type: "openai",
base_url: "https://api.openai.com/v1",
api_key: "",
default_model: "gpt-4o",
is_active: true,
};
let testStatus = { type: '', message: '' };
let testStatus = { type: "", message: "" };
let isTesting = false;
function resetForm() {
formData = {
name: '',
provider_type: 'openai',
base_url: 'https://api.openai.com/v1',
api_key: '',
default_model: 'gpt-4o',
is_active: true
name: "",
provider_type: "openai",
base_url: "https://api.openai.com/v1",
api_key: "",
default_model: "gpt-4o",
is_active: true,
};
editingProvider = null;
testStatus = { type: '', message: '' };
testStatus = { type: "", message: "" };
}
function handleEdit(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;
}
async function testConnection() {
console.log("[ProviderConfig][Action] Testing connection", formData);
isTesting = true;
testStatus = { type: 'info', message: $t.llm.testing };
testStatus = { type: "info", message: $t.llm.testing };
try {
const endpoint = editingProvider ? `/llm/providers/${editingProvider.id}/test` : '/llm/providers/test';
const result = await requestApi(endpoint, 'POST', formData);
const endpoint = editingProvider
? `/llm/providers/${editingProvider.id}/test`
: "/llm/providers/test";
const result = await requestApi(endpoint, "POST", formData);
if (result.success) {
testStatus = { type: 'success', message: $t.llm.connection_success };
testStatus = { type: "success", message: $t.llm.connection_success };
} 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) {
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 {
isTesting = false;
}
@@ -75,8 +82,10 @@
async function handleSubmit() {
console.log("[ProviderConfig][Action] Submitting provider config");
const method = editingProvider ? 'PUT' : 'POST';
const endpoint = editingProvider ? `/llm/providers/${editingProvider.id}` : '/llm/providers';
const method = editingProvider ? "PUT" : "POST";
const endpoint = editingProvider
? `/llm/providers/${editingProvider.id}`
: "/llm/providers";
// When editing, only include api_key if user entered a new one
const submitData = { ...formData };
@@ -97,9 +106,9 @@
async function toggleActive(provider) {
try {
await requestApi(`/llm/providers/${provider.id}`, 'PUT', {
await requestApi(`/llm/providers/${provider.id}`, "PUT", {
...provider,
is_active: !provider.is_active
is_active: !provider.is_active,
});
onSave();
} catch (err) {
@@ -111,28 +120,53 @@
<div class="p-4">
<div class="flex justify-between items-center mb-6">
<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"
on:click={() => { resetForm(); showForm = true; }}
on:click={() => {
resetForm();
showForm = true;
}}
>
{$t.llm.add_provider}
</button>
</div>
{#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">
<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>
<label 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" />
<label
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>
<label 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">
<label
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="openrouter">OpenRouter</option>
<option value="kilo">Kilo</option>
@@ -140,47 +174,88 @@
</div>
<div>
<label 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" />
<label
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>
<label 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-..."} />
<label
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>
<label 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" />
<label
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 class="flex items-center">
<input 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>
<input
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>
{#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}
</div>
{/if}
<div class="mt-6 flex justify-between gap-2">
<button
<button
class="px-4 py-2 border rounded hover:bg-gray-50 flex-1"
on:click={() => { showForm = false; }}
on:click={() => {
showForm = false;
}}
>
{$t.llm.cancel}
</button>
<button
<button
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 flex-1"
disabled={isTesting}
on:click={testConnection}
>
{isTesting ? $t.llm.testing : $t.llm.test}
</button>
<button
<button
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 flex-1"
on:click={handleSubmit}
>
@@ -193,37 +268,45 @@
<div class="grid gap-4">
{#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 class="font-bold flex items-center gap-2">
{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'}`}>
{provider.is_active ? $t.llm.active : 'Inactive'}
<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"}`}
>
{provider.is_active ? $t.llm.active : "Inactive"}
</span>
</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 class="flex gap-2">
<button
<button
class="text-sm text-blue-600 hover:underline"
on:click={() => handleEdit(provider)}
>
{$t.common.edit}
</button>
<button
class={`text-sm ${provider.is_active ? 'text-orange-600' : 'text-green-600'} hover:underline`}
<button
class={`text-sm ${provider.is_active ? "text-orange-600" : "text-green-600"} hover:underline`}
on:click={() => toggleActive(provider)}
>
{provider.is_active ? 'Deactivate' : 'Activate'}
{provider.is_active ? "Deactivate" : "Activate"}
</button>
</div>
</div>
{: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}
</div>
{/each}
</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"
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}
on:change={handleLevelChange}
onchange={handleLevelChange}
aria-label="Filter by level"
>
{#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"
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}
on:change={handleSourceChange}
onchange={handleSourceChange}
aria-label="Filter by source"
>
<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"
placeholder="Search..."
value={searchText}
on:input={handleSearchChange}
oninput={handleSearchChange}
aria-label="Search logs"
/>
</div>
@@ -123,7 +123,7 @@
{#if hasActiveFilters}
<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"
on:click={clearFilters}
onclick={clearFilters}
aria-label="Clear filters"
>
<svg

View File

@@ -134,7 +134,7 @@
<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
{autoScroll ? 'text-terminal-accent' : ''}"
on:click={toggleAutoScroll}
onclick={toggleAutoScroll}
aria-label="Toggle auto-scroll"
>
{#if autoScroll}

View File

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

View File

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

View File

@@ -1,11 +1,24 @@
<!-- [DEF:ErrorPage:Page] -->
<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>
<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>
<p class="text-2xl text-gray-600 mb-8">{$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">
<p class="text-2xl text-gray-600 mb-8">
{$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
</a>
</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 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 {
id: number;
title: string;
@@ -10,4 +16,5 @@ export interface DashboardSelection {
source_env_id: string;
target_env_id: string;
replace_db_config?: boolean;
}
}
// [/DEF:DashboardTypes:Module]

File diff suppressed because it is too large Load Diff