Adds a "UI & Brand (2026-05)" section covering the OLIVER design tokens (yellow #FFCB05, near-black #1A1A1A, Montserrat), signature motifs (.page-title highlight, .stat-tile, card-header accent), and the two gotchas to avoid re-introducing (don't override .navbar position in nav.html; tabs must use button data-bs-target, not a href). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
30 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
AgentHub is a FastAPI-based AI Agent Management System with MongoDB backend. It provides three-tier role-based authentication (admin/readonly_admin/user), agent CRUD operations, client verification workflow, email notifications, user management, and a web interface built with Jinja2 templates and Bootstrap 5.
Common Development Tasks
Running the Application
uvicorn main:app --reload --port 8000
Access at: http://localhost:8000
Installing Dependencies
pip install -r requirements.txt
Environment Setup
Create .env file with:
MONGODB_URI: MongoDB connection string (default: mongodb://localhost:27017)MONGODB_DBNAME: Database name (default: agenthub_db)SECRET_KEY: JWT secret keyALGORITHM: JWT algorithm (default: HS256)ACCESS_TOKEN_EXPIRE_MINUTES: Token expiration (default: 60)
Optional: Prompt Audit (Gemini)
GOOGLE_API_KEY: Google API key for Gemini (if not set, audit is silently disabled)AUDIT_GEMINI_MODEL: Gemini model name (default:gemini-2.5-pro)AUDIT_CONCURRENCY: Batch size for sequential processing (default: 2)
Optional: Email Notifications (Mailgun)
MAILGUN_API_KEY: Mailgun API key (if not set, notifications are silently disabled)MAILGUN_DOMAIN: Mailgun sending domain (e.g. your-domain.mailgun.org)MAILGUN_FROM_EMAIL: Sender address (default:AgentHub <noreply@{MAILGUN_DOMAIN}>)NOTIFICATION_REPLY_TO: Reply-To address attached to every Mailgun send (default:Nick.Viljoen@oliver.agency). Lets recipients reply to a real mailbox instead of thenoreply@sender.TOKEN_USAGE_THRESHOLD: Weekly token count that triggers an alert (default: 100000)NOTIFICATION_COOLDOWN_HOURS: Hours between repeat alerts for the same agent (default: 24)CLIENT_AGENT_NOTIFY_EMAILS: Comma-separated list of emails for client agent notificationsWEEKLY_DIGEST_HOUR: Hour (24h format) to send weekly digest on Mondays (default: 7)
Optional: Completion-Reminder Emails (LibreChat completion flow)
COMPLETION_REMINDER_COOLDOWN_DAYS: Min days between reminders for the same user (default: 7)COMPLETION_REMINDER_MAX_NUDGES: After this many nudges, stop emailing the user (default: 4)COMPLETION_REMINDER_HOUR: Hour (24h) for the daily reminder cron (default: 8)AGENTHUB_PUBLIC_URL: Public absolute URL used in email links (e.g.https://agenthub.example.com). If unset, links are relative and won't work in email clients.
Default Login Credentials
- Admin:
admin@agenthub.com/admin123 - Test User:
test@example.com/testpass123
Code Architecture
Core Application Structure
main.py: FastAPI application with:
- JWT cookie-based authentication system
- HTML routes for web interface
- REST API endpoints for agent/user management
- Three-tier role-based access control (admin / readonly_admin / user)
- Client verification workflow with email notifications
- Daily agent digest scheduler (APScheduler)
- Template rendering with Jinja2
Key Authentication Functions:
get_current_user_optional(): Cookie-based auth for templatesget_current_user_from_cookie(): Required auth for API endpointsrequire_admin(): Admin-only access control (write operations)require_admin_or_readonly(): Admin + readonly_admin access (read-only dashboard views)
Data Layer
models.py: Pydantic models for:
AiAgent: Core agent model with comprehensive fields (includesdiscipline,rating,client,client_name,studio_name)UsageTimelineEntry: Daily usage data includingmessage_countandtoken_countUserCreate/UserResponse: User management models (includesrolefield:user/admin/readonly_admin)UserUpdate: Includesrolefield for three-tier role managementAiAgentCreate/AiAgentResponse: API request/response models (includestotal_tokens,prompt_tokens,completion_tokens,discipline,rating,rating_count,client,client_name,studio_name,verification_status,verified_by,verified_date)AgentCollectorCreate: Collector API input model (includestotal_tokens,prompt_tokens,completion_tokens,discipline,client,client_name,studio_name)AuditReviewRequest: Audit review input (audit_status: flagged/reviewed/cleared,reviewer_notes)AgentUsageStatsResponse: Usage statistics response (includestotal_tokens,prompt_tokens,completion_tokens)
crud.py: Database operations using Motor (async MongoDB driver):
- User CRUD: authentication, creation, management
- Agent CRUD: create, read, update, delete with user ownership
- Advanced features: search, filtering, statistics, pagination
- All operations use ObjectId for MongoDB document IDs
database.py: MongoDB connection setup with Motor async client
- Collections:
users,agents,agent_usage,token_notifications,agent_ratings,audit_history,completion_reminders ensure_indexes(): Creates compound unique index onagent_ratings(agent_id, user_id), indexes onverification_status,audit_status, andaudit_history(agent_id, audit_date)
audit_analyzer.py: Gemini-powered agent classification and audit system:
is_gemini_configured(): Returns False ifGOOGLE_API_KEYnot set (gracefully disabled)analyze_single_agent(): Sends agent instructions to Gemini 2.5 Pro, returns structured JSON with category, discipline, department, client detection, risk level, flags, and recommendations. Includes retry with exponential backoff for rate limits.store_audit_result(): Writes audit results to agent document (audit_status,audit_category,audit_risk_level, etc.) and inserts intoaudit_historycollectionapply_classification_fields(): Auto-assignsdiscipline(from defined list),agent_department(free text inferred from instructions), and client detection (client = "yes",verification_status = "needs_verification") — only overwrites fields that are currently empty/nullclassify_single_agent(): Convenience function for post-sync automatic classification. Loads agent, analyses, stores result, applies fields.run_audit_batch(): Batch processes agents sequentially with rate-limit-safe pauses. Supportsunclassified_onlyandsingle_agent_idparams. Returns summary with audited/failed/skipped counts.get_all_audit_results(): Returns all agents with audit fields for the Prompt Audit tabupdate_audit_review(): Admin marks audit as reviewed/cleared with notes- Uses
google-genaiSDK (new API), model configurable viaAUDIT_GEMINI_MODELenv var (default:gemini-2.5-pro)
notifications.py: Mailgun email notification system:
is_mailgun_configured(): Returns False if env vars not set (gracefully disabled)send_mailgun_email(): POST to Mailgun HTTP API with 10s timeoutbuild_threshold_email(): HTML email template for threshold alertscheck_and_notify_threshold(): Checks 7-day token usage against threshold, enforces cooldown viatoken_notificationscollection, sends to admin userssend_client_agent_notification(): Sends email when client-facing agent is created (toCLIENT_AGENT_NOTIFY_EMAILS)build_client_agent_email(): HTML email template for client agent notificationssend_weekly_agent_digest(): Queries agents created in last 7 days, sends summary to all admin usersbuild_weekly_digest_email(): HTML email template for weekly digest
auth.py: JWT authentication with:
- bcrypt password hashing
- JWT token creation/validation using python-jose
- Configurable token expiration
Frontend Templates
Located in templates/ directory:
- base.html: Bootstrap 5 base template with navigation
- nav.html: Dynamic navigation based on user role
- index.html: Landing page
- login.html/register.html: Authentication forms
- agent_register.html: Agent creation form
- agent_management.html: Agent dashboard with real data
- search.html: Global search functionality
- user_management.html: User management interface
- admin/dashboard.html: Admin statistics, management, and verification workflow
Static Assets
static/style.css: Custom CSS with:
- CSS variables for consistent theming
- Gradient backgrounds and modern styling
- Responsive design for mobile devices
- Bootstrap 5 customizations
Key Features
Authentication Flow
- Cookie-based JWT authentication
- Three-tier role-based access:
user,admin,readonly_adminuser: Standard access, can manage own agentsadmin: Full access to admin dashboard, all write operationsreadonly_admin: Can view admin dashboard but all write actions (edit, delete, approve, create) are hidden
rolefield on user documents;is_adminkept in sync for backward compatibilityrequire_admin_or_readonly()dependency for read-only admin endpoints- Automatic redirects based on user role (admin/readonly_admin → /admin, user → /agent-management)
- Secure logout with token cleanup
Agent Management
- Full CRUD operations with user ownership
- Status tracking (Active, Inactive, Development, Deprecated)
- Rich metadata: tags, userbase, department, contact person
- Search functionality across multiple fields
- Filtering by status, audit status (Audited / Not Audited), and discipline
- Admin can view/manage all agents
Client & Verification System
clientfield on agents:"yes"or"no"(mandatory on registration form)client_name: free text, required when client is "yes"studio_name: optional free text field- Registration form order: Name, Description, Purpose, Client (Yes/No), Client Name (conditional), Studio Name, Tool, then Version/Status/etc.
- When
client == "yes": agent auto-tagged withverification_status = "needs_verification" - Verification tab on admin dashboard shows pending agents with Approve button
PUT /api/admin/agents/{id}/verify— admin-only, sets status to "verified" with verifier infoGET /api/admin/agents/pending-verification— returns agents needing verification- Verification badges displayed on agent cards (orange "Needs Verification", green "Verified")
Client Agent Email Notification
- When
client == "yes"on agent creation, sends email via Mailgun toCLIENT_AGENT_NOTIFY_EMAILS - Subject: "Client Agent Created"
- Body includes: Agent Name, Description, Purpose, Client Name, Studio Name, Tool, Created By
- Non-blocking: failure does not break agent creation
Weekly Agent Digest Email
- Scheduled via APScheduler to run every Monday morning (default 7:00 AM,
WEEKLY_DIGEST_HOURenv var) - Queries agents created in last 7 days
- Sends to all active admin users
- Body includes: Agent Name, Purpose, Description, Created By (email)
- Subject: "Agents Created in Last Week"
- Skips sending if no agents created
- Can be manually triggered via
POST /api/admin/digest/send(admin-only)
Discipline & Star Rating
disciplinefield classifies agents into business categories: Strategy, Creative, Oversight including delivery, Optimization, Back Office including operations, Pencil Agents- Required on registration form, optional on edit (to support legacy agents)
- Pencil Agents discipline is auto-assigned to agents with "pencil" in the name when no discipline is set (collector API auto-tag + startup migration)
ratingfield stores the average star rating (1-5) computed from all per-user ratingsrating_countfield stores the number of individual ratings- Per-user rating system: Any authenticated user can rate any agent via
PUT /api/agents/{id}/rating- Individual ratings stored in
agent_ratingscollection with compound unique index on(agent_id, user_id) - After each rating, the agent's average rating and count are recalculated and stored on the agent document
GET /api/agents/{id}/my-ratingreturns the current user's rating plus the average and count
- Individual ratings stored in
- Interactive star rating widget in detail modal shows the user's own rating as filled stars
- Average rating and count displayed below the stars and on agent card badges
- Rating removed from edit modals (rating is per-user, not admin-set)
- Rating framework info modal accessible via info icon next to "Rating:" label
- Dashboard supports filtering by discipline and sorting by rating
- Discipline badge (purple) and star rating badge displayed on agent cards
- Both fields included in CSV export/import
- Discipline passed through collector API; rating is human-only (not in collector)
Token Usage Tracking
total_tokens,prompt_tokens,completion_tokensfields on agents track cumulative LLM token consumptionprompt_tokens(input) andcompletion_tokens(output) provide cost breakdown detailtoken_countper day in usage timeline entries alongsidemessage_count- Token badge displayed on agent cards (gold/coins icon) with prompt/completion breakdown in tooltip
- Usage modal shows Total Tokens stat with In/Out breakdown alongside messages/conversations/users
- Dual-axis chart (messages left axis, tokens right axis) when token data exists
- Sort agents by Total Tokens
- CSV export includes
total_tokens,prompt_tokens,completion_tokenscolumns
High Usage Email Notifications
- Entirely optional — silently disabled when Mailgun env vars are not set
- Triggered from the Agent Collector endpoint (POST
/agents); checks 7-day rolling token usage fromusage_timeline - Alerts when weekly token usage exceeds threshold (default 100,000, configurable via
TOKEN_USAGE_THRESHOLD) - Non-blocking — notification failure never breaks the collector API
- Cooldown tracking in MongoDB
token_notificationscollection (default 24h, configurable) - Sends to all active admin users' email addresses
Prompt Audit & Auto-Classification (Gemini)
- Automated analysis of agent
instructions(system prompts) using Google Gemini 2.5 Pro - Two trigger modes:
- Automatic post-sync: After the collector API (
POST /agents) creates/updates an agent with instructions, a background task (asyncio.create_task) auto-classifies it. Non-blocking — sync response is not delayed. - Manual batch: Admin clicks "Run Full Audit" or "Run Unclassified Only" on the Prompt Audit tab. Processes agents sequentially with 4-second pauses between batches to avoid Gemini rate limits.
- Automatic post-sync: After the collector API (
- Classification outputs per agent:
audit_category: Cat 1 (Internal Sandbox), Cat 1B (High Cost Internal), Cat 2 (Client-Exposed), Cat 3 (Client-Sold)audit_risk_level: low / medium / high / criticalaudit_discipline: Picks from existing discipline list (Strategy, Creative, Oversight including delivery, Optimization, Back Office including operations, Pencil Agents)audit_department: Free text inferred from instructions (e.g., "Project Management", "Media")audit_is_client_work: Boolean — detects client names, brands, client deliverables in instructionsaudit_flags: Array of risk flags (client_facing, handles_pii, uses_external_tools, etc.)audit_summary,audit_recommendations,audit_category_reasoning,audit_discipline_reasoning,audit_client_work_reasoning,audit_client_name_detected
- Auto-assignment: Gemini results auto-populate
disciplineandagent_departmentfields on the agent document (only if currently empty, to respect manual edits) - Client work auto-detection: When
audit_is_client_work = true, auto-setsclient = "yes"andverification_status = "needs_verification"— agent appears on Verification tab. Does NOT overwrite manually-set values. - Audit statuses: All audited agents start as
flagged. Admin can mark asreviewedorclearedvia the detail modal with optional notes. - Prompt Audit tab on admin dashboard: Summary cards (Audited, Flagged, Reviewed, Cleared, Client Detected, No Instructions), filterable results table, detail modal with full analysis and review controls
- Rate limit handling: Retry with exponential backoff (10s, 20s, 40s) for 429/quota errors
- Logging: Uses Python
loggingmodule (audit_analyzerlogger) for systemd journal visibility - Agents without
instructionsare skipped (counted as "No Instructions") audit_historycollection stores historical record of each audit run and review action- Gracefully disabled when
GOOGLE_API_KEYnot set — UI shows config warning, buttons hidden
User Management
- User registration with validation
- Admin user creation capabilities
- Three-tier role system:
user,admin,readonly_admin- Role dropdown in admin user edit modal (replaces is_admin checkbox)
roleandis_adminfields kept in sync on update
- Profile management
- User statistics and administration
Database Integration
- MongoDB with proper ObjectId handling
- Async operations using Motor driver
- Indexed queries for performance
- Data aggregation for statistics
- Collections:
users,agents,agent_usage,token_notifications,agent_ratings,audit_history,completion_reminders
Development Guidelines
Database Operations
- Always use ObjectId for MongoDB document IDs
- Use Motor async driver methods (await collection.find_one())
- Handle ObjectId conversion in CRUD operations
- Implement proper error handling with try/except blocks
Authentication
- Use cookie-based auth for web interface
- API endpoints require
get_current_user_from_cookie()dependency - Write endpoints use
require_admin()dependency - Read-only admin endpoints use
require_admin_or_readonly()dependency - Always validate user permissions for data access
readonly_adminusers can view admin dashboard but UI hides all write-action buttons viaadmin-write-actionCSS class
Template Context
- Pass
current_userto all templates for navigation - Handle dict objects (not User model instances) in templates
- Use proper null checks for optional user data
API Response Models
- Convert ObjectId to string in API responses
- Handle optional datetime fields with isoformat()
- Maintain consistency between Create and Response models
Error Handling
- Provide meaningful error messages in templates
- Use proper HTTP status codes in API responses
- Graceful degradation for missing data
Project Dependencies
Key dependencies from requirements.txt:
- fastapi: Web framework
- uvicorn: ASGI server
- motor: Async MongoDB driver
- pymongo: MongoDB operations
- python-jose: JWT token handling
- passlib: Password hashing
- bcrypt: Password encryption
- pydantic: Data validation
- jinja2: Template engine
- python-multipart: Form handling
- requests: HTTP client (used for Mailgun API calls)
- apscheduler: Task scheduling (weekly digest email)
- google-genai: Google Gemini API client (used for prompt audit auto-classification)
API Endpoints (New)
Verification
GET /api/admin/agents/pending-verification— List agents with verification status (admin + readonly_admin)PUT /api/admin/agents/{agent_id}/verify— Approve/verify an agent (admin only)
Weekly Digest
POST /api/admin/digest/send— Manually trigger the weekly agent digest email (admin only)
Prompt Audit
POST /api/admin/audit/run— Run Gemini audit batch. Optional JSON body:{agent_id, unclassified_only}. Returns{status, total, audited_count, failed_count, skipped_count, results_summary}(admin only)GET /api/admin/audit/results— Get all agents with audit data +config_status.gemini_configured(admin + readonly_admin)PUT /api/admin/audit/{agent_id}/review— Mark audit as reviewed/cleared with{audit_status, reviewer_notes}(admin only)
Registration Completion Flow (LibreChat-synced agents)
GET /api/agents/incomplete— Current user's incomplete agents (admins see all). Used by the agent management page banner. Owner resolution:created_by == user_idOR (created_by == "agent_collector_api"ANDagent_contact_person == user_email).GET /agent-complete/{agent_id}— HTML form (parameterisedagent_register.htmlin completion mode). Pre-fills existing fields, required new governance fields blank. Auth: agent owner (by ID or email) or admin.POST /agent-complete/{agent_id}— Submit completion. Setsregistration_complete=True, reassignscreated_byfrom"agent_collector_api"to the submitting user's ID.POST /api/admin/completion-reminders/send— Manually trigger the daily reminder digest. Optional?force=trueto bypass cooldown + max-nudges cap (admin only).GET /api/admin/agents/unresolved-owner— Collector-created agents whoseagent_contact_persondoesn't match an active user (admin only)PUT /api/admin/agents/{agent_id}/reassign-owner— Admin reassigns ownership; body{new_contact_email, new_owner_user_id?}. Resolves user by email ifnew_owner_user_idomitted (admin only)
Registration Form Redesign (2026-05)
The agent registration form was rebuilt around 7 governance sections per Indext2.html mockup. See PLAN-registration-form-redesign.md for full design rationale.
New schema fields on agents
Top-level (all Optional):
business_entity(enum: OLIVER, DARE, Brandtech Group, Pencil, Jellyfish, Adjust, Other) — required on formclient_scope(enum:internal,all,specific) — required on form. Maps to legacyclientfield:specific→yes, else→no. Both fields stay in sync to preserve existing verification + Mailgun client-agent-email flows.agent_classification(enum: Utility, Functional, Supervisory, Guardian) — required on formautonomy_level(enum: Human-Led, Hybrid, Autopilot) — required on formip_ownership(enum: Brandtech IP, Client IP, Shared/TBD) — required on formfoundation_model(string) — optional, dropdown with optgroups by providervalidated_by,validation_date,evals_method— optional Performance & Testing fieldsregistration_complete(bool) —Truefor form-submitted agents,Falsefor collector-created. Drives the completion-reminder flow.
Nested objects:
safety: { off_switch_confirmed, access_rights_confirmed }— booleans, recorded but don't block submissionpii: { handles_pii, legal_ref, data_types, consent_recorded }— Yes/No radio with conditional fieldsdeclarations: { governance, accuracy, upkeep }— three required checkboxes
Module-level enum constants live in models.py (BUSINESS_ENTITIES, CLIENT_SCOPES, AGENT_CLASSIFICATIONS, AUTONOMY_LEVELS, IP_OWNERSHIPS, DISCIPLINES).
Form field mapping
- "Studio / Department" form input → stored as
studio_name(notagent_department—agent_departmentcontinues to be auto-populated by Gemini audit) - Author / Contact Person — autofilled from logged-in user's email; not a user-editable input
- Capabilities — 8 fixed checkboxes ∪ comma-split values from "Other" free-text input, stored as
agent_capabilities: list[str]
Discipline rename
Discipline value Optimization (US) was renamed to Optimisation (UK) in 2026-05. Migration runs on startup (crud.migrate_optimisation_spelling). All template dropdowns + Gemini system prompt updated. Defensive Optimization → Optimisation normalisation in audit_analyzer.store_audit_result and apply_classification_fields.
Required-field enforcement
The /agent-register POST handler enforces required fields server-side via Form(...) defaults plus explicit conditional checks (e.g. client_name required if client_scope == "specific", all 3 declarations required). Existing agents are grandfathered via the startup migration that backfilled registration_complete based on created_by.
Completion flow for LibreChat-synced agents
LibreChat-synced agents enter via the collector with created_by="agent_collector_api" and only a subset of fields populated. They land with registration_complete=False (set in crud.create_agent_from_collector via setdefault). Owners are nudged via daily APScheduler email job (notifications.send_completion_reminders):
- Job groups incomplete agents by lowercased
agent_contact_personand resolves to active users (case-insensitive email match) - Per-user cooldown (default 7 days) + nudge cap (default 4) tracked in
completion_reminderscollection - Single digest email per user with one "Complete →" CTA per agent
- Submitting the completion form flips
registration_complete=TrueAND reassignscreated_byfrom the marker to the submitting user's ID — they then own it normally for future edits
agent_register.html is parameterised: mode="register" (default) or mode="complete". The completion-mode handler _build_completion_context() in main.py pre-computes capability sets, derived client_scope from legacy client field, and PII/safety pre-fills. Special case: agent_purpose renders blank if it equals agent_description (collector defaults purpose=description for ~94% of agents per prod data analysis).
Gemini audit auto-classification (extended 2026-05)
audit_analyzer.SYSTEM_INSTRUCTION schema extended to also return:
agent_classification(Utility/Functional/Supervisory/Guardian) — auto-applied toagent_classificationfield if emptyautonomy_level_hint(Human-Led/Hybrid/Autopilot/null) — auto-applied toautonomy_levelif emptyagent_classification_reasoning,autonomy_reasoning— stored on agent doc as audit fields (not used as primary values)
Running POST /api/admin/audit/run after deploy auto-fills these on the ~225 agents with instructions, leaving only the truly non-inferable fields (business_entity, ip_ownership, declarations) for the human owner via the completion form.
Filter dimensions
Both agent_management.html and admin/dashboard.html agent views support filtering by:
- Business Entity, Agent Type, Autonomy Level (new dropdowns)
- "Compliance risks" quick toggle — shows agents where
pii.handles_pii=trueORip_ownership="Shared/TBD"ORautonomy_level="Autopilot"
CSV roundtrip for backfilling
/api/admin/agents/export/csv includes 21 new columns covering all governance fields. Import accepts them; nested objects (safety, pii, declarations) only build up if at least one cell is populated, so partially-filled CSVs don't clobber existing data with False. Defensive _csv_bool() helper returns None for empty cells.
UI & Brand (2026-05)
The frontend was rebranded to the OLIVER master template (/Users/nickviljoen/Documents/Optical_Projects/Pres Template/OLIVER Master PPT Template.potx). The old muddy-orange #f3ae3e palette and Inter font are gone. See PLAN-ui-refresh.md for design rationale.
Design tokens (static/style.css :root)
Always use these tokens — do not introduce new hex codes:
--brand-yellow: #FFCB05— primary accent (page-title highlight, stat-tile left strip, card-header icons, active states, primary buttons)--brand-yellow-soft: #FFF5C4— subtle hover backgrounds, dropdown-item hover--brand-orange: #FF5C00— destructive accent (btn-danger, Microsoft SSO button, logout link)--brand-dark: #1A1A1A— body text, table headers, modal headers, nav text--brand-grey: #626262— secondary text / muted copy--brand-grey-light: #DEE2E5— borders, dividers--brand-off-white: #F6F7F7— page background, table row hover- Font: Montserrat 400/500/600/700/800 (loaded from Google Fonts in
base.html)
Signature motifs
- Page title —
.page-titleclass. Yellow highlight rectangle behind black text via alinear-gradient(yellow up to 70% of the height, transparent above). Apply to every page<h2>; subtitle goes in<p class="page-subtitle">below. Don't wrap with a leading icon — the highlight is the visual. - Stat tile —
.stat-tileclass. White card with 8px yellow::beforestrip on the left, yellow Font Awesome icon, big near-black.stat-tile-valueand uppercase.stat-tile-label. Replaced the old orange-gradient.stat-card. - Card section accent — every
<h5>/<h6>inside.card-headerautomatically gets a leading yellow icon (CSS targets.card-header h5 > i.fas:first-child) and a 28×3px yellow underline accent (::after). The accent doesn't apply to modal headers or to header titles displaying counts (those go in.card-body). - Modal header — solid
--brand-darkbackground, white text, white close button. - Active nav-link / nav-tab — 2-3px
--brand-yellowbottom border, never a coloured background pill.
Gotchas (don't re-introduce these bugs)
- Navbar must use
position: fixed(set instyle.css). Do not addposition: relativeorposition: stickyoverrides intemplates/nav.html— a previous inline<style>rule there silently overrode the fixed positioning and made the navbar scroll away with the page. - Bootstrap tabs must use
<button data-bs-target="#x">, not<a href="#x">. The anchor form causes the browser to scroll to the tab pane on click (visible as the page "jumping down"). Bootstrap 5 recommends buttons for this reason. - Body has
padding-top: 64pxto clear the fixed navbar — keep it whenever you change navbar height. - No
transform: scale(...)on table-row hover — jittery and shifts neighbour rows. Use a background-color change. - Chart.js colours in
templates/admin/dashboard.htmlreference the brand palette directly (#FFCB05,#1A1A1A,#FF5C00) because the library can't read CSS vars. Keep them aligned when adding charts.
Where things live
static/style.css— all tokens + component styles (palette, navbar, tabs, buttons, forms, tables, cards, stat-tile, page-title, modal, alerts).templates/base.html— Google Fonts link (Montserrat).templates/nav.html— navbar markup + scoped styles for user-avatar / dropdown.templates/admin/dashboard.html— inline<style>block has admin-specific overrides (column widths, audit risk badge colours, tabs); kept in-file because they're not reused elsewhere.- Page templates apply
.page-titleto their main<h2>and.page-subtitleto the description line.