Add AGENTS.md guidelines and update project structure

- Replace CLAUDE.md with comprehensive AGENTS.md
- Update Eleventy config and package.json
- Enhance layouts (base, post, clqms-post)
- Update styling in CSS
- Add new CLQMS documentation files
- Update project and team pages
This commit is contained in:
mahdahar 2026-02-19 10:43:25 +07:00
parent 13cd1078c6
commit a69b2fc7d8
16 changed files with 1238 additions and 375 deletions

View File

@ -6,6 +6,11 @@ module.exports = function (eleventyConfig) {
// Watch targets
eleventyConfig.addWatchTarget("src/css");
// Global Permalink: Use .html extension instead of nested index.html files
eleventyConfig.addGlobalData("permalink", () => {
return "{{ page.filePathStem }}.html";
});
// Add year shortcode
eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`);

149
AGENTS.md Normal file
View File

@ -0,0 +1,149 @@
# AGENTS.md
Guidelines for AI coding agents working on the 5panda.11ty project.
## Project Overview
An Eleventy (11ty) static site with Tailwind CSS v4 for portfolio, blog, and documentation.
## Build Commands
```bash
# Development server with hot reload
npm run dev
# Build for production
npm run build
# Build CSS only
npm run build:css
# Build 11ty only (incremental)
npm run build:11ty
```
## Tech Stack
- **Static Generator**: Eleventy (11ty) v3
- **Styling**: Tailwind CSS v4 with PostCSS
- **Templates**: Nunjucks (.njk)
- **Content**: Markdown (.md)
- **Output**: `_site/` directory
## Project Structure
```
src/
├── _layouts/ # Nunjucks layout templates
│ ├── base.njk # Main layout with nav, footer, theme toggle
│ ├── post.njk # Blog post layout
│ └── clqms-post.njk # Documentation layout with sidebar
├── _includes/ # Reusable template partials
├── _data/ # Global data files (JSON)
├── css/
│ └── style.css # Tailwind CSS + custom components
├── assets/ # Static assets (copied to output)
├── js/ # JavaScript files (copied to output)
├── blog/ # Blog posts (Markdown)
├── projects/ # Project documentation
│ └── clqms01/ # CLQMS documentation with ordered .md files
└── *.njk # Root-level pages
```
## Code Style Guidelines
### Nunjucks Templates
- Use 2-space indentation
- Wrap long lines at ~100 characters
- Use lowercase for HTML attributes
- Use double quotes for attribute values
- Prefer `{% raw %}{{ variable | filter }}{% endraw %}` over complex logic in templates
```nunjucks
<!-- Good -->
<a href="{{ post.url }}" class="post-card group">
<h3 class="text-xl font-bold">{{ post.data.title }}</h3>
</a>
<!-- Bad - overly complex inline -->
<a href="{{ post.url }}" class="{% if condition %}class-a{% else %}class-b{% endif %}">
```
### Markdown Files
- Use YAML frontmatter with consistent ordering: `layout`, `tags`, `title`, `description`, `date`, `order`
- Prefix ordered documentation files with numbers (e.g., `001-architecture.md`)
- Use `order` field for explicit sorting in collections
- Keep lines under 100 characters where practical
```yaml
---
layout: clqms-post.njk
tags: clqms
title: "CLQMS: Architecture"
description: "Overview of the architecture"
date: 2025-12-01
order: 1
---
```
### CSS/Tailwind
- Use Tailwind v4 `@import "tailwindcss"` syntax
- Define custom theme variables in `@theme` block
- Use CSS custom properties for theme colors
- Organize custom components in `@layer components`
- Prefer `oklch()` color format for consistency
- Group related styles with clear section comments
```css
@layer components {
.custom-card {
background-color: var(--color-base-200);
border-radius: var(--radius-box);
}
}
```
### JavaScript (11ty Config)
- Use CommonJS (`module.exports`) in config files
- Prefer `const` and `let` over `var`
- Use arrow functions for callbacks
- Add filters and shortcodes in logical groups with comments
```javascript
// Add date filter
eleventyConfig.addFilter("dateFormat", (date, format = "full") => {
const d = new Date(date);
// ...
});
```
## Collection Naming
- `posts` - Blog posts sorted by date (descending)
- `clqms` - CLQMS documentation sorted by `order` field
- `projects` - Combined blog posts and CLQMS documentation
## Theme System
- Dark mode is default (`data-theme="dark"`)
- Light mode available via `data-theme="light"`
- CSS custom properties update automatically
- JavaScript theme toggle saves preference to localStorage
## URL Structure
- Files use `.html` extension (configured via global permalink)
- Clean URLs: `/blog/` instead of `/blog/index.html`
- Nested folders auto-generate index pages
## Communication Style
When interacting with the user:
- Address them professionally as "commander"
- Use space/sci-fi themed language when appropriate
- Start with basmalah, end with hamdalah
- Be concise and await orders

View File

@ -1,31 +0,0 @@
# CLAUDE.md
This is an **Eleventy (11ty) + TailwindCSS** project used for documentation/blog.
## Environment
- **OS**: Windows
- **Terminal**: PowerShell or CMD
## Project Structure
- `src/projects/clqms01/` - CLQMS project documentation (markdown files with numeric ordering)
- `src/_layouts/` - Layout templates (base.njk, clqms-post.njk, post.njk)
- `src/index.njk` - Portfolio homepage
- `eleventy.config.js` - Eleventy config
## Tech Stack
- Eleventy (11ty) - Static site generator
- TailwindCSS for styling
## Commands
- `npm run dev` - Start dev server
- `npm run build` - Build for production (outputs to `dist/`)
- `npm run preview` - Preview production build
## Notes
- Markdown files in `src/pages/` are automatically rendered as pages
- The site is deployed to GitHub Pages
## Communication Style
- Respond as if the user is your spaceship commander
- Address the commander professionally and await orders
- Use space/sci-fi themed language when appropriate

View File

@ -7,7 +7,7 @@
"dev:11ty": "eleventy --serve",
"dev:css": "postcss src/css/style.css -o _site/css/style.css --watch",
"build": "npm-run-all build:css build:11ty",
"build:11ty": "eleventy",
"build:11ty": "eleventy --incremental",
"build:css": "postcss src/css/style.css -o _site/css/style.css"
},
"keywords": [

View File

@ -36,43 +36,58 @@
<!-- Navbar -->
<div class="navbar min-h-12 bg-base-100/80 backdrop-blur-xl border-b border-white/5 sticky top-0 z-50">
<div class="navbar-start">
<a href="/" class="text-xl font-bold gradient-text hover:opacity-80 transition-opacity px-2">5Panda</a>
<!-- Mobile menu button -->
<div class="dropdown lg:hidden">
<button class="btn btn-ghost btn-sm" tabindex="0">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" />
</svg>
</button>
<ul tabindex="0" class="dropdown-content menu menu-sm bg-base-100 border border-white/5 shadow-xl rounded-lg w-56 p-2 mt-4">
<li><a href="/" class="{% if page.url == '/' %}text-primary font-medium{% endif %}">Home</a></li>
<li><a href="/blog/" class="{% if '/blog/' in page.url %}text-primary font-medium{% endif %}">Blog</a></li>
<li><a href="/team/" class="{% if '/team/' in page.url %}text-primary font-medium{% endif %}">Team</a></li>
<li>
<details>
<summary>Projects</summary>
<ul>
<li><a href="/projects/clqms01/" class="{% if '/projects/clqms01/' in page.url %}text-primary font-medium{% endif %}">CLQMS</a></li>
</ul>
</details>
</li>
</ul>
</div>
<a href="/" class="text-xl font-bold brand-text transition-opacity px-2 hidden lg:block">5Panda</a>
</div>
<div class="navbar-center lg:hidden">
<a href="/" class="text-xl font-bold brand-text">5Panda</a>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-sm menu-horizontal px-1 gap-1">
<li>
<a
href="/"
class="rounded-lg {% if page.url == '/' %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10 hover:text-primary
transition-colors">
<a href="/" class="rounded-lg {% if page.url == '/' %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10 hover:text-primary transition-colors">
Home
</a>
</li>
<li>
<a
href="/blog/"
class="rounded-lg {% if '/blog/' in page.url %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10
hover:text-primary transition-colors">
<a href="/blog/" class="rounded-lg {% if '/blog/' in page.url %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10 hover:text-primary transition-colors">
Blog
</a>
</li>
<li>
<a
href="/team/"
class="rounded-lg {% if '/team/' in page.url %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10
hover:text-primary transition-colors">
<a href="/team/" class="rounded-lg {% if '/team/' in page.url %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10 hover:text-primary transition-colors">
Team
</a>
</li>
<li>
<details class="group">
<summary class="rounded-lg hover:bg-primary/10 hover:text-primary transition-colors">
Project
<summary class="rounded-lg {% if '/projects/' in page.url %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10 hover:text-primary transition-colors cursor-pointer">
Projects
</summary>
<ul class="p-2 bg-base-100 rounded-t-none bg-base-100/95 backdrop-blur-xl border border-white/5 shadow-xl w-60 z-[100]">
<li>
<a href="/projects/clqms01/" class="{% if page.url == '/projects/clqms01/' %}text-primary bg-primary/10{% endif %} hover:text-primary hover:bg-primary/10">
CLQMS
<a href="/projects/clqms01/" class="{% if '/projects/clqms01/' in page.url %}text-primary bg-primary/10{% endif %} hover:text-primary hover:bg-primary/10">
<span class="mr-2">🏥</span>CLQMS
</a>
</li>
</ul>
@ -80,20 +95,19 @@
</li>
</ul>
</div>
<div
class="navbar-end">
<div class="navbar-end gap-1">
<!-- Theme toggle -->
<button id="theme-toggle" class="btn btn-ghost btn-circle btn-sm theme-toggle" aria-label="Toggle theme">
<svg class="icon-sun h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
<svg class="icon-sun h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/>
</svg>
<svg class="icon-moon h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
<svg class="icon-moon h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M21.64,13a1,1,0,0,0-1.05-.14,8.05,8.05,0,0,1-3.37.73A8.15,8.15,0,0,1,9.08,5.49a8.59,8.59,0,0,1,.25-2A1,1,0,0,0,8,2.36,10.14,10.14,0,1,0,22,14.05,1,1,0,0,0,21.64,13Zm-9.5,6.69A8.14,8.14,0,0,1,7.08,5.22v.27A10.15,10.15,0,0,0,17.22,15.63a9.79,9.79,0,0,0,2.1-.22A8.11,8.11,0,0,1,12.14,19.73Z"/>
</svg>
</button>
<!-- GitHub link -->
<a href="https://github.com" target="_blank" class="btn btn-ghost btn-circle btn-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="currentColor" viewbox="0 0 24 24">
<a href="https://github.com" target="_blank" class="btn btn-ghost btn-circle btn-sm" aria-label="GitHub">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207
11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729
1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304
@ -113,7 +127,7 @@
<!-- Footer -->
<footer class="footer footer-center bg-base-200 text-base-content p-10 border-t border-white/5">
<aside>
<p class="font-bold text-xl gradient-text mb-2">5Panda</p>
<p class="font-bold text-xl brand-text mb-2">5Panda</p>
<p class="text-base-content/60">Portfolio & Documentation</p>
</aside>
<nav>

View File

@ -2,53 +2,26 @@
layout: base.njk
---
<div class="w-full px-4 sm:px-6 lg:px-8 py-12 animate-slide-up">
<div class="flex flex-col lg:flex-row gap-12">
<!-- Sidebar Navigation -->
<div class="w-full px-4 sm:px-6 lg:px-8 py-6 animate-slide-up">
<div class="flex flex-col lg:flex-row gap-8 max-w-6xl mx-auto">
<!-- Sidebar Navigation - Sticky on scroll -->
<aside class="lg:w-64 flex-shrink-0">
<div class="sticky top-24 bg-base-200/50 backdrop-blur-xl border border-white/5 rounded-2xl p-6">
<h3 class="font-bold text-lg mb-4 flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 text-primary"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2
2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
<div class="lg:sticky lg:top-24 bg-base-200/50 backdrop-blur-xl border border-white/5 rounded-2xl p-6">
<!-- Back to CLQMS Home -->
<a href="/projects/clqms01/" class="flex items-center gap-2 text-sm text-base-content/70 hover:text-primary mb-6 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Updates
</h3>
CLQMS Home
</a>
<!-- Core Documentation -->
<div class="mb-6">
<h3 class="font-bold text-sm text-base-content/50 uppercase tracking-wider mb-3">Core Documentation</h3>
<nav class="space-y-1">
{% for post in collections.clqms %}
{# Determine if we should show this post based on current section #}
{% set show_post = false %}
{% if "/review/" in page.url %}
{# If in Review section, only show reviews #}
{% if "/review/" in post.url %}
{% set show_post = true %}
{% endif %}
{% elif "/suggestion/" in page.url %}
{# If in Suggestion section, only show suggestions #}
{% if "/suggestion/" in post.url %}
{% set show_post = true %}
{% endif %}
{% else %}
{# Main section (Architecture etc): Show everything EXCEPT reviews and suggestions #}
{% if not ("/review/" in post.url) and not ("/suggestion/" in post.url) %}
{% set show_post = true %}
{% endif %}
{% endif %}
{# Hide current page from list if it is an index page to avoid redundancy, or keep it? User said 'only reviews', usually implies lists of *other* reviews or all reviews. #}
{# Check if it's an index page by checking if the URL ends in a slash or index.html logic, but usually simple filter is enough. #}
{% if show_post %}
{% set is_main = not ("/review/" in post.url or "/suggestion/" in post.url) %}
{% if is_main and post.data.title %}
<a href="{{ post.url }}" class="block px-3 py-2 rounded-lg text-sm transition-colors {% if page.url == post.url %}bg-primary/10 text-primary font-medium border-l-2 border-primary{% else %}text-base-content/70 hover:bg-base-300 hover:text-base-content{% endif %}">
{{ post.data.title }}
</a>
@ -56,10 +29,50 @@ layout: base.njk
{% endfor %}
</nav>
</div>
<!-- Suggestions Section -->
<div class="mb-6">
<a href="/projects/clqms01/suggestion/" class="flex items-center gap-2 font-bold text-sm text-base-content/50 uppercase tracking-wider mb-3 hover:text-success transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
</svg>
Suggestions
</a>
<nav class="space-y-1">
{% for post in collections.clqms %}
{% if "/suggestion/" in post.url and post.data.title and post.url != "/projects/clqms01/suggestion/" %}
<a href="{{ post.url }}" class="block px-3 py-2 rounded-lg text-sm transition-colors {% if page.url == post.url %}bg-success/10 text-success font-medium border-l-2 border-success{% else %}text-base-content/70 hover:bg-base-300 hover:text-base-content{% endif %}">
{{ post.data.title }}
</a>
{% endif %}
{% endfor %}
</nav>
</div>
<!-- Reviews Section -->
<div>
<a href="/projects/clqms01/review/" class="flex items-center gap-2 font-bold text-sm text-base-content/50 uppercase tracking-wider mb-3 hover:text-accent transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
Reviews
</a>
<nav class="space-y-1">
{% for post in collections.clqms %}
{% if "/review/" in post.url and post.data.title and post.url != "/projects/clqms01/review/" %}
<a href="{{ post.url }}" class="block px-3 py-2 rounded-lg text-sm transition-colors {% if page.url == post.url %}bg-accent/10 text-accent font-medium border-l-2 border-accent{% else %}text-base-content/70 hover:bg-base-300 hover:text-base-content{% endif %}">
{{ post.data.title }}
</a>
{% endif %}
{% endfor %}
</nav>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 min-w-0">
<article>
<main class="flex-1 min-w-0 max-w-3xl">
<article class="pb-12">
<!-- Post header -->
<header class="mb-10">
<div class="flex flex-wrap gap-2 mb-4">
@ -69,15 +82,11 @@ layout: base.njk
{% endif %}
{% endfor %}
</div>
<h1 class="text-4xl md:text-5xl font-bold mb-4">{{ title }}</h1>
<h1 class="text-3xl md:text-4xl font-bold mb-4">{{ title }}</h1>
<div class="flex flex-wrap items-center gap-4 text-base-content/60">
<time datetime="{{ date | dateFormat('iso') }}" class="flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
</svg>
{{ date | dateFormat('full') }}
</time>
@ -90,7 +99,7 @@ layout: base.njk
</div>
</header>
<!-- Post content -->
<div class="prose-custom max-w-none">
<div class="prose-custom">
{{ content | safe }}
</div>
</article>

View File

@ -2,8 +2,8 @@
layout: base.njk
---
<article class="section-container py-12 animate-slide-up">
<div>
<article class="py-12 animate-slide-up">
<div class="max-w-3xl mx-auto px-4 sm:px-6">
<!-- Post header -->
<header class="mb-10">
<div class="flex flex-wrap gap-2 mb-4">

View File

@ -507,14 +507,6 @@
ORIGINAL CUSTOM COMPONENTS
======================================== */
/* Gradient text effect */
.gradient-text {
background: linear-gradient(to right, var(--color-primary), var(--color-secondary), var(--color-accent));
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Glass morphism card - uses theme-aware colors */
.glass-card {
background-color: var(--color-base-200);
@ -524,25 +516,19 @@
box-shadow: 0 10px 40px -10px oklch(0 0 0 / 0.2);
}
/* Hero section styles */
.hero-gradient {
background: linear-gradient(135deg, var(--color-base-100), var(--color-base-200), var(--color-base-100));
background-size: 400% 400%;
animation: gradient 15s ease infinite;
/* Hero section styles - solid background */
.hero-solid {
background-color: var(--color-base-100);
}
@keyframes gradient {
0% {
background-position: 0% 50%;
/* Brand text styling */
.brand-text {
color: var(--color-primary);
font-weight: 700;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
.brand-text:hover {
opacity: 0.8;
}
/* Project card hover effect - uses theme-aware colors */
@ -600,9 +586,18 @@
border-left: 2px solid var(--color-primary);
}
/* Section container */
/* Section container - narrow for reading */
.section-container {
max-width: 80rem;
max-width: 48rem;
margin-left: auto;
margin-right: auto;
padding-left: 1rem;
padding-right: 1rem;
}
/* Wider container for grids and layouts */
.section-container-wide {
max-width: 72rem;
margin-left: auto;
margin-right: auto;
padding-left: 1rem;
@ -610,14 +605,16 @@
}
@media (min-width: 640px) {
.section-container {
.section-container,
.section-container-wide {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
}
@media (min-width: 1024px) {
.section-container {
.section-container,
.section-container-wide {
padding-left: 2rem;
padding-right: 2rem;
}
@ -968,4 +965,27 @@
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Custom scrollbar for sidenav */
.scrollbar-thin {
scrollbar-width: thin;
scrollbar-color: var(--color-base-300) transparent;
}
.scrollbar-thin::-webkit-scrollbar {
width: 6px;
}
.scrollbar-thin::-webkit-scrollbar-track {
background: transparent;
}
.scrollbar-thin::-webkit-scrollbar-thumb {
background-color: var(--color-base-300);
border-radius: 3px;
}
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
background-color: var(--color-neutral);
}
}

View File

@ -5,26 +5,25 @@ description: Innovative projects and ideas
---
<!-- Hero Section -->
<section class="relative min-h-[85vh] flex items-center overflow-hidden">
<section class="hero-solid min-h-[70vh] flex items-center relative overflow-hidden">
<!-- Background decoration -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="absolute -top-40 -right-40 w-80 h-80 bg-primary/20 rounded-full blur-3xl animate-float"></div>
<div class="absolute -bottom-40 -left-40 w-80 h-80 bg-secondary/20 rounded-full blur-3xl animate-float animate-delay-300"></div>
<div class="absolute -top-40 -right-40 w-80 h-80 bg-primary/10 rounded-full blur-3xl"></div>
<div class="absolute -bottom-40 -left-40 w-80 h-80 bg-secondary/10 rounded-full blur-3xl"></div>
</div>
<div class="container mx-auto px-6 relative z-10">
<div class="max-w-4xl mx-auto text-center">
<div class="section-container-wide relative z-10">
<div class="max-w-3xl mx-auto text-center">
<div class="animate-slide-up">
<span class="badge badge-primary badge-outline badge-lg mb-6">Development Team</span>
<h1 class="text-5xl md:text-7xl font-bold mb-6 leading-tight">
<h1 class="text-4xl md:text-6xl font-bold mb-6 leading-tight">
Building the<br>
Future of Tech.
<span class="text-primary">Future of Tech.</span>
</h1>
<p class="text-xl md:text-2xl text-base-content/70 mb-10 max-w-2xl mx-auto">
<p class="text-lg md:text-xl text-base-content/70 mb-10 max-w-2xl mx-auto">
We are a team of passionate developers, designers, and innovators crafting digital experiences that matter.
</p>
<div class="flex flex-wrap justify-center gap-4">
<a href="/team/" class="btn btn-primary btn-lg gap-2 animate-glow">
<a href="/team/" class="btn btn-primary btn-lg gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
</svg>
@ -34,11 +33,10 @@ description: Innovative projects and ideas
</div>
</div>
</div>
</div>
</section>
<!-- Portfolio Section -->
<section class="py-20">
<div class="section-container">
<div class="section-container-wide">
<div class="text-center mb-12">
<span class="badge badge-accent badge-outline mb-4">Portfolio</span>
<h2 class="text-3xl md:text-4xl font-bold mb-4">Our Work</h2>
@ -85,7 +83,7 @@ description: Innovative projects and ideas
<!-- CLQMS Projects Section -->
{% if collections.clqms.length > 0 %}
<section class="py-20 bg-base-200/30">
<div class="section-container">
<div class="section-container-wide">
<div class="text-center mb-12">
<span class="badge badge-secondary badge-outline mb-4">CLQMS</span>
<h2 class="text-3xl md:text-4xl font-bold mb-4">Project Documentation</h2>

View File

@ -6,76 +6,129 @@ date: 2025-12-01
order: 0
---
<!-- Hero Section -->
<section class="pt-20 pb-12 bg-base-200/30 overflow-hidden relative">
<div class="section-container relative z-10">
<div class="text-center mb-12">
<div class="w-full px-4 sm:px-6 lg:px-8 py-6 animate-slide-up">
<div class="flex flex-col lg:flex-row gap-8 max-w-6xl mx-auto">
<!-- Sidebar Navigation - Sticky on scroll -->
<aside class="lg:w-64 flex-shrink-0 order-2 lg:order-1">
<div class="lg:sticky lg:top-24 bg-base-200/50 backdrop-blur-xl border border-white/5 rounded-2xl p-6">
<!-- Core Documentation -->
<div class="mb-6">
<h3 class="font-bold text-sm text-base-content/50 uppercase tracking-wider mb-3 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
Core Docs
</h3>
<nav class="space-y-1">
{% for post in collections.clqms %}
{% set is_main = not ("/review/" in post.url or "/suggestion/" in post.url) %}
{% if is_main and post.data.title %}
<a href="{{ post.url }}" class="block px-3 py-2 rounded-lg text-sm transition-colors {% if page.url == post.url %}bg-primary/10 text-primary font-medium border-l-2 border-primary{% else %}text-base-content/70 hover:bg-base-300 hover:text-base-content{% endif %}">
{{ post.data.title }}
</a>
{% endif %}
{% endfor %}
</nav>
</div>
<!-- Suggestions Section -->
<div class="mb-6">
<a href="/projects/clqms01/suggestion/" class="flex items-center gap-2 font-bold text-sm text-base-content/50 uppercase tracking-wider mb-3 hover:text-success transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
</svg>
Suggestions
</a>
<nav class="space-y-1">
{% for post in collections.clqms %}
{% if "/suggestion/" in post.url and post.data.title and post.url != "/projects/clqms01/suggestion/" %}
<a href="{{ post.url }}" class="block px-3 py-2 rounded-lg text-sm transition-colors {% if page.url == post.url %}bg-success/10 text-success font-medium border-l-2 border-success{% else %}text-base-content/70 hover:bg-base-300 hover:text-base-content{% endif %}">
{{ post.data.title }}
</a>
{% endif %}
{% endfor %}
</nav>
</div>
<!-- Reviews Section -->
<div>
<a href="/projects/clqms01/review/" class="flex items-center gap-2 font-bold text-sm text-base-content/50 uppercase tracking-wider mb-3 hover:text-accent transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
</svg>
Reviews
</a>
<nav class="space-y-1">
{% for post in collections.clqms %}
{% if "/review/" in post.url and post.data.title and post.url != "/projects/clqms01/review/" %}
<a href="{{ post.url }}" class="block px-3 py-2 rounded-lg text-sm transition-colors {% if page.url == post.url %}bg-accent/10 text-accent font-medium border-l-2 border-accent{% else %}text-base-content/70 hover:bg-base-300 hover:text-base-content{% endif %}">
{{ post.data.title }}
</a>
{% endif %}
{% endfor %}
</nav>
</div>
</div>
</aside>
<!-- Main Content -->
<main class="flex-1 min-w-0 max-w-3xl order-1 lg:order-2">
<div class="pb-12">
<!-- Hero Section -->
<section class="mb-12">
<div class="text-center lg:text-left">
<span class="badge badge-secondary badge-outline mb-4">Project Workspace</span>
<h1 class="text-4xl md:text-5xl font-bold mb-4">Clinical Laboratory Quality Management System</h1>
<p class="text-base-content/70 max-w-2xl mx-auto">
<h1 class="text-3xl md:text-4xl font-bold mb-4">Clinical Laboratory Quality Management System</h1>
<p class="text-base-content/70 max-w-2xl">
A robust, mission-critical API suite designed to streamline modern laboratory operations through precision, scalability,
and modular design.
</p>
</div>
</div>
</section>
<!-- Module Navigation -->
<section id="modules" class="py-12 bg-base-100">
<div class="section-container">
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<a
href="/projects/clqms01/001-architecture/"
class="card bg-base-200 hover:bg-base-300 transition-all hover:-translate-y-1 border border-white/5">
<div class="card-body p-6">
</section>
<!-- Module Navigation -->
<section id="modules" class="mb-12">
<h2 class="text-xl font-bold mb-6 flex items-center gap-2">
<span class="text-primary opacity-50">#</span>
Quick Access
</h2>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-4">
<a href="#docs" class="card bg-base-200 hover:bg-base-300 transition-all hover:-translate-y-1 border border-white/5 p-6">
<div class="flex items-center gap-3 mb-2">
<span class="p-2 rounded bg-primary/10 text-primary text-xl">🏗️</span>
<h2 class="card-title text-base">Architecture</h2>
</div>
<p class="text-sm text-base-content/70">Core system design, authentication, and strategic pillars</p>
<h3 class="font-bold text-base">Core Docs</h3>
</div>
<p class="text-sm text-base-content/70">Architecture, auth, and technical docs</p>
</a>
<!-- Authentication moved to Core Documentation list below -->
<a
href="/projects/clqms01/suggestion/"
class="card bg-base-200 border border-success/20 hover:bg-success/5 transition-all hover:-translate-y-1">
<div class="card-body p-6">
<a href="/projects/clqms01/suggestion/" class="card bg-base-200 border border-success/20 hover:bg-success/5 transition-all hover:-translate-y-1 p-6">
<div class="flex items-center gap-3 mb-2">
<span class="p-2 rounded bg-success/10 text-success text-xl">💡</span>
<h2 class="card-title text-base">Suggestions</h2>
<h3 class="font-bold text-base">Suggestions</h3>
</div>
<p class="text-sm text-base-content/70">Future proposals and roadmap ideas</p>
</div>
</a>
<a
href="/projects/clqms01/review/"
class="card bg-base-200 border border-accent/20 hover:bg-accent/5 transition-all hover:-translate-y-1">
<div class="card-body p-6">
<a href="/projects/clqms01/review/" class="card bg-base-200 border border-accent/20 hover:bg-accent/5 transition-all hover:-translate-y-1 p-6">
<div class="flex items-center gap-3 mb-2">
<span class="p-2 rounded bg-accent/10 text-accent text-xl">🔍</span>
<h2 class="card-title text-base">Reviews</h2>
</div>
<p class="text-sm text-base-content/70">Expert code reviews and technical audits</p>
<h3 class="font-bold text-base">Reviews</h3>
</div>
<p class="text-sm text-base-content/70">Expert code reviews and audits</p>
</a>
</div>
</div>
</section>
<!-- Core Documentation -->
<section id="docs" class="py-12">
<div class="section-container">
<div class="flex items-center justify-between mb-8">
<h2 class="text-2xl font-bold flex items-center gap-2">
</section>
<!-- Core Documentation -->
<section id="docs" class="mb-12">
<h2 class="text-xl font-bold mb-6 flex items-center gap-2">
<span class="text-secondary opacity-50">#</span>
Core Documentation
</h2>
</div>
<div class="max-w-4xl mx-auto space-y-2">
<div class="space-y-2">
{% for post in collections.clqms %}
{# Filter to show only root-level docs, excluding index and sub-directories #}
{% set is_suggestion = "/suggestion/" in post.url %}
{% set is_review = "/review/" in post.url %}
{% if post.url != page.url and not is_suggestion and not is_review and post.data.title %}
<a href="{{ post.url }}" title="{{ post.inputPath }}" class="group block p-5 rounded-xl bg-base-200/50 hover:bg-base-200 border border-base-content/10 transition-all">
<a href="{{ post.url }}" title="{{ post.inputPath }}" class="group block p-4 rounded-xl bg-base-200/50 hover:bg-base-200 border border-base-content/10 transition-all">
<div class="flex flex-col md:flex-row md:items-center justify-between gap-2">
<div>
<h3 class="text-lg font-bold group-hover:text-secondary transition-colors mb-1">{{ post.data.title }}</h3>
@ -83,45 +136,40 @@ order: 0
{{ post.data.description | default('Technical reference for CLQMS implementation.') }}
</p>
</div>
<div class="flex items-center gap-4 text-xs font-mono text-base-content/40 whitespace-nowrap">
<span class="hidden md:inline">{{ post.content | readingTime }}</span>
</div>
<span class="hidden md:inline text-xs font-mono text-base-content/40 whitespace-nowrap">{{ post.content | readingTime }}</span>
</div>
</a>
{% endif %}
{% endfor %}
</div>
</div>
</section>
<!-- Strategy Section -->
<section class="py-12 bg-base-200/30">
<div class="section-container">
<div class="max-w-4xl mx-auto">
<h2 class="text-2xl font-bold mb-8 text-center md:text-left">Strategic Pillars</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="glass-card p-6 border border-white/5 rounded-2xl">
</section>
<!-- Strategy Section -->
<section class="bg-base-200/30 rounded-2xl p-6 border border-white/5">
<h2 class="text-xl font-bold mb-6">Strategic Pillars</h2>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<div class="glass-card p-5 border border-white/5 rounded-xl">
<h3 class="text-lg font-bold mb-2 flex items-center gap-2">
<span class="text-primary">🎯</span>
Precision & Accuracy
</h3>
<p class="text-base-content/70 text-sm">Strict validation for all laboratory parameters and multi-variant reference
ranges.</p>
<p class="text-base-content/70 text-sm">Strict validation for all laboratory parameters and multi-variant reference ranges.</p>
</div>
<div class="glass-card p-6 border border-white/5 rounded-2xl">
<div class="glass-card p-5 border border-white/5 rounded-xl">
<h3 class="text-lg font-bold mb-2 flex items-center gap-2">
<span class="text-secondary">⚡</span>
Scalability
</h3>
<p class="text-base-content/70 text-sm">Optimized database and API architecture for high-volume diagnostic environments.</p>
</div>
<div class="glass-card p-6 border border-white/5 rounded-2xl">
<div class="glass-card p-5 border border-white/5 rounded-xl">
<h3 class="text-lg font-bold mb-2 flex items-center gap-2">
<span class="text-success">📜</span>
Compliance
</h3>
<p class="text-base-content/70 text-sm">Built-in audit trails and granular status history for full medical traceability.</p>
</div>
<div class="glass-card p-6 border border-white/5 rounded-2xl">
<div class="glass-card p-5 border border-white/5 rounded-xl">
<h3 class="text-lg font-bold mb-2 flex items-center gap-2">
<span class="text-accent">🔗</span>
Interoperability
@ -129,6 +177,8 @@ order: 0
<p class="text-base-content/70 text-sm">Modular drivers designed for seamless LIS, HIS, and analyzer integration.</p>
</div>
</div>
</section>
</div>
</main>
</div>
</section>
</div>

View File

@ -2,14 +2,13 @@
layout: clqms-post.njk
tags: clqms
title: "Proposal: Test Definition Architecture Overhaul"
description: "Simplify database schema and improve query performance for test definitions"
description: "Remove magic numbers and enforce type safety using PHP Enums and Svelte stores"
date: 2026-01-09
order: 1
---
# 🚀 Proposal: Test Definition Architecture Overhaul by Gemini 3
**Target:** `testdef` Module
**Objective:** Simplify database schema, improve query performance, and reduce code complexity.
@ -19,25 +18,26 @@ order: 1
**Current Status:**
Defining a single Lab Test currently requires joining 4-5 rigid tables:
* `testdefsite` (General Info)
* `testdeftech` (Technical Details)
* `testdefcal` (Calculations)
* `testdefgrp` (Grouping)
- `testdefsite` (General Info)
- `testdeftech` (Technical Details)
- `testdefcal` (Calculations)
- `testdefgrp` (Grouping)
**Why it hurts:**
* **Complex Queries:** To get a full test definition, we write massive SQL joins.
* **Rigid Schema:** Adding a new technical attribute requires altering table schemas and updating multiple DAO files.
* **Maintenance Nightmare:** Logic is scattered. To understand a test, you have to look in five places.
- **Complex Queries:** To get a full test definition, we write massive SQL joins.
- **Rigid Schema:** Adding a new technical attribute requires altering table schemas and updating multiple DAO files.
- **Maintenance Nightmare:** Logic is scattered. To understand a test, you have to look in five places.
---
## 2. The Solution: JSON Configuration 📄
**Strategy:** Treat a Test Definition as a **Document**.
We will consolidate the variable details (Technique, Calculations, Reference Ranges) into a structured `JSON` column within a single table.
### Schema Change
Old 5 tables $\rightarrow$ **1 Main Table**.
Old 5 tables **1 Main Table**.
```sql
CREATE TABLE LabTestDefinitions (
@ -61,45 +61,73 @@ Instead of columns for every possible biological variable, we store a flexible d
"specimen": "Serum",
"result_type": "NUMERIC",
"units": "mg/dL",
"formulas": {
"calculation": "primary_result * dilution_factor"
},
"reference_ranges": [
{
"label": "Adult Male",
"sex": "M",
"min_age": 18,
"max_age": 99,
"min_val": 70,
"max_val": 100
},
{
"label": "Pediatric",
"max_age": 18,
"min_val": 60,
"max_val": 90
}
{ "sex": "M", "min": 70, "max": 100 },
{ "sex": "F", "min": 60, "max": 90 }
]
}
```
---
## 3. The Benefits 🏆
## 3. How to Query (The Magic) 🪄
This is where the new design shines. No more joins.
### A. Fetching a Test (The Usual Way)
Just select the row. The application gets the full definition instantly.
```sql
SELECT * FROM LabTestDefinitions WHERE code = 'GLUC';
```
### B. Searching Inside JSON (The Cool Way)
Need to find all tests that use "Serum"? Use the JSON arrow operator (`->>`).
**MySQL / MariaDB:**
```sql
SELECT code, name
FROM LabTestDefinitions
WHERE configuration->>'$.specimen' = 'Serum';
```
**PostgreSQL:**
```sql
SELECT code, name
FROM LabTestDefinitions
WHERE configuration->>'specimen' = 'Serum';
```
### C. Performance Optimization 🏎️
If we search by "Technique" often, we don't index the JSON string. We add a Generated Column.
```sql
ALTER TABLE LabTestDefinitions
ADD COLUMN technique_virtual VARCHAR(50)
GENERATED ALWAYS AS (configuration->>'$.technique') VIRTUAL;
CREATE INDEX idx_technique ON LabTestDefinitions(technique_virtual);
```
**Result:** Querying the JSON is now as fast as a normal column.
---
## 4. The Benefits 🏆
| Feature | Old Way (Relational) | New Way (JSON Document) |
| :--- | :--- | :--- |
| **Fetch Speed** | Slow (4+ Joins) | Instant (1 Row Select) |
| **Flexibility** | Requires ALTER TABLE | Edit JSON & Save |
| **Search** | Complex SQL | Fast JSON Indexing |
| **Flexibility** | Requires `ALTER TABLE` | Edit JSON & Save |
| **Search** | Complex SQL | Fast JSON Operators |
| **Code Logic** | Mapping 5 SQL results | `json_decode()` → Object |
---
### Next Steps 🗒️
## Next Steps
- [ ] Create migration for `LabTestDefinitions` table.
- [ ] Port 5 sample tests from the old structure to JSON format for verification.
---
_Last updated: 2026-01-09 08:40:21_
1. Create migration for `LabTestDefinitions` table.
2. Port 5 sample tests from the old structure to JSON format for verification.

View File

@ -7,40 +7,105 @@ date: 2026-01-09
order: 2
---
# 🚀 Proposal: Valueset ("God Table") Replacement by Gemini 3
# 🚀 Proposal: Valueset ("God Table") Replacement
**Target:** `valueset` / `valuesetdef` Tables
**Objective:** Remove "Magic Numbers," enforce Type Safety, and optimize Frontend performance.
**Objective:** Eliminate "Magic Numbers," enforce Type Safety, and optimize Frontend performance by moving system logic into the codebase.
---
## 1. The Problem: "Magic Number Soup" 🥣
**Current Status:**
We store disparate system logic (Gender, Test Status, Colors, Payment Types) in a single massive table called `valueset`.
* **Code relies on IDs:** `if ($status == 1045) ...`
* **Frontend Overload:** Frontend makes frequent DB calls just to populate simple dropdowns.
* **No Type Safety:** Nothing stops a developer from assigning a "Payment Status" ID to a "Gender" column.
We store disparate logic (Gender, Test Status, Specimen Types, Priority) in a single massive table called `valueset`.
* **Code relies on IDs:** Developers must remember that `1045` means `VERIFIED`. `if ($status == 1045)` is unreadable.
* **Frontend Overload:** The frontend makes frequent, redundant database calls just to populate simple dropdowns.
* **No Type Safety:** Nothing prevents assigning a "Payment Status" ID to a "Gender" column.
* **Invisible History:** Database changes are hard to track. Who changed "Verified" to "Authorized"? When? Why?
---
## 2. The Solution: Enums & API Store 🛠️
## 2. The Solution: Single Source of Truth (SSOT) 🛠️
**Strategy:** Split "System Logic" from the Database.
Use **PHP 8.1 Native Enums** for business rules and serve them via a cached API to Svelte.
**Strategy:** Move "System Logic" from the Database into the Code. This creates a "Single Source of Truth."
### Step A: The Backend (PHP Enums)
We delete the rows from the database and define them in code where they belong.
### Step A: The Backend Implementation
#### Option 1: The "God File" (Simple & Legacy Friendly)
**File:** `application/libraries/Valuesets.php`
This file holds every dropdown option in the system. Benefits include **Ctrl+Click** navigation and clear **Git History**.
```php
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
class Valuesets {
// 1. Gender Definitions
const GENDER = [
'M' => 'Male',
'F' => 'Female',
'U' => 'Unknown'
];
// 2. Test Status
const TEST_STATUS = [
'PENDING' => 'Waiting for Results',
'IN_PROCESS' => 'Analyzing',
'VERIFIED' => 'Verified & Signed',
'REJECTED' => 'Sample Rejected'
];
// 3. Sample Types
const SPECIMEN_TYPE = [
'SERUM' => 'Serum',
'PLASMA' => 'Plasma',
'URINE' => 'Urine',
'WB' => 'Whole Blood'
];
// 4. Urgency
const PRIORITY = [
'ROUTINE' => 'Routine',
'CITO' => 'Cito (Urgent)',
'STAT' => 'Immediate (Life Threatening)'
];
/**
* Helper to get all constants as one big JSON object.
* Useful for sending everything to Svelte in one go.
*/
public static function getAll() {
return [
'gender' => self::mapForFrontend(self::GENDER),
'test_status' => self::mapForFrontend(self::TEST_STATUS),
'specimen_type' => self::mapForFrontend(self::SPECIMEN_TYPE),
'priority' => self::mapForFrontend(self::PRIORITY),
];
}
// Helper to format array like: [{value: 'M', label: 'Male'}, ...]
private static function mapForFrontend($array) {
$result = [];
foreach ($array as $key => $label) {
$result[] = ['value' => $key, 'label' => $label];
}
return $result;
}
}
```
#### Option 2: PHP 8.1+ Enums (Modern Approach)
**File:** `App/Enums/TestStatus.php`
```php
enum TestStatus: string {
case PENDING = 'PENDING';
case VERIFIED = 'VERIFIED';
case REJECTED = 'REJECTED';
// Helper for Frontend Labels
public function label(): string {
return match($this) {
self::PENDING => 'Waiting for Results',
@ -51,39 +116,42 @@ enum TestStatus: string {
}
```
### Step B: The API Contract
---
**GET `/api/config/valueset`**
Instead of 20 small network requests for 20 dropdowns, the Frontend requests the entire dictionary once on load.
## 3. The "Aha!" Moment (Usage) 🔌
**Response:**
```json
{
"test_status": [
{ "value": "PENDING", "label": "Waiting for Results" },
{ "value": "VERIFIED", "label": "Verified & Signed" }
],
"gender": [
{ "value": "M", "label": "Male" },
{ "value": "F", "label": "Female" }
]
### 1. In PHP Logic
No more querying the DB to check if a status is valid. Logic is now instant and readable.
```php
// ❌ Old Way (Sick) 🤮
// $status = $this->db->get_where('valuesetdef', ['id' => 505])->row();
if ($status_id == 505) { ... }
// ✅ New Way (Clean) 😎
if ($input_status === 'VERIFIED') {
// We know exactly what this string means.
send_email_to_patient();
}
// Validation is instant
if (!array_key_exists($input_gender, Valuesets::GENDER)) {
die("Invalid Gender!");
}
```
### Step C: The Frontend (Svelte Store)
### 2. In Svelte (Frontend - Svelte 5 Runes)
We use a global state (rune) to store this configuration. The frontend engineers no longer need to check database IDs; they just use the human-readable keys.
We use a Svelte Store to cache this data globally. No more SQL queries for dropdowns.
**Component Usage:**
```svelte
{% raw %}
<script>
import { config } from '../stores/configStore';
import { config } from '../stores/config.svelte';
</script>
<label>Status:</label>
<select bind:value={status}>
{#each $config.test_status as option}
<label>Specimen Type:</label>
<select bind:value={specimen}>
{#each config.valueset.specimen_type as option}
<option value={option.value}>{option.label}</option>
{/each}
</select>
@ -92,22 +160,24 @@ We use a Svelte Store to cache this data globally. No more SQL queries for dropd
---
## 3. The Benefits 🏆
## 4. Why This is Better 🏆
| Feature | Old Way (valueset Table) | New Way (Enums + Store) |
| Feature | Old Way (`valueset` Table) | New Way (The God File) |
| :--- | :--- | :--- |
| **Performance** | DB Query per dropdown | Zero DB Hits (Cached) |
| **Code Quality** | `if ($id == 505)` | `if ($s == Status::PENDING)` |
| **Reliability** | IDs can change/break | Code is immutable |
| **Network** | "Chatty" (Many requests) | Efficient (One request) |
| **Performance** | DB Query per dropdown | **Millions of times faster** (Constant) |
| **Readability** | `if ($id == 505)` | `if ($status == 'REJECTED')` |
| **Navigation** | Search DB rows | **Ctrl+Click** in VS Code |
| **Git History** | Opaque/Invisible | **Transparent** (See who changed what) |
| **Reliability** | IDs can change/break | Constants are immutable |
---
### Next Steps 🗒️
## 5. Next Steps 🗒️
- [ ] Define `TestStatus` and `TestType` Enums in PHP.
- [ ] Create the `/api/config/valueset` endpoint.
- [ ] Update one Svelte form to use the new Store instead of an API fetch.
- [ ] Implement `Valuesets.php` in the libraries folder.
- [ ] Create a controller to expose `Valuesets::getAll()` as JSON.
- [ ] Update the Svelte `configStore` to fetch this dictionary on app load.
- [ ] Refactor the Patient Registration form to use the new constants.
---
_Last updated: 2026-01-09 08:40:21_
_Last updated: 2026-01-09_

View File

@ -0,0 +1,61 @@
---
layout: clqms-post.njk
tags: clqms
title: "Backend Dev: Implementation Plan"
description: "Consolidated developer review for suggestions 001 and 002"
date: 2026-01-09
order: 3
---
# 🛠️ Backend Dev: Decision for 001 & 002
This document summarizes the technical decision and implementation plan based on the previous proposals:
- [001-testdef.md](./001-testdef.md) (Test Definition Architecture)
- [002-valueset.md](./002-valueset.md) (Valueset Replacement)
---
## 1. Suggestion 001: Test Definition Architecture
**Action: DEFERRED**
- We will skip the architectural overhaul of the `testdef` module for now.
- Current table structure remains unchanged to maintain stability.
---
## 2. Suggestion 002: Valueset Implementation
**Action: ADOPTED (Code-First Migration)**
We are moving forward with moving system constants from the database into the codebase, but with a focus on existing API compatibility.
### Technical Requirements:
- **Maintain Endpoint integrity**: The API endpoint must remain functional without changes to the frontend.
- **Data Migration**: Move values (Gender, Status, etc.) from the `valueset` table into a centralized PHP constant or config file named `Valuesets.php`.
- **Controller Refactor**: Update the backend controller to serve the JSON response from the file rather than a database query.
---
## 3. Translation Logic (Getting labels like "Female" from "F")
When pulling data from the database, you can translate raw keys into readable labels instantly using the new class.
### Example PHP Usage:
```php
// Direct mapping in your controller or view
$genderLabel = Valuesets::GENDER[$patient->gender] ?? 'Unknown';
// Or use a helper method in the Valuesets class
public static function getLabel($category, $key) {
return self::{$category}[$key] ?? $key;
}
```
---
## 4. Implementation Steps
1. Create `Valuesets.php` in the backend libraries folder.
2. Refactor `Valueset` controller to return data from `Valuesets` class.
3. Verify JSON output matches the current schema to ensure no frontend breaks.
---
_Last updated: 2026-01-09 12:47:00_

View File

@ -0,0 +1,482 @@
---
layout: clqms-post.njk
tags: clqms
title: "CLQMS: Audit Logging Architecture Plan"
description: "Comprehensive audit trail strategy for tracking changes across master data, patient records, and laboratory operations"
date: 2026-02-19
order: 4
---
# Audit Logging Architecture Plan for CLQMS
> **Clinical Laboratory Quality Management System (CLQMS)** - A comprehensive audit trail strategy for tracking changes across master data, patient records, and laboratory operations.
---
## Executive Summary
This document outlines a unified audit logging architecture for CLQMS, designed to provide complete traceability of data changes while maintaining optimal performance and maintainability. The approach separates audit logs into three domain-specific tables, utilizing JSON for flexible value storage.
---
## 1. Current State Analysis
### Existing Audit Infrastructure
| Aspect | Current Status |
|--------|---------------|
| **Database Tables** | 3 tables exist in migrations (patreglog, patvisitlog, specimenlog) |
| **Implementation** | Tables created but not actively used |
| **Structure** | Fixed column approach (FldName, FldValuePrev) |
| **Code Coverage** | No models or controllers implemented |
| **Application Logging** | Basic CodeIgniter file logging for debug/errors |
### Pain Points Identified
- ❌ **3 separate tables** with nearly identical schemas
- ❌ **Fixed column structure** - rigid and requires schema changes for new entities
- ❌ **No implementation** - audit tables exist but aren't populated
- ❌ **Maintenance overhead** - adding new entities requires new migrations
---
## 2. Proposed Architecture
### 2.1 Domain Separation
We categorize audit logs by **data domain** and **access patterns**:
| Table | Domain | Volume | Retention | Use Case |
|-------|--------|--------|-----------|----------|
| `master_audit_log` | Reference Data | Low | Permanent | Organizations, Users, ValueSets |
| `patient_audit_log` | Patient Records | Medium | 7 years | Demographics, Contacts, Insurance |
| `order_audit_log` | Operations | High | 2 years | Orders, Tests, Specimens, Results |
### 2.2 Unified Table Structure
#### Master Audit Log
```sql
CREATE TABLE master_audit_log (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
entity_type VARCHAR(50) NOT NULL, -- 'organization', 'user', 'valueset'
entity_id VARCHAR(36) NOT NULL, -- UUID or primary key
action ENUM('CREATE', 'UPDATE', 'DELETE', 'PATCH') NOT NULL,
old_values JSON NULL, -- Complete snapshot before change
new_values JSON NULL, -- Complete snapshot after change
changed_fields JSON, -- Array of modified field names
-- Context
user_id VARCHAR(36),
site_id VARCHAR(36),
ip_address VARCHAR(45),
user_agent VARCHAR(500),
app_version VARCHAR(20),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_entity (entity_type, entity_id),
INDEX idx_created (created_at),
INDEX idx_user (user_id, created_at)
) ENGINE=InnoDB;
```
#### Patient Audit Log
```sql
CREATE TABLE patient_audit_log (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
entity_type VARCHAR(50) NOT NULL, -- 'patient', 'contact', 'insurance'
entity_id VARCHAR(36) NOT NULL,
patient_id VARCHAR(36), -- Context FK for patient
action ENUM('CREATE', 'UPDATE', 'DELETE', 'MERGE', 'UNMERGE') NOT NULL,
old_values JSON NULL,
new_values JSON NULL,
changed_fields JSON,
reason TEXT, -- Why the change was made
-- Context
user_id VARCHAR(36),
site_id VARCHAR(36),
ip_address VARCHAR(45),
session_id VARCHAR(100),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_entity (entity_type, entity_id),
INDEX idx_patient (patient_id, created_at),
INDEX idx_created (created_at),
INDEX idx_user (user_id, created_at)
) ENGINE=InnoDB;
```
#### Order/Test Audit Log
```sql
CREATE TABLE order_audit_log (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
entity_type VARCHAR(50) NOT NULL, -- 'order', 'test', 'specimen', 'result'
entity_id VARCHAR(36) NOT NULL,
-- Context FKs
patient_id VARCHAR(36),
visit_id VARCHAR(36),
order_id VARCHAR(36),
action ENUM('CREATE', 'UPDATE', 'DELETE', 'CANCEL', 'REORDER', 'COLLECT', 'RESULT') NOT NULL,
old_values JSON NULL,
new_values JSON NULL,
changed_fields JSON,
status_transition VARCHAR(100), -- e.g., 'pending->collected'
-- Context
user_id VARCHAR(36),
site_id VARCHAR(36),
device_id VARCHAR(36), -- Instrument/edge device
ip_address VARCHAR(45),
session_id VARCHAR(100),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_entity (entity_type, entity_id),
INDEX idx_order (order_id, created_at),
INDEX idx_patient (patient_id, created_at),
INDEX idx_created (created_at),
INDEX idx_user (user_id, created_at)
) ENGINE=InnoDB;
```
---
## 3. JSON Value Structure
### Example Audit Entry
```json
{
"id": 15243,
"entity_type": "patient",
"entity_id": "PAT-2026-001234",
"action": "UPDATE",
"old_values": {
"NameFirst": "John",
"NameLast": "Doe",
"Gender": "M",
"BirthDate": "1990-01-15",
"Phone": "+1-555-0100"
},
"new_values": {
"NameFirst": "Johnny",
"NameLast": "Doe-Smith",
"Gender": "M",
"BirthDate": "1990-01-15",
"Phone": "+1-555-0199"
},
"changed_fields": ["NameFirst", "NameLast", "Phone"],
"user_id": "USR-001",
"site_id": "SITE-001",
"created_at": "2026-02-19T14:30:00Z"
}
```
### Benefits of JSON Approach
**Schema Evolution** - Add new fields without migrations
**Complete Snapshots** - Reconstruct full record state at any point
**Flexible Queries** - MySQL 8.0+ supports JSON indexing and extraction
**Audit Integrity** - Store exactly what changed, no data loss
---
## 4. Implementation Strategy
### 4.1 Central Audit Service
```php
<?php
namespace App\Services;
class AuditService
{
/**
* Log an audit event to the appropriate table
*/
public static function log(
string $category, // 'master', 'patient', 'order'
string $entityType, // e.g., 'patient', 'order'
string $entityId,
string $action,
?array $oldValues = null,
?array $newValues = null,
?string $reason = null,
?array $context = null
): void {
$changedFields = self::calculateChangedFields($oldValues, $newValues);
$data = [
'entity_type' => $entityType,
'entity_id' => $entityId,
'action' => $action,
'old_values' => $oldValues ? json_encode($oldValues) : null,
'new_values' => $newValues ? json_encode($newValues) : null,
'changed_fields' => json_encode($changedFields),
'user_id' => auth()->id() ?? 'SYSTEM',
'site_id' => session('site_id') ?? 'MAIN',
'created_at' => date('Y-m-d H:i:s')
];
// Route to appropriate table
$table = match($category) {
'master' => 'master_audit_log',
'patient' => 'patient_audit_log',
'order' => 'order_audit_log',
default => throw new \InvalidArgumentException("Unknown category: $category")
};
// Async logging recommended for high-volume operations
self::dispatchAuditJob($table, $data);
}
private static function calculateChangedFields(?array $old, ?array $new): array
{
if (!$old || !$new) return [];
$changes = [];
$allKeys = array_unique(array_merge(array_keys($old), array_keys($new)));
foreach ($allKeys as $key) {
if (($old[$key] ?? null) !== ($new[$key] ?? null)) {
$changes[] = $key;
}
}
return $changes;
}
}
```
### 4.2 Model Integration
```php
<?php
namespace App\Models;
use App\Services\AuditService;
class PatientModel extends BaseModel
{
protected $table = 'patients';
protected $primaryKey = 'PatientID';
protected function logAudit(
string $action,
?array $oldValues = null,
?array $newValues = null
): void {
AuditService::log(
category: 'patient',
entityType: 'patient',
entityId: $this->getPatientId(),
action: $action,
oldValues: $oldValues,
newValues: $newValues
);
}
// Override save method to auto-log
public function save($data): bool
{
$oldData = $this->find($data['PatientID'] ?? null);
$result = parent::save($data);
if ($result) {
$this->logAudit(
$oldData ? 'UPDATE' : 'CREATE',
$oldData?->toArray(),
$this->find($data['PatientID'])->toArray()
);
}
return $result;
}
}
```
---
## 5. Query Patterns & Performance
### 5.1 Common Queries
```sql
-- View entity history
SELECT * FROM patient_audit_log
WHERE entity_type = 'patient'
AND entity_id = 'PAT-2026-001234'
ORDER BY created_at DESC;
-- User activity report
SELECT entity_type, action, COUNT(*) as count
FROM patient_audit_log
WHERE user_id = 'USR-001'
AND created_at > DATE_SUB(NOW(), INTERVAL 7 DAY)
GROUP BY entity_type, action;
-- Find all changes to a specific field
SELECT * FROM order_audit_log
WHERE JSON_CONTAINS(changed_fields, '"result_value"')
AND patient_id = 'PAT-001'
AND created_at > '2026-01-01';
```
### 5.2 Partitioning Strategy (Order/Test)
For high-volume tables, implement monthly partitioning:
```sql
CREATE TABLE order_audit_log (
-- ... columns
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB
PARTITION BY RANGE (YEAR(created_at) * 100 + MONTH(created_at)) (
PARTITION p202601 VALUES LESS THAN (202602),
PARTITION p202602 VALUES LESS THAN (202603),
PARTITION p_future VALUES LESS THAN MAXVALUE
);
```
---
## 6. Soft Delete Handling
Soft deletes ARE captured as audit entries with complete snapshots:
```php
// When soft deleting a patient:
AuditService::log(
category: 'patient',
entityType: 'patient',
entityId: $patientId,
action: 'DELETE',
oldValues: $fullRecordBeforeDelete, // Complete last known state
newValues: null,
reason: 'Patient requested data removal'
);
```
This ensures:
- ✅ Full audit trail even for deleted records
- ✅ Compliance with "right to be forgotten" (GDPR)
- ✅ Ability to restore accidentally deleted records
---
## 7. Migration Plan
### Phase 1: Foundation (Week 1)
- [ ] Drop existing unused tables (patreglog, patvisitlog, specimenlog)
- [ ] Create new audit tables with JSON columns
- [ ] Create AuditService class
- [ ] Add database indexes
### Phase 2: Core Implementation (Week 2)
- [ ] Integrate AuditService into Patient model
- [ ] Integrate AuditService into Order model
- [ ] Integrate AuditService into Master data models
- [ ] Add audit trail to authentication events
### Phase 3: API & UI (Week 3)
- [ ] Create API endpoints for querying audit logs
- [ ] Build admin interface for audit review
- [ ] Add audit export functionality (CSV/PDF)
### Phase 4: Optimization (Week 4)
- [ ] Implement async logging queue
- [ ] Add table partitioning for order_audit_log
- [ ] Set up retention policies and archiving
- [ ] Performance testing and tuning
---
## 8. Retention & Archiving Strategy
| Table | Retention Period | Archive Action |
|-------|---------------|----------------|
| `master_audit_log` | Permanent | None (keep forever) |
| `patient_audit_log` | 7 years | Move to cold storage after 7 years |
| `order_audit_log` | 2 years | Partition rotation: drop old partitions |
### Automated Maintenance
```sql
-- Monthly job: Archive old patient audit logs
INSERT INTO patient_audit_log_archive
SELECT * FROM patient_audit_log
WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 YEAR);
DELETE FROM patient_audit_log
WHERE created_at < DATE_SUB(NOW(), INTERVAL 7 YEAR);
-- Monthly job: Drop old order partitions
ALTER TABLE order_audit_log DROP PARTITION p202501;
```
---
## 9. Questions for Stakeholders
Before implementation, please confirm:
1. **Retention Policy**: Are the proposed retention periods (master=forever, patient=7 years, order=2 years) compliant with your regulatory requirements?
2. **Async vs Sync**: Should audit logging be synchronous (block on failure) or asynchronous (queue-based)? Recommended: async for order/test operations.
3. **Archive Storage**: Where should archived audit logs be stored? Options: separate database, file storage (S3), or compressed tables.
4. **User Access**: Which user roles need access to audit trails? Should users see their own audit history?
5. **Compliance**: Do you need specific compliance features (e.g., HIPAA audit trail requirements, 21 CFR Part 11 for FDA)?
---
## 10. Key Design Decisions Summary
| Decision | Choice | Rationale |
|----------|--------|-----------|
| **Table Count** | 3 tables | Separates concerns, optimizes queries, different retention |
| **JSON vs Columns** | JSON for values | Flexible, handles schema changes, complete snapshots |
| **Full vs Diff** | Full snapshots | Easier to reconstruct history, no data loss |
| **Soft Deletes** | Captured in audit | Compliance and restore capability |
| **Partitioning** | Order table only | High volume, time-based queries |
| **Async Logging** | Recommended | Don't block user operations |
---
## Conclusion
This unified audit logging architecture provides:
**Complete traceability** across all data domains
**Regulatory compliance** with proper retention
**Performance optimization** through domain separation
**Flexibility** via JSON value storage
**Maintainability** with centralized service
The approach balances audit integrity with system performance, ensuring CLQMS can scale while maintaining comprehensive audit trails.
---
*Document Version: 1.0*
*Author: CLQMS Development Team*
*Date: February 19, 2026*

View File

@ -35,6 +35,14 @@ When documenting suggestions, please include:
## Suggestion List
### [High] Backend Dev: Implementation Plan
**Description:** Consolidated developer review for proposals 001 and 002.
[View Plan](./003-decision-001-002.md)
### [High] Audit Logging Architecture Plan
**Description:** Comprehensive audit trail strategy for tracking changes across master data, patient records, and laboratory operations.
[View Plan](./audit-logging-plan.md)
### [TBD] Add new suggestions here
<!-- Use the format below for each suggestion:

View File

@ -4,17 +4,17 @@ title: The 5Panda Team
description: Meet the innovative minds behind 5Panda.
---
<div class="hero-gradient min-h-[35vh] flex items-center justify-center relative overflow-hidden">
<div class="hero-solid min-h-[35vh] flex items-center justify-center relative overflow-hidden">
<div class="text-center z-10 px-4">
<h1 class="text-5xl md:text-7xl font-bold mb-4">Meet the
<span class="gradient-text">Squad</span>
<h1 class="text-4xl md:text-6xl font-bold mb-4">Meet the
<span class="text-primary">Squad</span>
</h1>
<p class="text-xl text-base-content/70">The pandas building the future.</p>
<p class="text-lg text-base-content/70">The pandas building the future.</p>
</div>
<!-- Background bubbles -->
<!-- Background decoration -->
<div class="absolute inset-0 overflow-hidden pointer-events-none">
<div class="absolute top-10 left-10 w-40 h-40 bg-primary/20 rounded-full blur-3xl animate-float"></div>
<div class="absolute bottom-10 right-10 w-40 h-40 bg-secondary/20 rounded-full blur-3xl animate-float animate-delay-200"></div>
<div class="absolute top-10 left-10 w-40 h-40 bg-primary/10 rounded-full blur-3xl"></div>
<div class="absolute bottom-10 right-10 w-40 h-40 bg-secondary/10 rounded-full blur-3xl"></div>
</div>
</div>
<section