legalOS — an operating system for legal departments. A multi-department, AI-native web app that gives lawyers and legal-ops staff a single, welcoming entry point to the agents and tools they use day-to-day — whether external (Gemini Gems, watsonX Orchestrate, custom links) or natively hosted inside the app.
legalOS is built to serve one corporate legal department at a time (single-tenant), with a multi-tenant-ready schema so the same codebase can later support a SaaS version for multiple legal departments. It ships with eight departments — Commercial, Public Sector, Mergers & Acquisitions, Privacy, Product, Compliance, Operations, General Tools — and is designed so adding more (Litigation, IP, etc.) is mostly configuration.
Adoption is a first-class concern. The UI is deliberately simple, clean, modern, and welcoming. Behind that simple front end is real infrastructure: role-based access, native chat with prompt caching, web search, attached references, per-message Word export, soft-delete with 30-day undo, Supabase-backed analytics, a productivity gains calculator, support flows, and an admin area.
Phase 2 — Agent product surface. Native agents fully wired (chat, attachments, caching, web search, exports); agent CRUD + soft delete + trash + 8-department launchpad behind RBAC.
| Layer | Technology | Deployment |
|---|---|---|
| Frontend | Next.js 16 (App Router), TypeScript, Tailwind CSS v4, shadcn/ui | Vercel |
| Backend | Next.js API routes + server actions | Vercel (same deploy) |
| Database | Supabase (PostgreSQL) with Row-Level Security | Supabase Cloud |
| Auth | Supabase Auth (magic link) | Supabase Cloud |
| AI / LLM | Anthropic API (Claude) for native agents; external links for Gemini Gems, watsonX, etc. | Server-side only |
| Analytics | Supabase tables; localStorage fallback in Phase 1 | Supabase Cloud |
legalos/
├── app/ # Next.js App Router
│ ├── (public)/ # Unauthenticated routes (login, landing)
│ ├── (app)/ # Authenticated routes
│ │ ├── departments/[slug]/ # Per-department launchpad page
│ │ ├── agents/[id]/ # Native agent chat page
│ │ └── admin/ # Admin dashboard (role-gated)
│ ├── api/ # Route handlers (chat proxy, webhooks)
│ └── layout.tsx # Root layout (theme provider, auth provider)
├── components/ # Reusable React components
│ ├── ui/ # shadcn/ui primitives
│ ├── launchpad/ # Agent cards, dept grids
│ ├── chat/ # Chat UI for native agents
│ └── admin/ # Admin dashboard components
├── styles/ # Global CSS and theme preset files (imported from app/globals.css)
├── lib/ # Shared utilities
│ ├── supabase/ # Supabase clients (server, browser, middleware)
│ ├── anthropic/ # Anthropic API client wrappers
│ ├── auth/ # Auth helpers, role checks
│ ├── actions/ # Server actions (promote to top-level when >~8 files)
│ ├── hooks/ # Custom React hooks (promote to top-level when >~8 files)
│ └── analytics/ # Event tracking helpers
├── config/ # Site configuration
│ ├── site.ts # Branding, company name, theme preset
│ ├── departments.ts # Department metadata (seed/override)
│ └── theme.ts # Theme token definitions
├── supabase/ # Supabase config
│ ├── migrations/ # SQL migrations
│ ├── seed.sql # Seed data
│ └── policies/ # RLS policy SQL (organized by table)
├── .claude/
│ └── skills/ # Skill files copied from claude-templates
├── public/ # Static assets
├── CLAUDE.md # This file
├── PROJECT_OUTLINE.md # Phases, architecture, roadmap
├── DECISION_LOG.md # Architecture decisions with reasoning
├── SETUP.md # Setup guide for new forks
├── CHANGELOG.md # Version history
├── README.md # Project overview for GitHub
├── .env.example # Env var template
└── proxy.ts # Auth proxy (Next.js 16 file convention, formerly middleware.ts)
External agent click (launchpad → third-party platform): User → Launchpad page → Click card → Log analytics event to Supabase → Open external URL in new tab.
Native agent click (launchpad → in-app chat):
User → Launchpad page → Click card → Navigate to /agents/[id] → Chat UI loads conversation history from Supabase → User sends message → Next.js server action → Anthropic API → Stream response to UI → Persist messages to Supabase.
Critical constraints:
agent-card.tsx exports AgentCard).@/components/...) → relative. One blank line between groups."use client" only when you need state, effects, or browser APIs. All Anthropic calls happen on the server — never in client components.any. Use unknown and narrow when data shape is uncertain. Database types come from generated Supabase types.{ ok: true, data } | { ok: false, error }. Never throw across trust boundaries.async/await only. No bare promise chains in application code.users_can_read_own_conversations).snake_case for tables and columns. Tables are plural (users, agents, conversations).created_at and updated_at with defaults and triggers.type: descriptionfeat, fix, refactor, chore, docs, test, security, db (for schema/migration-only changes)feature/description, fix/description, chore/description, db/descriptionEvery commit must leave the codebase in an internally consistent state. No commit may reference symbols — ADR IDs, function names, files, modules, doc cross-links — that don’t exist until a later commit.
When two changes are coupled by reference, either bundle them into one commit or order the commits so the referenced target lands first.
Why: broken intermediate states make git bisect unreliable, complicate PR review, and turn future readers of the history into archaeologists. A commit should build, lint, and read coherently when checked out in isolation.
UPPER_SNAKE_CASE. Client-exposed vars must be prefixed NEXT_PUBLIC_; all others stay server-only..env.local for dev, Vercel env vars for preview/prod. .env.example is the canonical list of required variables.ANTHROPIC_API_KEY (no NEXT_PUBLIC_ prefix, ever). If you ever see NEXT_PUBLIC_ANTHROPIC_... anywhere in this codebase, that is a critical security bug — stop and fix it before continuing.*.test.ts next to the code they test. Business logic, auth helpers, and validation schemas are always tested.NEXT_PUBLIC_, never in a client component, never in a client-side fetch.proxy.ts) checks role, and RLS policies re-enforce at the DB level.This app handles attorney work product, and in some future deployments may handle privileged or confidential information. Treat every AI integration accordingly.
agents table), not in code, so they can be updated without a deploy. System prompts are loaded server-side and never exposed to the client.usage_events table.user.id from the client. Always read it from the Supabase server session.departments table.When a feature is being ported from an upstream reference (currently the prior agent-launchpad-template, located at ../agent-launchpad-template/ relative to this repo, and any future reference repos this project draws from), you must read the original first and replicate it field-for-field, formula-for-formula, interaction-for-interaction.
Why: paraphrased descriptions of UX leak content. Ports built against a description rather than the source drift in subtle, compounding ways — field labels shift, formulas get “simplified,” interactions get “improved.” The reference is the source of truth for behavior; only the visual style is allowed to drift (per Constraint B — shadcn defaults, no theme port).
How to apply:
This applies to every reference port from this point forward. Sessions that port functionality begin with a verbatim read of the source; the plan presented to the user names the specific source files and line ranges read.
When making any product change (new feature, renamed component, new page, architectural change, design system change, or brand update), the following files must be checked and updated if affected:
This is not optional. Documentation updates are part of the definition of done for every change.
At the end of every phase or significant feature completion, sync generalized lessons back to the portable claude-templates library. Extract the universal principle, not the project-specific detail. If a new rule or convention is added to this project’s CLAUDE.md, evaluate whether it belongs in the template CLAUDE.md as well.
Before declaring a session done, triple-check that what was reported shipped is actually in origin/main:
git status — must show working tree clean. Anything modified or untracked is unshipped work.git log --oneline — recent commits must include the session’s deliverables.git rev-parse HEAD vs git rev-parse origin/main — must match. A local-only commit is not a shipped commit.Why: Session 5 closed with the entire admin shell uncommitted, despite reporting “done.” The Session 5 fix audit caught the gap by running this triple-check explicitly; adopting it as session-close discipline prevents recurrence.
Before performing any of the following types of work, you MUST read the specified skill file(s) in .claude/skills/ and follow their conventions. Do not rely on memory from previous sessions; re-read the skill file every time.
| Task Type | Read First | Examples |
|---|---|---|
| Any frontend work | nextjs.md + react-patterns.md + tailwind.md |
Components, pages, layouts, styling |
| Any UI/UX decision | ui-patterns.md + responsive-design.md + ux-writing.md |
Component design, error copy, empty states |
| Any accessibility concern | web-accessibility.md |
Forms, navigation, modals, keyboard flows |
| Any backend/API work | api-security.md + backend-security.md |
Route handlers, server actions, proxy (proxy.ts) |
| Any database work | supabase.md + database-patterns.md + database-security.md |
Schema changes, migrations, queries, RLS policies |
| Any auth work | supabase.md + backend-security.md + api-security.md |
Login, role checks, session handling |
| Any AI/prompt work | anthropic-api.md + prompt-engineering.md |
System prompts, Claude API calls, streaming |
| Any deployment/env work | vercel-deployment.md + environment-management.md + infra-security.md |
Build config, env vars, preview deploys |
| Any frontend security concern | frontend-security.md |
CSP, XSS prevention, cookie handling |
| Any analytics or cost work | analytics.md + cost-tracking.md |
Event tracking, dashboards, token logging |
| Any model provider change | model-abstraction.md |
Adding OpenAI, Google, or other providers |
| Any eval or quality work | eval-framework.md + observability.md |
Regression tests, tracing, monitoring |
| Any CI/CD work | ci-cd.md |
GitHub Actions, PR checks, deploy gates |
| Any work touching multiple domains | Read ALL relevant skills | Full-stack features, new phases |
If you are unsure whether a skill applies, read it anyway. It is always better to over-consult than to miss a convention.
If a task requires a skill that does not exist in .claude/skills/ yet but exists in the claude-templates library, copy it into the project first, then read it.
At the end of every phase or sub-phase, after completing the standard documentation updates above, you MUST also sync any new patterns, lessons, or gotchas discovered during that phase to the portable skill templates at <claude-templates>/skills/.
.claude/skills/ that was referenced or updated during this phase.<claude-templates>/skills/.If during a phase you discover that Supabase RLS policies behave differently when a service-role key is used on joined tables, the project skill gets the specific fix. The portable template gets: “When using admin/service role keys that bypass row-level security, verify behavior on queries involving joins, as RLS policies may not propagate across joined tables as expected.”
This is not optional. No phase is complete until both project-specific skills AND portable templates are current.