**Test Group Member Management:** - Refactor member data structure from simple ID array to object array with sequence numbers - Update member objects to include both TestSiteID and Member (sequence) fields - Fix addMember() to assign sequential Member numbers automatically - Fix removeMember() to re-sequence remaining members after removal - Fix moveMember() to properly swap and re-sequence members - Add sorting by sequence number in members list display - Update payload builder in tests.js to use proper object structure for API - Update TestFormModal.svelte member mapping for edit mode **Documentation:** - Add TypeScript Types section to AGENTS.md with TestType and TestSummary examples - Reorganize TODO.md with new test type categories and backend notes **Cleanup:** - Remove deprecated OpenSpec skills from .opencode/ directory: - opsx-apply.md, opsx-archive.md, opsx-explore.md, opsx-propose.md - openspec-apply-change/SKILL.md, openspec-archive-change/SKILL.md - openspec-explore/SKILL.md, openspec-propose/SKILL.md **New Files:** - Add .serena/memories/ for code style conventions and task tracking
5.0 KiB
5.0 KiB
CLQMS Frontend - Code Style & Conventions
File Structure
src/
lib/
api/ # API client and endpoints (per feature)
stores/ # Svelte stores (auth, config, valuesets)
components/ # Reusable components (DataTable, Modal, Sidebar)
utils/ # Utility functions (toast, helpers)
assets/ # Static assets (favicon, etc.)
routes/ # SvelteKit routes
(app)/ # Protected routes (authenticated users)
dashboard/
patients/
master-data/
login/ # Public routes
Svelte Component Conventions
Component Structure Order
- Imports (Svelte, $app, $lib, external)
- Props with
$bindablefor two-way binding - State (
$state) - Derived state (
$derived) - Effects (
$effect) - Functions (prefix handlers with
handle)
Props and State
let { open = $bindable(false), title = '', children, footer } = $props();
let loading = $state(false);
let error = $state('');
Imports Order
- Svelte imports (
svelte,$app/*) - $lib aliases (
$lib/stores/*,$lib/api/*,$lib/components/*,$lib/utils/*) - External libraries (
lucide-svelte) - Relative imports (minimize, prefer
$lib)
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
Code Style
- ES Modules: Always use
import/export - Semicolons: Use 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
Component Patterns
Modal Pattern
<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>
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;
}
}
API Client Patterns
// src/lib/api/client.js - Base client
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 });
}
};
}
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
LocalStorage
- Only access in browser: check
browserfrom$app/environment - Use descriptive keys:
clqms_username,clqms_remember,auth_token
Runtime Config Pattern
import { config } from '$lib/stores/config.js';
function getApiUrl() {
return config.getApiUrl() || import.meta.env.VITE_API_URL || '';
}