clqms-fe1/AGENTS.md
mahdahar 99d622ad05 refactor(tests): consolidate test management modal components
- 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
2026-02-20 13:51:54 +07:00

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

  1. Svelte imports (svelte, $app/*)
  2. $lib aliases ($lib/stores/*, $lib/api/*, $lib/components/*, $lib/utils/*)
  3. External libraries (lucide-svelte)
  4. 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-sm for compact forms

Authentication Patterns

  • Auth state in $lib/stores/auth.js using writable store
  • Check $auth.isAuthenticated in layout onMount
  • Redirect to /login if unauthenticated using goto('/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 browser from $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