AI Ticket Platform
+diff --git a/docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md b/docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md new file mode 100644 index 0000000..52991bb --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-ai-ticket-platform-implementation-roadmap.md @@ -0,0 +1,99 @@ +# AI Ticket Platform Implementation Roadmap + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:writing-plans to expand each stage into a detailed implementation plan before writing code. Use superpowers:subagent-driven-development or superpowers:executing-plans when executing a detailed stage plan. + +**Goal:** Break the AI-native technical support ticket platform into independently deliverable implementation stages. + +**Architecture:** Build a modular monolith first. Keep Django apps, React feature modules, workers, model gateway, Agent runtime, and MCP integration separated by clear interfaces so heavily loaded areas can be split later. + +**Tech Stack:** Django, Django Ninja, PostgreSQL, pgvector, Redis, Celery, MinIO, React, Vite, Tailwind CSS, shadcn/ui, Radix UI, TanStack Table, OpenAI-compatible model adapter. + +--- + +## Source Spec + +- `docs/superpowers/specs/2026-06-30-ai-ticket-platform-design.md` + +## Planning Rule + +The product contains multiple independent subsystems. Do not implement the entire spec from one plan. Use this roadmap to create one detailed plan per stage. Each detailed plan must produce working, testable software. + +## Stage Breakdown + +| Stage | Name | Purpose | Depends On | Detailed Plan | +|---|---|---|---|---| +| 0 | Foundation and design system | Create the repo skeleton, local runtime, auth/RBAC baseline, audit baseline, frontend theme and component standards. | None | `docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md` | +| 1 | Ticket workflow and reusable solutions | Implement ticket lifecycle, comments, attachments, events, export, solution library, and solution links. | Stage 0 | Create after Stage 0 passes. | +| 2 | Knowledge base and vector retrieval | Implement knowledge bases, documents, parsing jobs, chunks, embeddings, and retrieval. | Stage 0, partial Stage 1 | Create after Stage 1 model contracts stabilize. | +| 3 | Model gateway and Agent/Skill runtime | Implement provider configs, model configs, model testing, Skill configs, built-in Agents, runs, steps, AI artifacts. | Stage 0, Stage 1, Stage 2 retrieval interfaces | Create after Stage 2 retrieval contracts exist. | +| 4 | MCP tools and trust boundary | Implement MCP server registration, tool sync, risk levels, Agent tool binding, tool calls, confirmation, audit. | Stage 3 | Create after Agent runtime exists. | +| 5 | Historical ticket migration and polish | Import historical tickets, standardize fields, vectorize, generate solution drafts, add dashboards and UI polish. | Stage 1, Stage 2, Stage 3 | Create after core flows work. | + +## Stage 0 Deliverables + +| Area | Deliverable | +|---|---| +| Backend | Django project, settings split, Django Ninja API root, health endpoint, accounts app, audit app, system app. | +| Frontend | Vite React project, Tailwind, shadcn/Radix base, theme tokens, layout shell, component preview page. | +| Runtime | Docker Compose with backend, frontend, PostgreSQL, Redis, MinIO, Celery worker, Celery beat. | +| Governance | Login baseline, users/groups/roles baseline, menu definitions, permission seed command, audit model. | +| Quality | pytest, ruff, mypy baseline, frontend lint/typecheck/build, pre-commit optional. | +| Documentation | `.env.example`, local startup guide, design system notes. | + +## Stage 1 Deliverables + +| Area | Deliverable | +|---|---| +| Ticket core | `ticket`, `ticket_category`, `ticket_comment`, `ticket_attachment`, `ticket_event`, `ticket_collaborator`. | +| Ticket APIs | Create/list/detail/update/status transition/comment/upload/export. | +| Ticket UI | List page, create page, detail page, status timeline, comments, attachments. | +| Solutions | `solution`, `ticket_resolution`, `ticket_solution_link`, solution list/detail/linking. | +| Audit | Ticket operations written to audit/event tables. | + +## Stage 2 Deliverables + +| Area | Deliverable | +|---|---| +| Knowledge | `knowledge_base`, `knowledge_document`, `knowledge_item`, `knowledge_chunk`. | +| Jobs | Document parsing, chunking, embedding enqueue/retry/status. | +| Retrieval | Keyword search plus vector search interface. | +| UI | Knowledge base, document status, knowledge item editor, search page. | + +## Stage 3 Deliverables + +| Area | Deliverable | +|---|---| +| Model gateway | `model_provider`, `model_config`, encrypted secrets, OpenAI-compatible client adapter. | +| Model testing | Single model test and multi-model comparison UI. | +| Skills | `skill_definition`, prompt template rendering, input/output schema validation. | +| Agents | `agent_definition`, `agent_run`, `agent_step`, `ticket_ai_artifact`. | +| Built-ins | Ticket creation Agent, support analysis Agent, knowledge distillation Agent. | + +## Stage 4 Deliverables + +| Area | Deliverable | +|---|---| +| MCP config | `tool_server`, `tool_definition`, `tool_call`. | +| Sync | Tool discovery/sync from configured MCP server. | +| Risk control | low/medium/high risk, human confirmation for high risk. | +| Agent integration | Authorized tool calls appear in `agent_step` and `tool_call`. | + +## Stage 5 Deliverables + +| Area | Deliverable | +|---|---| +| Migration | Import script and staging model for historical tickets. | +| AI cleanup | Summaries, categories, error codes, solution draft generation. | +| Retrieval | Historical tickets available for similar-ticket search. | +| Dashboards | Ticket trends, high-frequency problems, solution usage, Agent usage. | + +## Cross-Stage Invariants + +- Business writes generated by AI require user confirmation by default. +- Secrets are encrypted at rest. +- Raw model/tool payloads stored in logs must support masking. +- No business page may define one-off colors, status badges, or table styling. +- Every backend stage includes API tests. +- Every frontend stage includes typecheck and build verification. +- Commit after each coherent task. + diff --git a/docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md b/docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md new file mode 100644 index 0000000..1c2fb36 --- /dev/null +++ b/docs/superpowers/plans/2026-06-30-stage-0-foundation-design-system.md @@ -0,0 +1,1585 @@ +# Stage 0 Foundation And Design System Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Build the initial runnable project foundation: backend, frontend, local runtime, RBAC/audit baseline, and frontend design system standards. + +**Architecture:** Use a modular monorepo with `backend/`, `frontend/`, `infra/`, and `docs/`. The backend is a Django modular monolith with Django Ninja APIs and dedicated apps for accounts, audit, and system configuration. The frontend is Vite React with Tailwind/shadcn-style primitives wrapped by product-level components. + +**Tech Stack:** Python 3.12, Django, Django Ninja, PostgreSQL, Redis, Celery, MinIO, pytest, ruff, mypy, React, Vite, TypeScript, Tailwind CSS, Radix UI, lucide-react, TanStack Table, React Hook Form, Zod. + +--- + +## Files To Create + +```text +backend/ + pyproject.toml + manage.py + config/ + __init__.py + settings/ + __init__.py + base.py + local.py + test.py + urls.py + asgi.py + wsgi.py + celery.py + apps/ + __init__.py + accounts/ + __init__.py + apps.py + models.py + schemas.py + api.py + permissions.py + management/ + __init__.py + commands/ + __init__.py + seed_permissions.py + tests/ + __init__.py + test_permissions_seed.py + audit/ + __init__.py + apps.py + models.py + api.py + tests/ + __init__.py + test_audit_api.py + system/ + __init__.py + apps.py + api.py + tests/ + __init__.py + test_health_api.py + tests/ + __init__.py + conftest.py +frontend/ + package.json + tsconfig.json + tsconfig.node.json + vite.config.ts + index.html + tailwind.config.ts + postcss.config.js + src/ + main.tsx + app/ + App.tsx + router.tsx + layout/ + AppShell.tsx + Sidebar.tsx + Topbar.tsx + components/ + ui/ + button.tsx + badge.tsx + card.tsx + app/ + AppPageHeader.tsx + AppStatusBadge.tsx + AppPriorityBadge.tsx + features/ + design-system/ + DesignSystemPreview.tsx + lib/ + cn.ts + styles/ + globals.css +infra/ + docker-compose.yml + backend.Dockerfile + frontend.Dockerfile +.env.example +docs/development/local-setup.md +docs/frontend/design-system.md +``` + +## Task 1: Create Backend Project Skeleton + +**Files:** +- Create: `backend/pyproject.toml` +- Create: `backend/manage.py` +- Create: `backend/config/settings/base.py` +- Create: `backend/config/settings/local.py` +- Create: `backend/config/settings/test.py` +- Create: `backend/config/urls.py` +- Create: `backend/config/asgi.py` +- Create: `backend/config/wsgi.py` +- Create: `backend/config/celery.py` +- Create: `backend/apps/system/api.py` +- Test: `backend/apps/system/tests/test_health_api.py` + +- [ ] **Step 1: Create the backend dependency manifest** + +Create `backend/pyproject.toml`: + +```toml +[project] +name = "ai-ticket-platform-backend" +version = "0.1.0" +description = "Backend for AI-native technical support ticket platform" +requires-python = ">=3.12" +dependencies = [ + "Django>=5.0,<5.3", + "django-ninja>=1.3,<2.0", + "psycopg[binary]>=3.2,<4.0", + "celery>=5.4,<6.0", + "redis>=5.0,<6.0", + "django-cors-headers>=4.4,<5.0", + "pydantic>=2.8,<3.0", + "python-dotenv>=1.0,<2.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=8.2,<9.0", + "pytest-django>=4.8,<5.0", + "ruff>=0.6,<1.0", + "mypy>=1.11,<2.0", + "django-stubs[compatible-mypy]>=5.0,<6.0", +] + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "config.settings.test" +python_files = ["test_*.py", "*_test.py"] +testpaths = ["apps", "tests"] + +[tool.ruff] +line-length = 100 +target-version = "py312" + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B"] + +[tool.mypy] +python_version = "3.12" +plugins = ["mypy_django_plugin.main"] +ignore_missing_imports = true +strict_optional = true + +[tool.django-stubs] +django_settings_module = "config.settings.test" +``` + +- [ ] **Step 2: Create Django entry points** + +Create `backend/manage.py`: + +```python +#!/usr/bin/env python +import os +import sys + + +def main() -> None: + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() +``` + +Create `backend/config/asgi.py`: + +```python +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +application = get_asgi_application() +``` + +Create `backend/config/wsgi.py`: + +```python +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +application = get_wsgi_application() +``` + +Create `backend/config/celery.py`: + +```python +import os + +from celery import Celery + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") + +app = Celery("ai_ticket_platform") +app.config_from_object("django.conf:settings", namespace="CELERY") +app.autodiscover_tasks() +``` + +- [ ] **Step 3: Create settings** + +Create `backend/config/settings/base.py`: + +```python +from pathlib import Path +import os + +BASE_DIR = Path(__file__).resolve().parents[3] + +SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "dev-only-change-me") +DEBUG = os.getenv("DJANGO_DEBUG", "false").lower() == "true" +ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",") + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "corsheaders", + "apps.accounts", + "apps.audit", + "apps.system", +] + +MIDDLEWARE = [ + "corsheaders.middleware.CorsMiddleware", + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "config.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + } +] + +WSGI_APPLICATION = "config.wsgi.application" +ASGI_APPLICATION = "config.asgi.application" + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": os.getenv("POSTGRES_DB", "ai_ticket_platform"), + "USER": os.getenv("POSTGRES_USER", "ai_ticket"), + "PASSWORD": os.getenv("POSTGRES_PASSWORD", "ai_ticket"), + "HOST": os.getenv("POSTGRES_HOST", "localhost"), + "PORT": int(os.getenv("POSTGRES_PORT", "5432")), + } +} + +LANGUAGE_CODE = "zh-hans" +TIME_ZONE = "Asia/Shanghai" +USE_I18N = True +USE_TZ = True + +STATIC_URL = "static/" +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +CORS_ALLOWED_ORIGINS = os.getenv( + "CORS_ALLOWED_ORIGINS", + "http://localhost:5173", +).split(",") + +CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0") +CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/1") +``` + +Create `backend/config/settings/local.py`: + +```python +from .base import * # noqa: F403 + +DEBUG = True +``` + +Create `backend/config/settings/test.py`: + +```python +from .base import * # noqa: F403 + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": ":memory:", + } +} + +PASSWORD_HASHERS = [ + "django.contrib.auth.hashers.MD5PasswordHasher", +] +``` + +- [ ] **Step 4: Create API root and health endpoint** + +Create `backend/apps/system/api.py`: + +```python +from ninja import Router + +router = Router(tags=["system"]) + + +@router.get("/health") +def health(request): + return {"status": "ok"} +``` + +Create `backend/config/urls.py`: + +```python +from django.contrib import admin +from django.urls import path +from ninja import NinjaAPI + +from apps.system.api import router as system_router + +api = NinjaAPI(title="AI Ticket Platform API", version="0.1.0") +api.add_router("/system", system_router) + +urlpatterns = [ + path("admin/", admin.site.urls), + path("api/", api.urls), +] +``` + +- [ ] **Step 5: Write the failing health API test** + +Create `backend/apps/system/tests/test_health_api.py`: + +```python +def test_health_endpoint_returns_ok(client): + response = client.get("/api/system/health") + + assert response.status_code == 200 + assert response.json() == {"status": "ok"} +``` + +- [ ] **Step 6: Run the backend test** + +Run: + +```bash +cd backend +python -m pip install -e ".[dev]" +pytest apps/system/tests/test_health_api.py -v +``` + +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add backend +git commit -m "feat: add backend foundation" +``` + +## Task 2: Add Accounts Permission Seed Baseline + +**Files:** +- Create: `backend/apps/accounts/apps.py` +- Create: `backend/apps/accounts/models.py` +- Create: `backend/apps/accounts/permissions.py` +- Create: `backend/apps/accounts/management/commands/seed_permissions.py` +- Test: `backend/apps/accounts/tests/test_permissions_seed.py` + +- [ ] **Step 1: Create accounts app config and empty models** + +Create `backend/apps/accounts/apps.py`: + +```python +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.accounts" +``` + +Create `backend/apps/accounts/models.py`: + +```python +# Use Django's built-in User, Group, and Permission models for MVP RBAC. +``` + +- [ ] **Step 2: Define permission catalog** + +Create `backend/apps/accounts/permissions.py`: + +```python +from dataclasses import dataclass + + +@dataclass(frozen=True) +class PermissionSpec: + codename: str + name: str + + +PERMISSIONS: tuple[PermissionSpec, ...] = ( + PermissionSpec("ticket.view", "View tickets"), + PermissionSpec("ticket.create", "Create tickets"), + PermissionSpec("ticket.edit", "Edit tickets"), + PermissionSpec("ticket.export", "Export tickets"), + PermissionSpec("ticket.assign", "Assign tickets"), + PermissionSpec("ticket.close", "Close tickets"), + PermissionSpec("knowledge.view", "View knowledge"), + PermissionSpec("knowledge.manage", "Manage knowledge"), + PermissionSpec("solution.view", "View solutions"), + PermissionSpec("solution.manage", "Manage solutions"), + PermissionSpec("agent.use", "Use Agents"), + PermissionSpec("agent.manage", "Manage Agents"), + PermissionSpec("model.manage", "Manage model configs"), + PermissionSpec("tool.manage", "Manage MCP tools"), + PermissionSpec("audit.view", "View audit logs"), + PermissionSpec("system.manage", "Manage system settings"), +) + +DEFAULT_GROUPS: dict[str, tuple[str, ...]] = { + "System Admin": tuple(permission.codename for permission in PERMISSIONS), + "Knowledge Admin": ( + "ticket.view", + "knowledge.view", + "knowledge.manage", + "solution.view", + "solution.manage", + "agent.use", + ), + "Support Engineer": ( + "ticket.view", + "ticket.create", + "ticket.edit", + "ticket.assign", + "ticket.close", + "knowledge.view", + "solution.view", + "agent.use", + ), + "Requester": ( + "ticket.view", + "ticket.create", + "knowledge.view", + "solution.view", + ), + "Auditor": ( + "ticket.view", + "knowledge.view", + "solution.view", + "audit.view", + ), +} +``` + +- [ ] **Step 3: Create permission seed command** + +Create `backend/apps/accounts/management/commands/seed_permissions.py`: + +```python +from django.contrib.auth.models import Group, Permission +from django.contrib.contenttypes.models import ContentType +from django.core.management.base import BaseCommand + +from apps.accounts.permissions import DEFAULT_GROUPS, PERMISSIONS + + +class Command(BaseCommand): + help = "Seed MVP permissions and default user groups." + + def handle(self, *args, **options): + content_type, _ = ContentType.objects.get_or_create( + app_label="system", + model="platformpermission", + ) + + permissions_by_codename: dict[str, Permission] = {} + for spec in PERMISSIONS: + permission, _ = Permission.objects.get_or_create( + content_type=content_type, + codename=spec.codename, + defaults={"name": spec.name}, + ) + if permission.name != spec.name: + permission.name = spec.name + permission.save(update_fields=["name"]) + permissions_by_codename[spec.codename] = permission + + for group_name, codenames in DEFAULT_GROUPS.items(): + group, _ = Group.objects.get_or_create(name=group_name) + group.permissions.set(permissions_by_codename[codename] for codename in codenames) + + self.stdout.write(self.style.SUCCESS("Seeded permissions and groups.")) +``` + +- [ ] **Step 4: Write the seed command test** + +Create `backend/apps/accounts/tests/test_permissions_seed.py`: + +```python +from django.contrib.auth.models import Group, Permission +from django.core.management import call_command + +from apps.accounts.permissions import DEFAULT_GROUPS, PERMISSIONS + + +def test_seed_permissions_creates_permissions_and_groups(db): + call_command("seed_permissions") + + assert Permission.objects.filter( + codename__in=[permission.codename for permission in PERMISSIONS] + ).count() == len(PERMISSIONS) + + for group_name, codenames in DEFAULT_GROUPS.items(): + group = Group.objects.get(name=group_name) + actual = set(group.permissions.values_list("codename", flat=True)) + assert actual == set(codenames) +``` + +- [ ] **Step 5: Run the accounts test** + +Run: + +```bash +cd backend +pytest apps/accounts/tests/test_permissions_seed.py -v +``` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add backend/apps/accounts +git commit -m "feat: seed baseline RBAC permissions" +``` + +## Task 3: Add Audit Model And API + +**Files:** +- Create: `backend/apps/audit/apps.py` +- Create: `backend/apps/audit/models.py` +- Create: `backend/apps/audit/api.py` +- Modify: `backend/config/urls.py` +- Test: `backend/apps/audit/tests/test_audit_api.py` + +- [ ] **Step 1: Create audit app config** + +Create `backend/apps/audit/apps.py`: + +```python +from django.apps import AppConfig + + +class AuditConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "apps.audit" +``` + +- [ ] **Step 2: Create operation audit model** + +Create `backend/apps/audit/models.py`: + +```python +from django.conf import settings +from django.db import models + + +class OperationAuditLog(models.Model): + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="operation_audit_logs", + ) + module = models.CharField(max_length=80) + action = models.CharField(max_length=80) + target_type = models.CharField(max_length=80) + target_id = models.CharField(max_length=120, blank=True) + request_snapshot = models.JSONField(default=dict, blank=True) + result = models.CharField(max_length=20) + ip_address = models.GenericIPAddressField(null=True, blank=True) + user_agent = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ["-created_at"] + indexes = [ + models.Index(fields=["module", "action"]), + models.Index(fields=["target_type", "target_id"]), + models.Index(fields=["created_at"]), + ] + + def __str__(self) -> str: + return f"{self.module}.{self.action}:{self.result}" +``` + +- [ ] **Step 3: Create read-only audit API** + +Create `backend/apps/audit/api.py`: + +```python +from datetime import datetime +from typing import Any + +from ninja import Router, Schema + +from apps.audit.models import OperationAuditLog + +router = Router(tags=["audit"]) + + +class OperationAuditLogOut(Schema): + id: int + module: str + action: str + target_type: str + target_id: str + request_snapshot: dict[str, Any] + result: str + ip_address: str | None + user_agent: str + created_at: datetime + + +@router.get("/operation-logs", response=list[OperationAuditLogOut]) +def list_operation_logs(request): + return OperationAuditLog.objects.all()[:100] +``` + +Modify `backend/config/urls.py` to include the audit router: + +```python +from django.contrib import admin +from django.urls import path +from ninja import NinjaAPI + +from apps.audit.api import router as audit_router +from apps.system.api import router as system_router + +api = NinjaAPI(title="AI Ticket Platform API", version="0.1.0") +api.add_router("/system", system_router) +api.add_router("/audit", audit_router) + +urlpatterns = [ + path("admin/", admin.site.urls), + path("api/", api.urls), +] +``` + +- [ ] **Step 4: Write the audit API test** + +Create `backend/apps/audit/tests/test_audit_api.py`: + +```python +from apps.audit.models import OperationAuditLog + + +def test_list_operation_logs_returns_recent_logs(client, db): + OperationAuditLog.objects.create( + module="system", + action="health_check", + target_type="system", + target_id="health", + result="success", + request_snapshot={"path": "/api/system/health"}, + ) + + response = client.get("/api/audit/operation-logs") + + assert response.status_code == 200 + payload = response.json() + assert len(payload) == 1 + assert payload[0]["module"] == "system" + assert payload[0]["action"] == "health_check" +``` + +- [ ] **Step 5: Run migrations and tests** + +Run: + +```bash +cd backend +python manage.py makemigrations audit +pytest apps/audit/tests/test_audit_api.py -v +``` + +Expected: PASS. + +- [ ] **Step 6: Commit** + +```bash +git add backend/apps/audit backend/config/urls.py +git commit -m "feat: add operation audit baseline" +``` + +## Task 4: Create Frontend Foundation And Design Tokens + +**Files:** +- Create: `frontend/package.json` +- Create: `frontend/tsconfig.json` +- Create: `frontend/tsconfig.node.json` +- Create: `frontend/vite.config.ts` +- Create: `frontend/index.html` +- Create: `frontend/tailwind.config.ts` +- Create: `frontend/postcss.config.js` +- Create: `frontend/src/main.tsx` +- Create: `frontend/src/app/App.tsx` +- Create: `frontend/src/lib/cn.ts` +- Create: `frontend/src/styles/globals.css` + +- [ ] **Step 1: Create frontend package manifest** + +Create `frontend/package.json`: + +```json +{ + "name": "ai-ticket-platform-frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "typecheck": "tsc -b", + "lint": "eslint src --ext ts,tsx" + }, + "dependencies": { + "@radix-ui/react-slot": "^1.1.0", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "lucide-react": "^0.468.0", + "tailwind-merge": "^2.5.0", + "tailwindcss-animate": "^1.0.7", + "vite": "^6.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.3.0", + "autoprefixer": "^10.4.20", + "eslint": "^9.16.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.16", + "typescript": "^5.7.0" + } +} +``` + +- [ ] **Step 2: Create Vite and TypeScript config** + +Create `frontend/tsconfig.json`: + +```json +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ES2020"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} +``` + +Create `frontend/tsconfig.node.json`: + +```json +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} +``` + +Create `frontend/vite.config.ts`: + +```typescript +import react from '@vitejs/plugin-react' +import path from 'node:path' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } + }, + server: { + port: 5173 + } +}) +``` + +- [ ] **Step 3: Create Tailwind theme tokens** + +Create `frontend/tailwind.config.ts`: + +```typescript +import type { Config } from 'tailwindcss' + +const config: Config = { + darkMode: ['class'], + content: ['./index.html', './src/**/*.{ts,tsx}'], + theme: { + extend: { + colors: { + border: 'hsl(var(--border))', + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + surface: 'hsl(var(--surface))', + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + success: 'hsl(var(--success))', + warning: 'hsl(var(--warning))', + danger: 'hsl(var(--danger))', + info: 'hsl(var(--info))', + ai: 'hsl(var(--ai-accent))' + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + } + } + }, + plugins: [require('tailwindcss-animate')] +} + +export default config +``` + +Create `frontend/postcss.config.js`: + +```javascript +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +} +``` + +Create `frontend/src/styles/globals.css`: + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 210 20% 98%; + --foreground: 222 25% 12%; + --surface: 0 0% 100%; + --muted: 210 18% 94%; + --muted-foreground: 215 12% 42%; + --border: 214 18% 86%; + --primary: 213 52% 32%; + --primary-foreground: 0 0% 100%; + --success: 151 55% 36%; + --warning: 38 86% 48%; + --danger: 0 70% 50%; + --info: 204 76% 44%; + --ai-accent: 186 52% 36%; + --radius: 0.5rem; + } + + body { + margin: 0; + background: hsl(var(--background)); + color: hsl(var(--foreground)); + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + } +} +``` + +- [ ] **Step 4: Create minimal app entry** + +Create `frontend/index.html`: + +```html + + +
+ + +AI Ticket Platform
+{description}
: null} +