- Consolidate fragmented test modal components into unified TestFormModal.svelte - Reorganize reference range components into organized tabs (RefNumTab, RefTxtTab) - Add new tab components: BasicInfoTab, TechDetailsTab, CalcDetailsTab, MappingsTab, GroupMembersTab - Move test-related type definitions to src/lib/types/test.types.ts for better type organization - Delete old component files: TestModal.svelte and 10+ sub-components - Add backup directory for old component preservation - Update AGENTS.md with coding guidelines and project conventions - Update tests.js API client with improved structure - Add documentation for frontend test management architecture
7.4 KiB
7.4 KiB
AGENTS.md - Coding Guidelines for CLQMS Frontend
Project Overview
SvelteKit frontend for Clinical Laboratory Quality Management System (CLQMS). Uses Svelte 5 runes, TailwindCSS 4, DaisyUI, and Lucide icons.
Build Commands
# Development server
pnpm run dev
# Production build
pnpm run build
# Preview production build
pnpm run preview
# Sync SvelteKit (runs automatically on install)
pnpm run prepare
Testing
No test framework configured yet. When adding tests:
- Use Vitest for unit tests (recommended with SvelteKit)
- Use Playwright for E2E tests
- Run single test:
vitest run src/path/to/test.js - Run tests in watch mode:
vitest
Code Style Guidelines
JavaScript/TypeScript
- ES Modules: Always use
import/export(type: "module" in package.json) - Semicolons: Use semicolons consistently
- Quotes: Use single quotes for strings
- Indentation: 2 spaces
- Trailing commas: Use in multi-line objects/arrays
- JSDoc: Document all exported functions with JSDoc comments
Svelte Components
<script>
// 1. Imports - Svelte, $app, $lib, external
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { auth } from '$lib/stores/auth.js';
import { login } from '$lib/api/auth.js';
import { User, Lock } from 'lucide-svelte';
// 2. Props with $bindable for two-way binding
let { open = $bindable(false), title = '', children, footer } = $props();
// 3. State
let loading = $state(false);
let error = $state('');
// 4. Derived state (if needed)
let isValid = $derived(username.length > 0);
// 5. Effects
$effect(() => { /* side effects */ });
// 6. Functions - prefix handlers with 'handle'
function handleSubmit() { /* implementation */ }
</script>
Naming Conventions
- Components: PascalCase (
LoginForm.svelte,PatientFormModal.svelte) - Files/Routes: lowercase with hyphens (
+page.svelte,user-profile/) - Variables: camelCase (
isLoading,userName) - Constants: UPPER_SNAKE_CASE (
API_URL,STORAGE_KEY) - Stores: camelCase, descriptive (
auth,userStore) - Event handlers: prefix with
handle(handleSubmit,handleClick) - Form state:
formLoading,formError,deleteConfirmOpen
Imports Order
- Svelte imports (
svelte,$app/*) - $lib aliases (
$lib/stores/*,$lib/api/*,$lib/components/*,$lib/utils/*) - External libraries (
lucide-svelte) - Relative imports (minimize these, prefer
$lib)
Project Structure
src/
lib/
api/ # API client and endpoints (per feature)
stores/ # Svelte stores (auth, config, valuesets)
components/ # Reusable components (DataTable, Modal, Sidebar)
utils/ # Utilities (toast, helpers)
assets/ # Static assets
routes/ # SvelteKit routes
(app)/ # Route groups (protected)
login/
dashboard/
API Client Patterns
// src/lib/api/client.js - Base client handles auth, 401 redirects
import { apiClient, get, post, put, patch, del } from '$lib/api/client.js';
// src/lib/api/feature.js - Feature-specific endpoints with JSDoc
export async function fetchItems(params = {}) {
const query = new URLSearchParams(params).toString();
return get(query ? `/api/items?${query}` : '/api/items');
}
export async function createItem(data) {
return post('/api/items', data);
}
export async function updateItem(data) {
return patch('/api/items', data);
}
export async function deleteItem(id) {
return del(`/api/items/${id}`);
}
Store Patterns
// Use writable for stores with localStorage persistence
import { writable } from 'svelte/store';
import { browser } from '$app/environment';
function createStore() {
const getInitialState = () => {
if (!browser) return { data: null };
return { data: JSON.parse(localStorage.getItem('key')) };
};
const { subscribe, set, update } = writable(getInitialState());
return {
subscribe,
setData: (data) => {
if (browser) localStorage.setItem('key', JSON.stringify(data));
set({ data });
}
};
}
Component Patterns
<!-- Use $bindable for two-way props -->
let { open = $bindable(false), selected = $bindable(null) } = $props();
<!-- Use snippets for slot content -->
{@render children?.()}
{@render footer()}
<!-- Dialog with backdrop handling -->
<dialog class="modal" class:modal-open={open}>
<div class="modal-box">
<button onclick={close}>X</button>
{@render children?.()}
</div>
<form method="dialog" class="modal-backdrop" onclick={handleBackdropClick}>
<button>close</button>
</form>
</dialog>
Error Handling
try {
const result = await api.fetchData();
toastSuccess('Operation successful');
} catch (err) {
const message = err.message || 'An unexpected error occurred';
toastError(message);
console.error('Operation failed:', err);
}
Styling with Tailwind & DaisyUI
- DaisyUI components:
btn,card,alert,input,navbar,modal,dropdown,menu - Color scheme:
primary(emerald),secondary(dark blue),accent(royal blue) - Custom compact classes:
.compact-y,.compact-p,.compact-input,.compact-btn,.compact-card - Size modifiers:
.btn-sm,.input-sm,.select-smfor compact forms
Authentication Patterns
- Auth state in
$lib/stores/auth.jsusing writable store - Check
$auth.isAuthenticatedin layoutonMount - Redirect to
/loginif unauthenticated usinggoto('/login') - API client auto-redirects on 401 responses
Runtime Config Pattern
// Use runtime config for API URL that can be changed at runtime
import { config } from '$lib/stores/config.js';
function getApiUrl() {
return config.getApiUrl() || import.meta.env.VITE_API_URL || '';
}
LocalStorage
- Only access in browser: check
browserfrom$app/environment - Use descriptive keys:
clqms_username,clqms_remember,auth_token
Form Handling Patterns
// Form state with validation
let formLoading = $state(false);
let formError = $state('');
let formData = $state({ username: '', password: '' });
function validateForm() {
formError = '';
if (!formData.username.trim()) {
formError = 'Username is required';
return false;
}
return true;
}
async function handleSubmit() {
if (!validateForm()) return;
formLoading = true;
try {
await api.submit(formData);
toastSuccess('Success');
} catch (err) {
formError = err.message;
} finally {
formLoading = false;
}
}
Toast/Notification System
import { success, error, info, warning } from '$lib/utils/toast.js';
// Use toast notifications for user feedback
success('Item created successfully');
error('Failed to create item');
info('New message received');
warning('Action requires confirmation');
Proxy Configuration
API requests to /api are proxied to http://localhost:8000 in dev. See vite.config.js.
API Documentation
API Reference (Swagger UI): https://clqms01-api.services-summit.my.id/swagger/
Important Notes
- No ESLint or Prettier configured yet - add if needed
- No test framework configured yet
- Uses Svelte 5 runes:
$props,$state,$derived,$effect,$bindable - SvelteKit file-based routing with
+page.svelte,+layout.svelte - Static adapter configured for static export
- Runtime config allows API URL changes without rebuild