Add spec pack
This commit is contained in:
@@ -0,0 +1,54 @@
|
|||||||
|
# CLAUDE.md — Claude Code Implementation Guide
|
||||||
|
## SOP Governance Tool (Stage 1, single-tenant)
|
||||||
|
|
||||||
|
You are pair-programming with a **coding beginner** who is the product owner and domain expert (3PL logistics). Optimize for his learning and for maintainability, not for cleverness.
|
||||||
|
|
||||||
|
## Project context
|
||||||
|
- Full specs live in `/docs` — READ THE RELEVANT SPEC before implementing anything:
|
||||||
|
`01-PRD` (scope/roles) · `02-FRD` (requirements + acceptance criteria) · `03-architecture` (stack & decisions AD-1…AD-6) · `04-database-schema` (DDL + RLS) · `05-api-spec` (routes & payloads) · `06-ui-ux` (screens & tokens) · `07-development-plan` (milestones M0–M7, incl. M6.5 incident log).
|
||||||
|
- Stack is FIXED: Next.js App Router + TypeScript + Tailwind + shadcn/ui + Supabase + Vercel + Anthropic API. Do not introduce other databases, ORMs (use supabase-js), state libraries, or i18n frameworks.
|
||||||
|
- Single org today, but `org_id` + RLS on every table is non-negotiable (AD-1).
|
||||||
|
|
||||||
|
## How to work with me (the human)
|
||||||
|
1. **Explain as you go.** After writing code, give a 3–5 line plain-English summary of what it does and why. If I ask "explain", teach me line-by-line.
|
||||||
|
2. **Small steps.** Implement ONE checklist item at a time, then tell me exactly how to test it in the browser before moving on.
|
||||||
|
3. **Ask before:** adding any dependency, changing the schema, deviating from a spec, or deleting more than ~30 lines.
|
||||||
|
4. **Never** put secrets in client code, disable RLS "temporarily", or mock away auth to make something work.
|
||||||
|
5. When something fails, show me the error, your diagnosis in plain English, then the fix.
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
- TypeScript strict; server components by default, `"use client"` only when needed.
|
||||||
|
- All workflow mutations go through `/app/api/*` route handlers using the service-role client; state transitions ONLY via `lib/workflow.ts`; every mutation writes `audit_log` in the same transaction (AD-3).
|
||||||
|
- Reads use the user-scoped client under RLS.
|
||||||
|
- Errors: return `{ error: { code, message } }` with correct HTTP status (see 05-api-spec §5).
|
||||||
|
- Commits: after each working step, imperative plain English ("Add SOP publish route with version snapshot").
|
||||||
|
- Files: kebab-case; components PascalCase; one component per file.
|
||||||
|
|
||||||
|
## Session plan (follow in order; ~2 hours each)
|
||||||
|
Before each session: read the matching milestone in `docs/07-development-plan.md` and the FRD items it cites. After each: run the ✅ checklist with me, commit, push (Vercel auto-deploys).
|
||||||
|
|
||||||
|
- **S1 (M0):** Scaffold app, Tailwind, shadcn init; Supabase clients in `lib/supabase/`; env wiring; deploy hello world.
|
||||||
|
- **S2 (M0):** Apply `db/schema.sql` + `db/policies.sql` + `db/seed.sql`; verify tables + RLS deny-by-default with an anon query test.
|
||||||
|
- **S3–4 (M1):** Login page + session; profiles + JWT claims hook; role-redirect middleware; route guards.
|
||||||
|
- **S5 (M1):** Users & Departments admin pages (invite, role change, deactivate).
|
||||||
|
- **S6–7 (M2):** SOP list (filters/search) + create dialog; SOP detail shell with status badge.
|
||||||
|
- **S8–9 (M2):** Structured editor: sections nav, step cards (add/reorder/delete), photo upload to Storage, autosave.
|
||||||
|
- **S10 (M3):** `lib/workflow.ts` (canTransition, nextVersion) with unit tests; submit route + validation.
|
||||||
|
- **S11 (M3):** decide (approve/reject) + approvals queue page; publish route with version snapshot transaction; versions tab.
|
||||||
|
- **S12 (M3):** `lib/audit.ts`; wire audit into all mutations; audit page + per-SOP history tab.
|
||||||
|
- **S13 (M4):** Assign-departments dialog + route; staff `/my-sops` list (mobile shell, needs-ack vs acked).
|
||||||
|
- **S14 (M4):** Staff SOP viewer (mobile, language switcher) + acknowledge flow (typed-name sheet, immutability, re-ack on new version) + share link.
|
||||||
|
- **S15 (M5):** Dashboard cards + per-SOP acknowledgement table + department table + overdue badges.
|
||||||
|
- **S16 (M5):** Ack report endpoint + CSV export; audit CSV.
|
||||||
|
- **S17 (M6):** `lib/ai/` (client, prompts, JSON parser with one retry); `/api/ai/draft` + editor modal; ai_log + daily rate limit.
|
||||||
|
- **S18 (M6):** `/api/ai/translate` + translations tab + reviewed flag + staff-viewer fallback rule (safety → English until reviewed).
|
||||||
|
- **S19 (M6.5):** Incidents table + policies; POST /api/incidents; staff report flow (viewer "Report a problem" + My SOPs button, severity buttons, photo upload).
|
||||||
|
- **S20 (M6.5):** Admin incident list with filters + detail; review/close route with resolution note + "requires SOP revision" jump; SOP detail Incidents tab; dashboard open-incidents card.
|
||||||
|
- **S21 (M7):** Security checklist from `docs/03-architecture.md §6` — verify each item with me and show the evidence.
|
||||||
|
- **S22 (M7):** Real data seed for the company; fix onboarding frictions; final walkthrough of `docs/02-FRD.md` acceptance criteria.
|
||||||
|
|
||||||
|
## Definition of done (any task)
|
||||||
|
Works in the browser · matches its FRD acceptance criteria · no TypeScript errors · explained to me in plain English · committed · deployed preview checked.
|
||||||
|
|
||||||
|
## Scratch — next session goal
|
||||||
|
(Human writes 1–2 lines here before each session.)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# SOP Governance Tool — Spec Pack (Stage 1)
|
||||||
|
|
||||||
|
Complete specification suite for the single-tenant SOP governance tool, written to be dropped into the project repository and used by **Claude Code**.
|
||||||
|
|
||||||
|
## Reading order (human)
|
||||||
|
| # | File | What it answers |
|
||||||
|
|---|------|-----------------|
|
||||||
|
| 1 | `docs/01-PRD.md` | What we're building, for whom, and what we're deliberately NOT building |
|
||||||
|
| 2 | `docs/02-FRD.md` | Every functional requirement with testable acceptance criteria (FR-x.x) |
|
||||||
|
| 3 | `docs/03-architecture.md` | Stack, directory layout, and the 6 architectural decisions (AD-1…AD-6) |
|
||||||
|
| 4 | `docs/04-database-schema.md` | Full Postgres DDL, JSON shapes, RLS policy matrix, seed plan |
|
||||||
|
| 5 | `docs/05-api-spec.md` | Every mutation & AI route with request/response examples and error codes |
|
||||||
|
| 6 | `docs/06-ui-ux.md` | Screen-by-screen specs, design tokens, mobile staff experience |
|
||||||
|
| 7 | `docs/07-development-plan.md` | M0–M7 milestones, weekly rhythm, scope-cut order |
|
||||||
|
| 8 | `CLAUDE.md` | Session-by-session implementation plan + working rules for Claude Code |
|
||||||
|
|
||||||
|
## How to use with Claude Code
|
||||||
|
1. Create your repo, copy this whole folder in (`CLAUDE.md` at repo root, `docs/` alongside).
|
||||||
|
2. Open Claude Code in the repo. It reads `CLAUDE.md` automatically.
|
||||||
|
3. Say: **"Read CLAUDE.md and docs/07-development-plan.md, then start Session S1."**
|
||||||
|
4. One session at a time. Run each milestone's ✅ checklist before moving on.
|
||||||
|
|
||||||
|
## Scope guardrails
|
||||||
|
- **Stage 1 only:** one company, fixed roles, 1-level approval, minimal incident log (report + review, no escalation workflows), no billing, no WhatsApp API (share links manually).
|
||||||
|
- **Stage-2 ready by design:** `org_id` on every table + RLS from day one — multi-tenant SaaS later is an evolution, not a rewrite.
|
||||||
|
- If a feature isn't in `02-FRD.md`, it is out of scope. Add it to a "later" list, don't build it.
|
||||||
|
|
||||||
|
Parent documents: BRD v1.1 · Solo-Founder Roadmap (Stage 0 → 1 → 2).
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Product Requirements Document (PRD)
|
||||||
|
## SOP Governance Tool — Stage 1 (Single-Tenant)
|
||||||
|
|
||||||
|
**Version:** 1.0 · **Scope:** Stage 1 of the Solo-Founder Roadmap · **Parent docs:** BRD v1.1, Solo-Founder Roadmap
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Purpose
|
||||||
|
|
||||||
|
A web application for ONE company (the founder's own 3PL warehouse) that replaces the manual Stage-0 concierge workflow: create governed SOPs with AI assistance, approve them, distribute them to staff in their language, and collect signed acknowledgements with an audit trail.
|
||||||
|
|
||||||
|
This is a learning-and-validation build. It must be simple enough for a beginner developer (working with Claude Code) to build in 3–4 months at 10–15 hrs/week, while laying foundations (org_id everywhere, RLS) that make the Stage-2 SaaS an evolution rather than a rewrite.
|
||||||
|
|
||||||
|
## 2. Problem Statement
|
||||||
|
|
||||||
|
Today (Stage 0), the founder delivers SOP governance manually: AI chat for drafting, Word templates, WhatsApp for distribution, screenshots + a Google Sheet for acknowledgement records. It works and earns money, but it doesn't scale past ~3 clients and consumes founder hours on clerical work.
|
||||||
|
|
||||||
|
## 3. Goals & Non-Goals
|
||||||
|
|
||||||
|
### Goals
|
||||||
|
1. Replace the manual workflow for the founder's own warehouse (tenant #1).
|
||||||
|
2. SOP lifecycle: Draft → Submitted → Approved → Published, with version history.
|
||||||
|
3. Staff view SOPs on their phones in their language (EN / BM / 中文) and acknowledge with one tap.
|
||||||
|
4. Dashboard showing acknowledgement % per SOP and per department.
|
||||||
|
5. AI assistance: draft an SOP from a description; translate an approved SOP.
|
||||||
|
6. Every governance action recorded in an audit log.
|
||||||
|
7. Staff can report operational incidents (problem + severity + photo, optionally linked to an SOP); approver/admin review and close them, feeding SOP revisions.
|
||||||
|
|
||||||
|
### Non-Goals (explicitly out — do not build)
|
||||||
|
- Multi-tenancy UI, billing, subscriptions, self-serve signup
|
||||||
|
- WhatsApp Business API integration (Stage 1.5 — staff access via a link for now)
|
||||||
|
- Checklists, evidence capture, training/quizzes, RACI, flow diagrams
|
||||||
|
- Incident escalation chains, SLA timers, incident alerts (the incident log is capture-and-review only)
|
||||||
|
- Custom roles, SSO, configurable approval chains (fixed 1-level approval only)
|
||||||
|
- Native mobile app (mobile-first web only)
|
||||||
|
- DOCX/PDF import (paste text instead)
|
||||||
|
|
||||||
|
## 4. Users & Roles (fixed set)
|
||||||
|
|
||||||
|
| Role | Who | Can do |
|
||||||
|
|---|---|---|
|
||||||
|
| **Admin** | The founder | Everything: manage users/departments, edit/approve/publish any SOP, view dashboard & audit log |
|
||||||
|
| **Approver** | Ops manager / dept head | Approve or reject submitted SOPs in their scope; view dashboard |
|
||||||
|
| **Editor** | Supervisors | Create/edit drafts, submit for approval, use AI draft/translate |
|
||||||
|
| **Staff** | Frontline workers | View published SOPs assigned to them (own language), acknowledge |
|
||||||
|
|
||||||
|
One user has exactly one role (keep it simple). Admin can change a user's role.
|
||||||
|
|
||||||
|
## 5. Core User Stories
|
||||||
|
|
||||||
|
1. As an **Editor**, I describe a process in plain language and get an AI-drafted SOP I can edit, so drafting takes minutes not days.
|
||||||
|
2. As an **Editor**, I submit a draft; the **Approver** gets it in their queue and approves or rejects with a comment.
|
||||||
|
3. As an **Admin**, publishing an approved SOP creates version 1.0 (or increments), assigns it to selected departments, and freezes that version's content.
|
||||||
|
4. As a **Staff** member, I open a link on my phone, see the SOP in my preferred language with large readable steps, and tap "I have read and agree" — recording my name, the version, language, and timestamp.
|
||||||
|
5. As an **Admin**, I see per-SOP and per-department acknowledgement % and can export the acknowledgement list.
|
||||||
|
6. As an **Approver**, I can view any prior version of an SOP and who approved it.
|
||||||
|
7. As a **Staff** member, when something goes wrong on the floor, I report it from my phone in under a minute — with a photo — and the **Admin** sees which SOPs keep generating problems and revises them.
|
||||||
|
|
||||||
|
## 6. Success Metrics (Definition of Done for Stage 1)
|
||||||
|
|
||||||
|
- Founder's warehouse runs on it: ≥ 20 published SOPs, ≥ 80% staff acknowledgement.
|
||||||
|
- An SOP goes from idea → published in under 1 hour including approval.
|
||||||
|
- Zero manual screenshot/Sheet work remains for tenant #1.
|
||||||
|
- A non-technical Approver and a Staff member can use it with no training beyond one WhatsApp message.
|
||||||
|
- Audit log answers: who created, edited, approved, published, acknowledged — with timestamps.
|
||||||
|
|
||||||
|
## 7. Constraints & Principles
|
||||||
|
|
||||||
|
- **Stack is fixed:** Next.js (App Router) + Supabase + Vercel + Anthropic API. No substitutions.
|
||||||
|
- **Beginner-maintainable:** prefer boring, well-documented patterns over clever ones.
|
||||||
|
- **org_id on every table** from day one, with RLS. Single-tenant today, SaaS-ready tomorrow.
|
||||||
|
- **AI is server-side only.** No API keys in the browser. AI never auto-publishes.
|
||||||
|
- **Mobile-first for Staff screens;** desktop-first for Admin/Editor screens.
|
||||||
|
- **English UI** for admin screens; SOP *content* is multilingual (EN/BM/ZH).
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# Functional Requirements Document (FRD)
|
||||||
|
## SOP Governance Tool — Stage 1
|
||||||
|
|
||||||
|
Each requirement has an ID, description, and acceptance criteria (AC). Build modules in order M1→M7 (see 07-development-plan.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M1 — Authentication & Users
|
||||||
|
|
||||||
|
**FR-1.1 Login/logout** — Email + password via Supabase Auth.
|
||||||
|
AC: Wrong password shows a friendly error; session persists across refresh; logout clears session.
|
||||||
|
|
||||||
|
**FR-1.2 User management (Admin)** — Admin creates users (name, email, role, department, preferred_language), deactivates users, changes roles.
|
||||||
|
AC: Deactivated users cannot log in; a Staff user cannot open any admin page (redirected).
|
||||||
|
|
||||||
|
**FR-1.3 Departments (Admin)** — Create/rename departments; assign users to one department.
|
||||||
|
AC: Deleting a department with users is blocked with a clear message.
|
||||||
|
|
||||||
|
**FR-1.4 Role guard** — Every page and API route enforces the role matrix in the PRD §4.
|
||||||
|
AC: A Staff JWT calling an admin API receives 403; verified by test.
|
||||||
|
|
||||||
|
## M2 — SOP Repository & Editor
|
||||||
|
|
||||||
|
**FR-2.1 SOP record** — Fields: code (e.g. WH-PICK-001), title, department, category, owner (user), status, review_months (3/6/12/24).
|
||||||
|
AC: Code is unique; list view filters by department, status, category; search by title/code.
|
||||||
|
|
||||||
|
**FR-2.2 Structured editor** — Sections: Purpose, Scope, Roles & Responsibilities, Procedure (ordered steps, each step = text + optional photo), Exceptions, Safety Notes, Required Records. Purpose + Procedure mandatory.
|
||||||
|
AC: Cannot submit for approval with a mandatory section empty; steps can be added, reordered, deleted; photos upload to Supabase Storage and preview inline.
|
||||||
|
|
||||||
|
**FR-2.3 Autosave drafts** — Editor autosaves every 10s or on blur.
|
||||||
|
AC: Refresh mid-edit loses at most 10 seconds of typing.
|
||||||
|
|
||||||
|
## M3 — Governance: Workflow, Versions, Audit
|
||||||
|
|
||||||
|
**FR-3.1 Status flow** — draft → submitted → approved → published; rejection returns submitted → draft with a required comment. Published SOPs edited again create a NEW draft version; the published version stays live until the new one is published.
|
||||||
|
AC: Illegal transitions (e.g. draft → published) are impossible via UI and API.
|
||||||
|
|
||||||
|
**FR-3.2 Approval (1 level)** — Approver/Admin sees a queue of submitted SOPs; approves or rejects with comment.
|
||||||
|
AC: Approval records approver id, timestamp, decision, comment; Editor is notified in-app.
|
||||||
|
|
||||||
|
**FR-3.3 Versioning** — First publish = 1.0. Each subsequent publish increments: minor edit → 1.1, "major revision" checkbox → 2.0. Published version content is immutable (stored snapshot).
|
||||||
|
AC: Version history list shows every version with date, publisher, change note; opening an old version renders its frozen content.
|
||||||
|
|
||||||
|
**FR-3.4 Audit log** — Append-only records for: sop.created, sop.edited, sop.submitted, sop.approved, sop.rejected, sop.published, sop.assigned, ack.recorded, user.created, user.role_changed.
|
||||||
|
AC: Log entries store actor, action, entity, timestamp, and detail JSON; Admin can filter by SOP and export CSV. No update/delete path exists.
|
||||||
|
|
||||||
|
**FR-3.5 Review reminders** — Dashboard flags SOPs past `published_at + review_months`.
|
||||||
|
AC: An overdue SOP shows a red "Review overdue" badge on dashboard and list.
|
||||||
|
|
||||||
|
## M4 — Distribution & Acknowledgement
|
||||||
|
|
||||||
|
**FR-4.1 Assignment** — On publish (or later), Admin assigns the SOP to one or more departments; all active Staff in those departments become assignees. New staff added to a department inherit its assignments.
|
||||||
|
AC: Assignee list is visible per SOP; unassigning a department removes pending (not completed) acknowledgement expectations.
|
||||||
|
|
||||||
|
**FR-4.2 Staff "My SOPs" page (mobile-first)** — Staff log in and see assigned published SOPs: "Needs acknowledgement" on top, then "Acknowledged". Content renders in the staff member's preferred_language if a translation exists, else English, with a language switcher.
|
||||||
|
AC: Loads fast on a phone; steps render with large text and photos; language switch is one tap.
|
||||||
|
|
||||||
|
**FR-4.3 Acknowledgement** — Button: "I have read and agree / Saya faham & setuju / 我已阅读并同意". Tap records user, SOP, version, language viewed, timestamp, and a typed-name confirmation field (acts as the signature).
|
||||||
|
AC: One acknowledgement per user per published version; a NEW published version resets the requirement (re-ack needed); record is immutable.
|
||||||
|
|
||||||
|
**FR-4.4 Share link** — Each published SOP has a link Staff can open after login (paste into a WhatsApp group manually for now).
|
||||||
|
AC: Link deep-links to that SOP in the viewer for an authenticated assignee; unauthenticated users hit login first, then land on the SOP.
|
||||||
|
|
||||||
|
## M5 — Dashboard & Reports
|
||||||
|
|
||||||
|
**FR-5.1 Dashboard (Admin/Approver)** — Cards: total published SOPs, drafts, pending approvals, overdue reviews. Table: per-SOP acknowledgement % with progress bar; filter by department. Second table: per-department overall %.
|
||||||
|
AC: Numbers reconcile with raw data (verified by a seeded test dataset).
|
||||||
|
|
||||||
|
**FR-5.2 Acknowledgement export** — Per SOP: CSV of assignees with acknowledged version/date/language or "outstanding".
|
||||||
|
AC: Opens correctly in Excel; includes SOP code, title, version in the header rows.
|
||||||
|
|
||||||
|
## M6 — AI Assistance
|
||||||
|
|
||||||
|
**FR-6.1 AI Draft** — Editor enters a plain-language description (and optionally pastes messy notes); server calls Anthropic API and returns a structured draft filling the FR-2.2 sections; Editor reviews/edits before saving.
|
||||||
|
AC: AI output populates the editor fields (never publishes anything); a failed AI call shows a retry-able error without losing the input; prompt + response stored in ai_log for cost tracking.
|
||||||
|
|
||||||
|
**FR-6.2 AI Translate** — On an approved/published SOP, generate BM and/or ZH translations of all sections. Translations are stored per version and flagged `machine=true` until a human marks "reviewed".
|
||||||
|
AC: Publishing a new version marks old translations stale and prompts regeneration; safety-notes sections require the human "reviewed" flag before the translation is shown to Staff (English shown until then).
|
||||||
|
|
||||||
|
**FR-6.3 AI guardrails** — All AI routes: server-side only, role Editor+, rate-limited (e.g. 20 calls/user/day), inputs capped (~8k chars).
|
||||||
|
AC: Exceeding the limit returns a clear message; the Anthropic key never appears in client bundles (verified by build inspection).
|
||||||
|
|
||||||
|
## M7 — Operational Incident Log (minimal)
|
||||||
|
|
||||||
|
**FR-7.1 Report an incident (staff+, any role)** — From the staff SOP viewer ("Report a problem" link) or a standalone "Report incident" button on My SOPs. Fields: description (required), severity (low/medium/high), optional photo, optional linked SOP (pre-filled when opened from a viewer; records the SOP version too).
|
||||||
|
AC: Submitting takes < 60 seconds on a phone; photo uploads to Storage; reporter, timestamp, and department are recorded automatically; confirmation screen shown.
|
||||||
|
|
||||||
|
**FR-7.2 Incident list (approver/admin)** — Table of incidents: date, reporter, department, linked SOP, severity, status (open / reviewed / closed). Filter by status, severity, department, SOP. Detail view shows full description + photo.
|
||||||
|
AC: New incidents default to "open"; the dashboard shows an "Open incidents" count card (red if any high severity open).
|
||||||
|
|
||||||
|
**FR-7.3 Review & close (approver/admin)** — Status transitions open → reviewed → closed, with an optional resolution note. Closing prompts: "Does this incident require an SOP revision?" — if yes, a one-click jump to that SOP's edit page.
|
||||||
|
AC: Status changes and notes are audit-logged (`incident.reviewed`, `incident.closed`); a closed incident is read-only.
|
||||||
|
|
||||||
|
**FR-7.4 Incidents per SOP** — SOP detail page gains an "Incidents" tab listing incidents linked to that SOP across versions.
|
||||||
|
AC: An SOP with ≥3 incidents in 90 days shows a "Review suggested" hint on the dashboard.
|
||||||
|
|
||||||
|
**Deliberately excluded (do not build):** escalation chains, assignment/ownership of incidents, SLA timers, email/WhatsApp alerts, root-cause taxonomies, client-facing incident reports. The module is a capture-and-review loop feeding SOP revisions — nothing more in Stage 1.
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
# System Architecture
|
||||||
|
## SOP Governance Tool — Stage 1
|
||||||
|
|
||||||
|
## 1. Stack (fixed — do not substitute)
|
||||||
|
|
||||||
|
| Layer | Choice | Why |
|
||||||
|
|---|---|---|
|
||||||
|
| Frontend + backend | **Next.js 14+ (App Router, TypeScript)** | One codebase for pages and API routes; industry standard |
|
||||||
|
| Database / Auth / Storage | **Supabase** (Postgres + Auth + Storage + RLS) | Hands a beginner the hard parts; RLS becomes Stage-2 tenant isolation |
|
||||||
|
| Hosting | **Vercel** | Push-to-deploy; free tier sufficient |
|
||||||
|
| AI | **Anthropic API** (claude-sonnet for drafting/translation) | Called ONLY from server routes |
|
||||||
|
| Styling | **Tailwind CSS + shadcn/ui** | Fast, consistent, well-documented |
|
||||||
|
|
||||||
|
## 2. High-Level Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
[Browser: Admin/Editor desktop UI · Staff mobile UI]
|
||||||
|
│ (HTTPS)
|
||||||
|
▼
|
||||||
|
[Vercel: Next.js App]
|
||||||
|
├─ Server Components / Pages ──────────► reads via Supabase client (RLS enforced)
|
||||||
|
├─ Route Handlers /api/* ─────────────► writes + workflow logic (service role where needed)
|
||||||
|
│ └─ /api/ai/* ───────────────────► Anthropic API (server-side key)
|
||||||
|
▼
|
||||||
|
[Supabase]
|
||||||
|
├─ Postgres (all tables, RLS policies)
|
||||||
|
├─ Auth (email/password, JWT with role + org_id claims)
|
||||||
|
└─ Storage (bucket: sop-photos)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Key Architectural Decisions
|
||||||
|
|
||||||
|
**AD-1: org_id everywhere, single org today.**
|
||||||
|
Every table carries `org_id`. Stage 1 seeds exactly one org row and all data references it. RLS policies already filter by org_id. Stage 2 = add org signup + billing; data layer barely changes. Do NOT skip this "because we only have one company."
|
||||||
|
|
||||||
|
**AD-2: Roles live in a `profiles` table, mirrored into the JWT.**
|
||||||
|
`profiles(id → auth.users.id, org_id, role, department_id, preferred_language, full_name, active)`. A Postgres function copies role/org_id into JWT claims (custom access token hook) so RLS can check them cheaply.
|
||||||
|
|
||||||
|
**AD-3: Reads through RLS; privileged writes through route handlers.**
|
||||||
|
Simple reads (my SOPs, dashboard queries) use the anon/user Supabase client and rely on RLS. Workflow mutations (submit/approve/publish/assign/acknowledge) go through `/api/*` route handlers that (a) re-verify the session and role, (b) run the transition logic in one transaction via the service-role client, (c) write the audit_log row in the same transaction. This keeps state-machine rules in ONE place.
|
||||||
|
|
||||||
|
**AD-4: Published content is a frozen snapshot.**
|
||||||
|
`sop_versions.content` stores the full JSON of all sections at publish time. The editor works on the draft row; publish copies it into an immutable version row. Acknowledgements and translations reference the version, never the live draft.
|
||||||
|
|
||||||
|
**AD-5: AI calls are stateless, logged, and cheap-by-default.**
|
||||||
|
One route per capability (`/api/ai/draft`, `/api/ai/translate`). Each call: validate role + rate limit → build prompt from templates in `lib/ai/prompts.ts` → call Anthropic → validate/parse JSON output (retry once on parse failure) → log tokens to `ai_log`. Use a smaller/cheaper model for translation, a stronger one for drafting.
|
||||||
|
|
||||||
|
**AD-6: No client-side secrets, no localStorage for auth.**
|
||||||
|
Supabase handles session cookies. `ANTHROPIC_API_KEY` and `SUPABASE_SERVICE_ROLE_KEY` exist only in Vercel env vars and are only imported in server files.
|
||||||
|
|
||||||
|
## 4. Directory Layout
|
||||||
|
|
||||||
|
```
|
||||||
|
/app
|
||||||
|
/(auth)/login
|
||||||
|
/(admin)/dashboard /sops /sops/[id]/edit /approvals /users /audit
|
||||||
|
/(staff)/my-sops /my-sops/[id]
|
||||||
|
/api/sops/... /api/ai/... /api/ack ...
|
||||||
|
/components (ui/ = shadcn, sop/ = editor pieces, staff/ = mobile viewer)
|
||||||
|
/lib
|
||||||
|
supabase/ (browser client, server client, service client)
|
||||||
|
ai/ (anthropic client, prompts, parsers)
|
||||||
|
workflow.ts (status machine: canTransition(), nextVersion())
|
||||||
|
audit.ts (writeAudit())
|
||||||
|
/db
|
||||||
|
schema.sql policies.sql seed.sql
|
||||||
|
/docs (this spec pack)
|
||||||
|
CLAUDE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Environments
|
||||||
|
|
||||||
|
- **Local:** `supabase start` (local Postgres) or a free "dev" Supabase project; `.env.local` for keys.
|
||||||
|
- **Production:** Supabase project in **Singapore region** (PDPA/data-residency story), Vercel production.
|
||||||
|
- One branch (`main`) is fine for a solo dev; Vercel preview deploys per push give you a test URL.
|
||||||
|
|
||||||
|
## 6. Security Checklist (must all be true before real staff data goes in)
|
||||||
|
|
||||||
|
- [ ] RLS enabled on every table; anonymous access returns zero rows
|
||||||
|
- [ ] Staff role cannot read drafts, other users' profiles beyond names, or the audit log
|
||||||
|
- [ ] All /api routes verify session server-side (no trust of client-sent role)
|
||||||
|
- [ ] Storage bucket: authenticated read, Editor+ write, no public listing
|
||||||
|
- [ ] Service-role key and Anthropic key absent from client bundle (`next build` + search)
|
||||||
|
- [ ] Supabase daily backups enabled (dashboard toggle) and a restore has been tested once
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
# Database Schema
|
||||||
|
## SOP Governance Tool — Stage 1 (Supabase Postgres)
|
||||||
|
|
||||||
|
Run order: `schema.sql` → `policies.sql` → `seed.sql`. All tables have `org_id` (AD-1). Timestamps are `timestamptz default now()`.
|
||||||
|
|
||||||
|
## 1. Entity Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
orgs ─┬─ departments ─┬─ profiles (← auth.users)
|
||||||
|
│ └─ sop_assignments
|
||||||
|
├─ sops ─┬─ sop_versions ─┬─ acknowledgements
|
||||||
|
│ │ └─ sop_translations
|
||||||
|
│ └─ approvals
|
||||||
|
├─ incidents (→ sops, sop_versions optional)
|
||||||
|
├─ audit_log
|
||||||
|
└─ ai_log
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Tables (DDL)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
create type user_role as enum ('admin','approver','editor','staff');
|
||||||
|
create type sop_status as enum ('draft','submitted','approved','published','archived');
|
||||||
|
create type lang_code as enum ('en','ms','zh');
|
||||||
|
|
||||||
|
create table orgs (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
name text not null,
|
||||||
|
created_at timestamptz default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create table departments (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
name text not null,
|
||||||
|
unique (org_id, name)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table profiles (
|
||||||
|
id uuid primary key references auth.users(id) on delete cascade,
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
full_name text not null,
|
||||||
|
role user_role not null default 'staff',
|
||||||
|
department_id uuid references departments(id),
|
||||||
|
preferred_language lang_code not null default 'en',
|
||||||
|
active boolean not null default true,
|
||||||
|
created_at timestamptz default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create table sops (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
code text not null, -- e.g. WH-PICK-001
|
||||||
|
title text not null,
|
||||||
|
department_id uuid references departments(id),
|
||||||
|
category text,
|
||||||
|
owner_id uuid references profiles(id),
|
||||||
|
status sop_status not null default 'draft',
|
||||||
|
review_months int not null default 12 check (review_months in (3,6,12,24)),
|
||||||
|
draft_content jsonb not null default '{}'::jsonb, -- working copy (sections, steps)
|
||||||
|
current_version_id uuid, -- FK added after sop_versions exists
|
||||||
|
published_at timestamptz,
|
||||||
|
created_by uuid references profiles(id),
|
||||||
|
created_at timestamptz default now(),
|
||||||
|
updated_at timestamptz default now(),
|
||||||
|
unique (org_id, code)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table sop_versions (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
sop_id uuid not null references sops(id) on delete cascade,
|
||||||
|
version_label text not null, -- '1.0', '1.1', '2.0'
|
||||||
|
content jsonb not null, -- FROZEN snapshot of sections at publish
|
||||||
|
change_note text,
|
||||||
|
is_major boolean not null default false,
|
||||||
|
published_by uuid references profiles(id),
|
||||||
|
published_at timestamptz default now(),
|
||||||
|
unique (sop_id, version_label)
|
||||||
|
);
|
||||||
|
|
||||||
|
alter table sops
|
||||||
|
add constraint fk_current_version
|
||||||
|
foreign key (current_version_id) references sop_versions(id);
|
||||||
|
|
||||||
|
create table approvals (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
sop_id uuid not null references sops(id) on delete cascade,
|
||||||
|
decision text not null check (decision in ('approved','rejected')),
|
||||||
|
comment text,
|
||||||
|
decided_by uuid not null references profiles(id),
|
||||||
|
decided_at timestamptz default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create table sop_assignments (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
sop_id uuid not null references sops(id) on delete cascade,
|
||||||
|
department_id uuid not null references departments(id),
|
||||||
|
assigned_by uuid references profiles(id),
|
||||||
|
assigned_at timestamptz default now(),
|
||||||
|
unique (sop_id, department_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table acknowledgements (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
sop_version_id uuid not null references sop_versions(id),
|
||||||
|
user_id uuid not null references profiles(id),
|
||||||
|
language_viewed lang_code not null,
|
||||||
|
typed_name text not null, -- the "signature"
|
||||||
|
acknowledged_at timestamptz default now(),
|
||||||
|
unique (sop_version_id, user_id) -- one ack per user per version
|
||||||
|
);
|
||||||
|
|
||||||
|
create table sop_translations (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
sop_version_id uuid not null references sop_versions(id) on delete cascade,
|
||||||
|
language lang_code not null,
|
||||||
|
content jsonb not null,
|
||||||
|
machine boolean not null default true,
|
||||||
|
reviewed_by uuid references profiles(id),
|
||||||
|
reviewed_at timestamptz,
|
||||||
|
created_at timestamptz default now(),
|
||||||
|
unique (sop_version_id, language)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table audit_log (
|
||||||
|
id bigint generated always as identity primary key,
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
actor_id uuid references profiles(id),
|
||||||
|
action text not null, -- 'sop.published', 'ack.recorded', ...
|
||||||
|
entity_type text not null, -- 'sop','user','acknowledgement'
|
||||||
|
entity_id uuid,
|
||||||
|
detail jsonb,
|
||||||
|
created_at timestamptz default now()
|
||||||
|
);
|
||||||
|
-- Append-only: grant INSERT/SELECT; never UPDATE/DELETE (enforced by grants + no policy).
|
||||||
|
|
||||||
|
create table ai_log (
|
||||||
|
id bigint generated always as identity primary key,
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
user_id uuid references profiles(id),
|
||||||
|
kind text not null check (kind in ('draft','translate')),
|
||||||
|
input_chars int, output_chars int,
|
||||||
|
input_tokens int, output_tokens int,
|
||||||
|
model text, success boolean, error text,
|
||||||
|
created_at timestamptz default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create type incident_severity as enum ('low','medium','high');
|
||||||
|
create type incident_status as enum ('open','reviewed','closed');
|
||||||
|
|
||||||
|
create table incidents (
|
||||||
|
id uuid primary key default gen_random_uuid(),
|
||||||
|
org_id uuid not null references orgs(id),
|
||||||
|
reporter_id uuid not null references profiles(id),
|
||||||
|
department_id uuid references departments(id), -- copied from reporter at insert
|
||||||
|
sop_id uuid references sops(id), -- optional link
|
||||||
|
sop_version_id uuid references sop_versions(id), -- optional, set when reported from viewer
|
||||||
|
description text not null,
|
||||||
|
severity incident_severity not null default 'medium',
|
||||||
|
photo_path text, -- Storage path, optional
|
||||||
|
status incident_status not null default 'open',
|
||||||
|
resolution_note text,
|
||||||
|
reviewed_by uuid references profiles(id),
|
||||||
|
closed_at timestamptz,
|
||||||
|
created_at timestamptz default now()
|
||||||
|
);
|
||||||
|
|
||||||
|
create index idx_incidents_status on incidents(org_id, status, severity);
|
||||||
|
create index idx_incidents_sop on incidents(sop_id);
|
||||||
|
|
||||||
|
create index idx_sops_org_status on sops(org_id, status);
|
||||||
|
create index idx_ack_version on acknowledgements(sop_version_id);
|
||||||
|
create index idx_audit_entity on audit_log(entity_type, entity_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### draft_content / content JSON shape
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"purpose": "…",
|
||||||
|
"scope": "…",
|
||||||
|
"roles": "…",
|
||||||
|
"steps": [ { "order": 1, "text": "…", "photo_path": "sop-photos/…" } ],
|
||||||
|
"exceptions": "…",
|
||||||
|
"safety": "…",
|
||||||
|
"records": "…"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. RLS Policy Sketch (`policies.sql`)
|
||||||
|
|
||||||
|
Enable RLS on every table. Helper: JWT carries `org_id` and `role` claims (custom access token hook reading `profiles`).
|
||||||
|
|
||||||
|
| Table | staff | editor | approver | admin |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| sops (read) | published + assigned to their dept only | all in org | all in org | all |
|
||||||
|
| sops (write) | — | insert/update drafts | — | all |
|
||||||
|
| sop_versions | read if assigned | read | read | all |
|
||||||
|
| acknowledgements | insert own; read own | read | read | read |
|
||||||
|
| profiles | read own + names in org | read org | read org | all |
|
||||||
|
| audit_log | — | — | read | read |
|
||||||
|
| approvals | — | read | insert/read | all |
|
||||||
|
| translations | read | read/write | read | all |
|
||||||
|
| incidents | insert own; read own | read org | read/update org | all |
|
||||||
|
|
||||||
|
Representative policy:
|
||||||
|
```sql
|
||||||
|
alter table acknowledgements enable row level security;
|
||||||
|
|
||||||
|
create policy ack_insert_own on acknowledgements
|
||||||
|
for insert with check (
|
||||||
|
user_id = auth.uid()
|
||||||
|
and org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||||
|
);
|
||||||
|
|
||||||
|
create policy ack_read on acknowledgements
|
||||||
|
for select using (
|
||||||
|
org_id = (auth.jwt() ->> 'org_id')::uuid
|
||||||
|
and ( user_id = auth.uid()
|
||||||
|
or (auth.jwt() ->> 'role') in ('admin','approver','editor') )
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Workflow mutations (publish, approve, assign) run via the **service-role client inside /api routes** so drafts can be snapshotted and audit rows written in one transaction; RLS remains the safety net for direct reads.
|
||||||
|
|
||||||
|
## 4. Seed (`seed.sql`)
|
||||||
|
One org ("<Your Company>"), 3 departments (Inbound, Outbound, Admin), the founder as admin, 2 test staff, 2 sample SOPs (one draft, one published v1.0 with BM translation), a few acknowledgements — enough for the dashboard to show real numbers on day one.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# API Specification
|
||||||
|
## SOP Governance Tool — Stage 1 (Next.js Route Handlers)
|
||||||
|
|
||||||
|
Conventions: JSON in/out · session cookie auth (Supabase) verified server-side on every route · errors as `{ "error": { "code": "FORBIDDEN", "message": "…" } }` with proper HTTP status · every mutation writes `audit_log` in the same transaction.
|
||||||
|
|
||||||
|
Plain reads (lists, dashboard) may query Supabase directly from server components under RLS; the routes below are the **mutations and AI calls** — the state machine lives here and only here (`lib/workflow.ts`).
|
||||||
|
|
||||||
|
## 1. SOPs
|
||||||
|
|
||||||
|
### POST /api/sops — create draft (editor+)
|
||||||
|
Req: `{ "code":"WH-PICK-001", "title":"Order Picking", "department_id":"…", "category":"Warehouse", "review_months":12 }`
|
||||||
|
Res 201: `{ "id":"…", "status":"draft" }`
|
||||||
|
409 if code exists.
|
||||||
|
|
||||||
|
### PATCH /api/sops/:id — update draft content/meta (editor+; only when status = draft)
|
||||||
|
Req: `{ "draft_content": { …sections… }, "title?":"…" }` → Res 200 `{ "ok":true, "saved_at":"…" }`
|
||||||
|
409 `NOT_EDITABLE` if not draft.
|
||||||
|
|
||||||
|
### POST /api/sops/:id/submit — draft → submitted (editor+)
|
||||||
|
Validates mandatory sections (purpose, ≥1 step). Res 200 `{ "status":"submitted" }`
|
||||||
|
422 `VALIDATION` listing missing sections.
|
||||||
|
|
||||||
|
### POST /api/sops/:id/decide — approve/reject (approver/admin)
|
||||||
|
Req: `{ "decision":"approved" | "rejected", "comment":"…" }` (comment required on reject)
|
||||||
|
approved → status `approved`; rejected → status `draft`. Writes `approvals` row.
|
||||||
|
Res 200 `{ "status":"approved" }`.
|
||||||
|
|
||||||
|
### POST /api/sops/:id/publish — approved → published (admin)
|
||||||
|
Req: `{ "change_note":"Initial release", "is_major":false }`
|
||||||
|
Logic (one transaction): compute next label via `nextVersion(current, is_major)` (none→1.0, 1.0+minor→1.1, +major→2.0) → insert `sop_versions` snapshot of `draft_content` → set `sops.current_version_id`, `status='published'`, `published_at` → mark existing translations of prior version stale (no-op flag; new version simply has none) → audit.
|
||||||
|
Res 200 `{ "version_label":"1.0", "version_id":"…" }`.
|
||||||
|
|
||||||
|
### POST /api/sops/:id/assign — set departments (admin)
|
||||||
|
Req: `{ "department_ids": ["…","…"] }` (replaces the set)
|
||||||
|
Res 200 `{ "assigned_departments": 2 }`.
|
||||||
|
|
||||||
|
## 2. Acknowledgement
|
||||||
|
|
||||||
|
### POST /api/ack (staff, self only)
|
||||||
|
Req: `{ "sop_version_id":"…", "language_viewed":"ms", "typed_name":"Ahmad bin Ali" }`
|
||||||
|
Checks: user's department is assigned to the SOP; version is the current one; typed_name non-empty.
|
||||||
|
Res 201 `{ "acknowledged_at":"…" }` · 409 `ALREADY_ACKNOWLEDGED` · 403 if not an assignee.
|
||||||
|
|
||||||
|
### GET /api/sops/:id/ack-report (approver/admin)
|
||||||
|
Res 200:
|
||||||
|
```json
|
||||||
|
{ "sop": { "code":"WH-PICK-001", "title":"…", "version":"1.2" },
|
||||||
|
"summary": { "assignees": 24, "acknowledged": 21, "pct": 87.5 },
|
||||||
|
"rows": [ { "name":"Ahmad", "department":"Outbound",
|
||||||
|
"status":"acknowledged", "version":"1.2",
|
||||||
|
"language":"ms", "at":"2026-07-01T02:11:00Z" },
|
||||||
|
{ "name":"Ravi", "department":"Outbound", "status":"outstanding" } ] }
|
||||||
|
```
|
||||||
|
`?format=csv` streams CSV of the same.
|
||||||
|
|
||||||
|
## 3. AI
|
||||||
|
|
||||||
|
### POST /api/ai/draft (editor+, rate-limited 20/day)
|
||||||
|
Req: `{ "description":"How we pick e-commerce orders…", "notes?":"pasted messy notes" }`
|
||||||
|
Server: prompt template → Anthropic → parse strict JSON (retry once) → log `ai_log`.
|
||||||
|
Res 200: `{ "draft": { "purpose":"…", "scope":"…", "roles":"…", "steps":[{"order":1,"text":"…"}], "exceptions":"…", "safety":"…", "records":"…" } }`
|
||||||
|
Client fills the editor; **user must Save** — the route never writes to `sops`.
|
||||||
|
429 `RATE_LIMIT` · 502 `AI_FAILED` (input preserved client-side).
|
||||||
|
|
||||||
|
### POST /api/ai/translate (editor+)
|
||||||
|
Req: `{ "sop_version_id":"…", "languages":["ms","zh"] }`
|
||||||
|
Res 200: `{ "created":[ {"language":"ms","translation_id":"…"} ] }` (upserts `sop_translations`, `machine=true`).
|
||||||
|
|
||||||
|
### POST /api/translations/:id/review (editor+) — human sign-off
|
||||||
|
Res 200 `{ "reviewed": true }`. Staff see safety sections in a translation only when reviewed (FRD-6.2).
|
||||||
|
|
||||||
|
## 4. Users & Departments (admin)
|
||||||
|
|
||||||
|
- `POST /api/users` `{ email, full_name, role, department_id, preferred_language }` → creates auth user (invite email) + profile.
|
||||||
|
- `PATCH /api/users/:id` `{ role?, department_id?, active?, preferred_language? }`
|
||||||
|
- `POST /api/departments` `{ name }` · `PATCH /api/departments/:id` `{ name }`
|
||||||
|
- `DELETE /api/departments/:id` → 409 `HAS_USERS` if occupied.
|
||||||
|
|
||||||
|
## 5. Incidents
|
||||||
|
|
||||||
|
### POST /api/incidents (any authenticated role)
|
||||||
|
Req: `{ "description":"Pallet label mismatch on inbound", "severity":"high", "sop_id?":"…", "sop_version_id?":"…", "photo_path?":"sop-photos/…" }`
|
||||||
|
Server sets reporter_id + department from the session profile. Res 201 `{ "id":"…", "status":"open" }` · 422 if description empty.
|
||||||
|
|
||||||
|
### PATCH /api/incidents/:id (approver/admin)
|
||||||
|
Req: `{ "status":"reviewed" | "closed", "resolution_note?":"Racking relabeled; SOP WH-INB-002 updated" }`
|
||||||
|
Transitions: open→reviewed→closed only; closed is immutable (409 `CLOSED`). Writes audit (`incident.reviewed` / `incident.closed`). Res 200 `{ "status":"closed" }`.
|
||||||
|
|
||||||
|
Reads (incident list, filters, per-SOP tab) go through RLS-scoped queries in server components — staff see only their own reports; approver/admin see all.
|
||||||
|
|
||||||
|
## 6. Status Codes Summary
|
||||||
|
200/201 success · 400 malformed · 401 no session · 403 wrong role / not assignee · 404 not found in org · 409 state conflict (duplicate code, not editable, already acknowledged) · 422 validation with field list · 429 AI rate limit · 500/502 server or AI failure.
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# UI / UX Specification
|
||||||
|
## SOP Governance Tool — Stage 1
|
||||||
|
|
||||||
|
## 1. Design Principles
|
||||||
|
|
||||||
|
1. **Two very different audiences:** Admin/Editor screens are information-dense desktop tools; Staff screens are huge-tap-target mobile pages usable by a warehouse worker in gloves, in a second language. Never compromise the Staff side for the Admin side.
|
||||||
|
2. **Boring and consistent beats clever.** shadcn/ui defaults, one accent color, no animation beyond built-ins.
|
||||||
|
3. **Status is always visible.** Every SOP shows a colored status badge everywhere it appears.
|
||||||
|
|
||||||
|
## 2. Design Tokens
|
||||||
|
|
||||||
|
- Accent/primary: teal `#028090` · success `#02C39A` · warning `#E0A100` · danger `#C0392B`
|
||||||
|
- Neutrals: slate scale; background `#F7FAF9`; card white
|
||||||
|
- Font: system UI stack (no webfont — fast on cheap phones)
|
||||||
|
- Status badges: draft = slate, submitted = amber, approved = blue, published = teal, archived = grey, review-overdue = red outline
|
||||||
|
- Staff screens: base font 18px, step text 20px, buttons ≥ 56px tall, single column
|
||||||
|
|
||||||
|
## 3. Navigation Map
|
||||||
|
|
||||||
|
```
|
||||||
|
Login
|
||||||
|
├─ ADMIN/EDITOR/APPROVER (desktop shell: sidebar + topbar)
|
||||||
|
│ ├─ Dashboard (admin, approver)
|
||||||
|
│ ├─ SOPs (list) ─ SOP detail ─ Edit draft
|
||||||
|
│ ├─ Approvals queue (approver, admin)
|
||||||
|
│ ├─ Incidents (approver, admin)
|
||||||
|
│ ├─ Users & Departments (admin)
|
||||||
|
│ └─ Audit log (admin, approver)
|
||||||
|
└─ STAFF (mobile shell: just a header + list)
|
||||||
|
├─ My SOPs (+ Report incident)
|
||||||
|
└─ SOP viewer (+ acknowledge, + Report a problem)
|
||||||
|
```
|
||||||
|
After login, route by role: staff → /my-sops, others → /dashboard.
|
||||||
|
|
||||||
|
## 4. Screen Specs
|
||||||
|
|
||||||
|
### 4.1 Login
|
||||||
|
Centered card: email, password, sign-in. Error inline. Company name/logo above.
|
||||||
|
|
||||||
|
### 4.2 Dashboard (admin/approver)
|
||||||
|
Top row of 4 stat cards: Published · Drafts · Pending approval · Review overdue (red if >0).
|
||||||
|
Then "Acknowledgement by SOP" table: code, title, dept, version, progress bar with %, outstanding count, row click → SOP detail. Department filter dropdown. Second small table: department overall %.
|
||||||
|
|
||||||
|
### 4.3 SOP List
|
||||||
|
Toolbar: search (code/title), filters (status, department, category), "New SOP" (editor+).
|
||||||
|
Table rows: code · title · dept · status badge · version · owner · updated. Row → detail.
|
||||||
|
|
||||||
|
### 4.4 SOP Detail
|
||||||
|
Header: code, title, status badge, version, owner, review date (red if overdue).
|
||||||
|
Action buttons by role/status: Edit draft · Submit · Approve/Reject (opens comment dialog) · Publish (dialog: change note + "major revision" checkbox) · Assign departments (multi-select dialog) · Translate (language checkboxes) · Copy staff link.
|
||||||
|
Tabs: **Content** (rendered read-only sections) · **Versions** (list; click renders frozen snapshot) · **Acknowledgements** (the FR-4 report + CSV button) · **Translations** (per language: machine/reviewed chip, preview, "Mark reviewed") · **Incidents** (linked incident list, FR-7.4) · **History** (audit entries for this SOP).
|
||||||
|
|
||||||
|
### 4.5 SOP Editor (draft)
|
||||||
|
Left: section list with completeness ticks. Main: one section at a time.
|
||||||
|
Procedure section: ordered step cards — textarea + photo upload + drag handle + delete; "Add step".
|
||||||
|
Top bar: autosave indicator ("Saved 10:32"), AI Draft button, Submit for approval.
|
||||||
|
**AI Draft modal:** big textarea "Describe the process in your own words", optional "Paste any existing notes", Generate → spinner → "Draft ready — review each section" fills the editor with unsaved changes highlighted. Nothing is saved until the user saves.
|
||||||
|
|
||||||
|
### 4.6 Approvals Queue
|
||||||
|
List of submitted SOPs: code, title, submitted by/when. Open → read-only content + Approve / Reject (comment required on reject).
|
||||||
|
|
||||||
|
### 4.7 My SOPs (STAFF, mobile)
|
||||||
|
Header: company name + user name + language switcher (EN | BM | 中文).
|
||||||
|
Section "Needs your acknowledgement" (amber cards) then "Acknowledged" (teal tick cards). Card: title, code, version, dept. Tap → viewer.
|
||||||
|
|
||||||
|
### 4.8 SOP Viewer (STAFF, mobile)
|
||||||
|
Title + version + language switcher. Sections in reading order; steps as numbered cards with photo above text. Sticky bottom bar:
|
||||||
|
- Not yet acknowledged → button "I have read and agree" (label in current language) → confirm sheet: "Type your full name to sign" + input + Confirm → success screen with tick, timestamp, version.
|
||||||
|
- Acknowledged → grey bar "Acknowledged v1.2 · 01 Jul 2026".
|
||||||
|
If viewing a translation where safety section is unreviewed → safety section shows English with a small note "English shown pending translation review".
|
||||||
|
|
||||||
|
### 4.9 Incidents (approver/admin)
|
||||||
|
List with filters (status, severity, department, SOP): date · reporter · dept · SOP code · severity chip (grey/amber/red) · status chip. Row → detail: full description, photo, linked SOP version, timeline. Actions: "Mark reviewed" · "Close" (dialog: optional resolution note + checkbox "This requires an SOP revision" → on save jumps to that SOP's editor). Dashboard gains an "Open incidents" card (red when any open high-severity).
|
||||||
|
|
||||||
|
**Staff report flow (mobile):** "Report a problem" link at the bottom of the SOP viewer (pre-links SOP + version) and a button on My SOPs (no SOP link). One screen: description textarea, severity selector (three big buttons), optional photo, Submit → success tick. Labels tri-lingual via lib/i18n.ts.
|
||||||
|
|
||||||
|
### 4.10 Users & Departments (admin)
|
||||||
|
Two tabs. Users table: name, email, role (inline select), department, language, active toggle, "Invite user" dialog. Departments: simple list + add/rename.
|
||||||
|
|
||||||
|
### 4.11 Audit Log (admin/approver)
|
||||||
|
Filter by SOP / action / date. Table: time · actor · action · entity · detail summary. Export CSV.
|
||||||
|
|
||||||
|
## 5. Empty / Error / Loading States
|
||||||
|
Every list has an empty state with the primary action ("No SOPs yet — create your first one" / "Nothing to approve 🎉"). Mutations show button spinners; failures show a toast with the server message and never lose form input. AI failures keep the description text in the modal with a Retry button.
|
||||||
|
|
||||||
|
## 6. Accessibility & Language Notes
|
||||||
|
Staff-facing static labels (buttons, headers on staff screens) are tri-lingual via a tiny dictionary file (`lib/i18n.ts`) keyed by preferred_language — do NOT pull in a heavy i18n framework. Admin screens stay English. Contrast AA minimum; all tap targets ≥ 44px; photos get alt text from step text.
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Development Plan
|
||||||
|
## SOP Governance Tool — Stage 1
|
||||||
|
|
||||||
|
Assumption: solo beginner + Claude Code, 10–15 focused hrs/week ⇒ ~15–17 weeks. Each milestone ends with a **demo you can show on your own phone/laptop** and a short acceptance checklist. Do not start a milestone until the previous checklist passes.
|
||||||
|
|
||||||
|
Golden rules:
|
||||||
|
1. **One milestone at a time.** Resist building ahead.
|
||||||
|
2. **Never paste code you can't explain.** Ask Claude Code to explain until you can.
|
||||||
|
3. **Commit after every working step** with a plain-English message.
|
||||||
|
4. **Deploy to Vercel from Week 1** — always have a live URL.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### M0 · Setup (Week 1)
|
||||||
|
Scaffold Next.js + TypeScript + Tailwind + shadcn/ui; create Supabase project (Singapore); run `db/schema.sql`; connect; deploy "hello world" to Vercel; put this `/docs` pack + `CLAUDE.md` in the repo.
|
||||||
|
✅ Live URL loads · schema visible in Supabase table editor · repo pushed.
|
||||||
|
|
||||||
|
### M1 · Auth & Users (Weeks 2–3) — FR-1.x
|
||||||
|
Login/logout; profiles; role-based redirect; Users & Departments admin pages; JWT claims hook; role guard middleware.
|
||||||
|
✅ Staff login lands on /my-sops and CANNOT open /users (test it) · admin can invite a user and change a role · deactivated user blocked.
|
||||||
|
|
||||||
|
### M2 · SOP Repository & Editor (Weeks 4–6) — FR-2.x
|
||||||
|
SOP list with filters/search; create SOP; structured editor with steps + photo upload; autosave.
|
||||||
|
✅ Create WH-PICK-001, add 5 steps with 2 photos, refresh mid-edit and lose ≤10s · duplicate code rejected politely.
|
||||||
|
|
||||||
|
### M3 · Governance (Weeks 7–8) — FR-3.x
|
||||||
|
`lib/workflow.ts` state machine; submit / decide / publish routes; version snapshotting; approvals queue; version history tab; audit log writes + audit page.
|
||||||
|
✅ Full loop: draft → submit → approve → publish v1.0 → edit → publish v1.1 → open v1.0 frozen content · reject requires a comment · every action appears in audit log · illegal transitions blocked at API (curl test).
|
||||||
|
|
||||||
|
### M4 · Distribution & Acknowledgement (Weeks 9–10) — FR-4.x
|
||||||
|
Assign departments; staff My SOPs + mobile viewer; acknowledge flow with typed-name signature; share link; re-ack on new version.
|
||||||
|
✅ On YOUR OWN PHONE: staff account sees assigned SOP in BM… acknowledges… it moves to "Acknowledged" · same user cannot ack twice · publishing v1.2 moves it back to "Needs acknowledgement".
|
||||||
|
|
||||||
|
### M5 · Dashboard & Reports (Week 11) — FR-5.x
|
||||||
|
Stat cards; per-SOP % table; department table; overdue-review flag; CSV export.
|
||||||
|
✅ Numbers match a hand-count of seeded data · CSV opens in Excel with outstanding names listed.
|
||||||
|
|
||||||
|
### M6 · AI (Weeks 12–13) — FR-6.x
|
||||||
|
`/api/ai/draft` + editor modal; `/api/ai/translate` + translations tab + reviewed flag; ai_log; rate limit.
|
||||||
|
✅ Describe a process in 3 sentences → usable structured draft in the editor · translate v1.x to BM + ZH, mark BM reviewed, staff viewer shows BM (safety in English until reviewed) · 21st call today politely refused · Anthropic key absent from client bundle.
|
||||||
|
|
||||||
|
### M6.5 · Incident Log (Week 14) — FR-7.x
|
||||||
|
Incidents table + RLS; staff report flow (viewer link + My SOPs button, photo, severity); admin incident list + review/close with resolution note; SOP detail Incidents tab; dashboard open-incidents card.
|
||||||
|
✅ Staff reports a problem with photo from the viewer in <60s · approver closes it with a note and jumps to the SOP editor · closed incident is read-only · all transitions in audit log.
|
||||||
|
|
||||||
|
### M7 · Hardening & Go-Live (Weeks 15–16)
|
||||||
|
Security checklist from 03-architecture §6 (all boxes); Supabase backups on + one restore test; seed real company data; onboard real staff via WhatsApp-shared link; fix top frictions for one week.
|
||||||
|
✅ 20+ real SOPs published · ≥80% acknowledgement · founder does ZERO manual screenshot/Sheet work for a full week.
|
||||||
|
|
||||||
|
### Scope-cut order (if behind schedule)
|
||||||
|
Cut from the end of the week's scope, never extend weeks: 1) ZH translation (keep BM) · 2) CSV exports (screen only) · 3) incident photo + per-SOP tab (keep report + list) · 4) audit log UI (data still recorded) · 5) photo upload in steps (text only). NEVER cut: RLS, the state machine, acknowledgement immutability, the basic incident report+list.
|
||||||
|
|
||||||
|
### Weekly rhythm (fits around warehouse + trading)
|
||||||
|
- 2 × 2-hr build sessions with Claude Code (follow CLAUDE.md session plan)
|
||||||
|
- 1 × 1-hr review: read the week's code, run the checklist, commit, deploy
|
||||||
|
- 10 min: write next session's goal at the top of CLAUDE.md scratch section
|
||||||
Reference in New Issue
Block a user