| name | zeta-api |
| description | ZetaCP internal API reference — notification system, Zustand stores, Tauri invoke patterns, event bus, z-index table, and database schema. Load this skill when adding features, wiring error handling, or working with any store/command.
|
ZetaCP Internal API Reference
Đọc toàn bộ file này trước khi implement feature mới.
Gọi đúng API, không tự tạo lại những gì đã có.
1. Notification System
Import
import { notify } from '@/stores/useNotificationStore';
import { notify } from '../../stores/useNotificationStore';
API
notify.error(title: string, message: string, hint?: string): string
notify.warn(title: string, message: string, hint?: string): string
notify.info(title: string, message: string): string
notify.success(title: string, message: string): string
notify.fromTauriError(title: string, err: unknown): string
notify.dismiss(id: string): void
Pattern chuẩn khi dùng invoke()
try {
const result = await invoke<MyResult>('my_command', { param });
notify.success('Hoàn thành', 'Mô tả ngắn');
} catch (err) {
notify.fromTauriError('Tiêu đề lỗi', err);
}
invoke('some_command', { param })
.then(() => notify.success('OK', 'Done'))
.catch((err) => notify.fromTauriError('Lỗi X', err));
invoke('cmd').catch(console.error);
Dùng trong store (outside React)
import { useNotificationStore } from './useNotificationStore';
const { error } = useNotificationStore.getState();
error('Lỗi lưu file', err.message);
import { notify } from './useNotificationStore';
notify.fromTauriError('Lỗi lưu file', err);
ZetaError shape (từ backend)
interface ZetaError {
code: 'COMPILER_NOT_FOUND' | 'DB_READONLY' | 'INVALID_INPUT' | 'IO_ERROR' | 'DB_ERROR' | 'FATAL';
message: string;
hint: string;
}
2. Zustand Stores
Tổng quan stores
| Store | File | Mục đích |
|---|
useProjectStore | stores/useProjectStore.ts | Active file, tabs, file content, autosave |
useTestcaseStore | stores/useTestcaseStore.ts | Testcase CRUD, judge run, file settings |
useOverlayStore | stores/useOverlayStore.ts | Floating overlay windows + logs |
useSettingsStore | stores/useSettingsStore.ts | Global settings (gpp_path, python_path...) |
useLayoutStore | stores/useLayoutStore.ts | Panel open/close state, activeView |
useStressTestStore | stores/useStressTestStore.ts | Stress test state, results |
useSnippetStore | stores/useSnippetStore.ts | Code snippets |
useNotificationStore | stores/useNotificationStore.ts | Notification center |
useProjectStore — key API
const {
rootPath,
activeFile,
activeFileContent,
openTabs,
dirtyFiles,
cursorPos,
openProject,
setActiveFile,
saveActiveFile,
closeTab,
} = useProjectStore();
useTestcaseStore — key API
const {
metas,
results,
loadedData,
subtasks,
activeFilePath,
fileSettings,
loadForFile,
loadData,
addTestcase,
deleteTestcase,
simulateRun,
cancelRun,
saveFileSettings,
} = useTestcaseStore();
useLayoutStore — key API
const {
terminalOpen,
setTerminalOpen,
activeView,
setActiveView,
} = useLayoutStore();
useSettingsStore — key API
const {
settings,
isSettingsOpen,
loadSettings,
saveSettings,
openSettings,
closeSettings,
} = useSettingsStore();
Đọc store ngoài React (không có hook)
const rootPath = useProjectStore.getState().rootPath;
const activeFile = useProjectStore.getState().activeFile;
await useTestcaseStore.getState().simulateRun();
Subscribe store (side effects)
const unsub = useTestcaseStore.subscribe((state, prev) => {
if (state.results !== prev.results) { }
});
3. Tauri Invoke — Commands Reference
Import
import { invoke } from '@tauri-apps/api/core';
import { compileFile, loadFileSettings, ... } from '@/lib/tauri-bridge';
tauri-bridge.ts wrappers (dùng những cái này thay invoke trực tiếp)
loadSettings(): Promise<GlobalSettings>
saveSettings(settings: GlobalSettings): Promise<void>
openProject(folderPath: string): Promise<ProjectInfo>
scanDirectory(folderPath: string, filter: FileFilter): Promise<FileNode[]>
startFileWatcher(root: string): Promise<void>
stopFileWatcher(): Promise<void>
writeTextFile(filePath: string, content: string, rootPath: string): Promise<void>
readTextFile(filePath: string, rootPath: string): Promise<string>
compileFile(filePath: string, flags: string[], projectRoot: string): Promise<CompileResult>
compileChecker(checkerPath: string, checkerType: string, projectRoot: string): Promise<CompileResult>
loadFileContext(filePath: string): Promise<FileContext>
loadFileSettings(filePath: string): Promise<FileSettings>
saveFileSettings(settings: FileSettings, filePath?: string): Promise<void>
importTestcasesFromFolder(folderPath: string, filePath: string): Promise<void>
loadOverlays(filePath: string): Promise<OverlayState[]>
saveOverlaysBackend(filePath: string, overlays: OverlayState[]): Promise<void>
Invoke trực tiếp (khi không có wrapper)
const result = await invoke<ReturnType>('command_name', { paramCamelCase: value });
await invoke('run_testcases', { filePath, testcaseIds });
await invoke('stop_testcases');
await invoke('run_stress_test', { solutionPath, brutePath, ... });
await invoke('stop_stress_test');
await invoke('open_docs_window', { docsType: 'cp-algorithms' });
await invoke('compute_diff', { expected, actual });
4. Tauri Event Bus
Listen events (FE nhận từ BE)
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen<JudgeProgress>('judge-progress', (event) => {
const { testcaseId, status, result } = event.payload;
});
await listen<StressTestPayload>('stress-test-progress', (event) => {
});
await listen('file-changed', handler);
await listen('testcase-list-updated', ({ payload }) => {
const { filePath } = payload as { filePath: string };
});
await listen<TestcaseImportedPayload>('testcase-imported', handler);
await listen('overlays-updated', handler);
await listen<{ testcaseId: string, diffLines: DiffLine[] }>('diff-data-updated', handler);
Emit events (FE gửi)
import { emit } from '@tauri-apps/api/event';
await emit('diff-data-updated', { testcaseId, diffLines });
Cleanup pattern
useEffect(() => {
let unlisten: (() => void) | null = null;
listen('event-name', handler).then(fn => { unlisten = fn; });
return () => { unlisten?.(); };
}, []);
5. Z-Index Hierarchy
KHÔNG dùng z-index tùy tiện. Tuân theo bảng sau:
| Z-index | Dùng cho |
|---|
z-10 | Resize handles nền |
z-30 | StressTester maximize button |
z-50 | TitleBar, StatusBar, dropdowns thông thường, modal fixed inset-0 |
z-[100] | SettingsPanel (modal backdrop) |
z-[9000] | Notification toasts & panel — trên mọi thứ trừ pinned overlay |
z-[9999] | OverlayPlusMenu, SnippetManager modal |
overlay.zIndex | Overlay window thường (dynamic, bắt đầu ~1) |
overlay.zIndex + 10000 | isPinned overlay — luôn trên cùng |
overlay.zIndex + 15000 | Drag ghost layer khi đang kéo |
Rule: Notification (z-9000) thắng tất cả trừ pinned overlay (≥10001).
6. Database Schema Reference
DB 1: zetacp-settings.db — Global, 1 file duy nhất
| Bảng | Columns | Ghi chú |
|---|
Settings | key TEXT PK, value TEXT | Global config: compiler.gpp_path, compiler.python_path, compiler.default_flags, judge.threads |
RecentProjects | path TEXT PK, last_open INTEGER | Unix timestamp |
Snippets | id INT PK, trigger TEXT, description TEXT, code TEXT, language TEXT, is_default INT | UNIQUE(trigger, language) |
Đọc settings từ BE:
let val = sqlx::query_scalar::<_, String>("SELECT value FROM Settings WHERE key = ?")
.bind("compiler.gpp_path")
.fetch_optional(&state.settings_db)
.await?;
DB 2: ZetaCP.db — Per source directory (<dir>/.ZetaCP/ZetaCP.db)
| Bảng | Columns key | Phân loại | Ghi chú |
|---|
TestcaseMeta | id PK, file_path, name, order_index, subtask_id, is_active | Domain core | Index: (file_path, order_index) |
TestcaseData | id PK FK(Meta), input, expected_output | Heavy data | CASCADE delete khi xóa Meta |
TestcaseResult | id PK FK(Meta), last_status, exec_time_ms, memory_kb, actual_output, diff_info, run_at | Heavy operational | (Lưu ý: Bảng này chỉ còn phục vụ hiển thị nhanh cho testcase, các bản ghi chi tiết đã chuyển sang bảng Runs) |
Subtask | id PK, file_path, name, max_score, order_index | Domain core | |
CompileCache | file_path PK, source_hash, binary_path, compiled_at | Ephemeral | Hash = SHA-256 source |
ExecutionConfig | file_path PK, compiler_flags, interpreter_flags, io_mode, input_file, output_file, time_limit_ms, memory_limit_kb, run_mode, checker_type, custom_checker_path, custom_checker_binary | Config chạy | Chứa cài đặt cấu hình chạy & dịch cơ bản |
StressConfig | file_path PK FK(ExecutionConfig), brute_path, sol_path, gen_path, gen_mode, gen_time_limit_ms, gen_memory_limit_kb, brute_time_limit_ms, brute_memory_limit_kb | Config Stress | Lưu cấu hình Stress test tách biệt |
Runs | id PK, run_type, parent_id, file_path, verdict, exec_time_ms, memory_kb, actual_output, diff_info, run_at, extra_json | Heavy operational | Bảng ghi lịch sử chạy tập trung (Stress + Judge) |
OverlayState | id PK, file_path, type, title, content, x, y, width, height, ... | UI session | |
Lưu ý về backward compatibility: Bảng cũ FileSettings (20 cột) vẫn được giữ lại trong database và đồng bộ hai chiều thông qua các SQLite triggers (file_settings_after_insert, file_settings_after_update, file_settings_after_delete) để tránh gây lỗi cho frontend cũ chưa nâng cấp. Tuy nhiên, code mới nên ưu tiên đọc/ghi trực tiếp vào ExecutionConfig và StressConfig.
Lấy project DB pool từ BE:
let proj_db = state.get_db_pool(&file_path, true).await?.unwrap();
7. Types Reference (Frontend)
Key types — import từ @/types/testcase
import {
TestcaseMeta,
TestcaseData,
TestcaseResult,
Subtask,
FileSettings,
FileContext,
DiffLine,
JudgeProgress,
} from '@/types/testcase';
GlobalSettings — import từ @/types/settings
interface GlobalSettings {
'compiler.gpp_path': string;
'compiler.python_path': string;
'compiler.default_flags': string;
'judge.threads': string;
}
8. Component Mounting Pattern
Thêm component global mới vào App
return (
<div className="flex flex-col h-screen ...">
{/* ... panels ... */}
<InternalOverlayContainer />
<NotificationCenter /> {/* ← thêm tương tự đây */}
<MyNewGlobalComponent />
</div>
);
Thêm tab vào ActivityBar (left sidebar)
{activeTab === 'my-tab' ? (
<MyComponent />
) : }
Overlay floating window
await useOverlayStore.getState().addOverlay('scratchpad', 'Title', 'initial content');
9. Codicons Reference (hay dùng)
codicon-error → lỗi (đỏ)
codicon-warning → cảnh báo (vàng)
codicon-info → thông tin (xanh)
codicon-pass-filled → thành công (xanh)
codicon-bell → notification
codicon-bell-slash → no notification
codicon-terminal → terminal/console
codicon-play → run/execute
codicon-debug-stop → stop
codicon-loading → loading (spinner với animate-spin)
codicon-close → đóng
codicon-chevron-down → collapse/expand
codicon-refresh → reload
codicon-gear → settings
codicon-file → file
codicon-folder → folder
codicon-eye / codicon-eye-closed → show/hide
codicon-pin / codicon-pinned → pin overlay
Dùng: <span className="codicon codicon-error text-[14px] text-red-400" />
10. Checklist trước khi submit code
11. Rust Backend Unified Modules
Khi thực thi chạy, dịch hoặc kiểm thử trong các Tauri command mới, bắt buộc phải gọi các hàm tiện ích đã được unify dưới đây:
1. Biên dịch C++ thống nhất (judge/compiler.rs)
pub async fn compile_cpp(
state: &AppState,
src_path: &Path,
bin_path: &Path,
flags: &[String],
include_parent: bool,
) -> Result<String, String>
2. Chạy Sandbox (judge/runner.rs)
pub async fn execute_once(opts: &RunOptions) -> Result<RunResult, std::io::Error>
3. Phân tích kết quả chạy (judge/runner.rs)
pub fn resolve_verdict(
stdout: &str,
stderr: &str,
success: bool,
is_timeout: bool,
memory_kb: i64,
memory_limit_kb: i64,
expected_output: Option<&str>,
checker_type: Option<&str>,
) -> String
4. Chạy Batch testcases song song (judge/orchestrator.rs)
pub async fn execute_batch(
testcase_ids: Vec<String>,
exec_path: String,
args: Vec<String>,
settings: FileSettings,
run_dir: &Path,
inp_name: String,
out_name: String,
proj_db: sqlx::SqlitePool,
app_handle: tauri::AppHandle,
concurrency: usize,
cancel_token: Arc<AtomicBool>,
) -> Result<Vec<TestcaseResult>, ZetaError>