Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail.
Requirements
Frontend
Checkbox with "I agree to the [Terms & Conditions]" label positioned above submit button
"Terms & Conditions" is a clickable link that opens PDF in new tab
Required field - blocks form submission if unchecked
Inline validation error when unchecked: "You must agree to the Terms & Conditions to create an account"
Must work on both mobile and desktop viewports
Checkbox must be keyboard navigable with proper ARIA labels
Backend/Database
Store comprehensive legal audit record with the following fields:
user_id: Reference to the created user
agreed_at: Timestamp in UTC
ip_address: Client IP address (check X-Forwarded-For for proxy scenarios)
user_agent: Browser/client user agent string
terms_version: Extract from filename (e.g., "v2026-01-03")
terms_url: Full URL path to the PDF (e.g., "/docs/v2026-01-03.pdf")
terms_content_hash: SHA-256 hash of the PDF file contents
Acceptance Criteria
Frontend:
Checkbox appears on signup form above submit button
Checkbox styling matches existing form elements
"Terms & Conditions" link opens PDF in new tab
Form submission blocked if checkbox unchecked
Inline error displays when validation fails
Works correctly on desktop viewports
Works correctly on mobile viewports
Keyboard navigation functions properly
Backend/Database:
Migration creates terms_agreements table with all required columns
Registration endpoint captures IP address from request headers
Registration endpoint captures User-Agent header
Timestamp stored in UTC
SHA-256 hash of PDF computed and stored
Terms version extracted from filename and stored
Terms URL stored as relative path
Agreement record created atomically with user creation
Files
PDF location: frontend/public/docs/v2026-01-03.pdf
Plan file: .claude/plans/cryptic-swimming-clock.md
Scope
In scope: Initial signup T&C agreement only
Out of scope: Re-consent flow for existing users when T&C changes (future enhancement)
## Description
Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail.
## Requirements
### Frontend
- Checkbox with "I agree to the [Terms & Conditions]" label positioned **above submit button**
- "Terms & Conditions" is a clickable link that opens PDF in new tab
- Required field - blocks form submission if unchecked
- Inline validation error when unchecked: "You must agree to the Terms & Conditions to create an account"
- Must work on both mobile and desktop viewports
- Checkbox must be keyboard navigable with proper ARIA labels
### Backend/Database
Store comprehensive legal audit record with the following fields:
- `user_id`: Reference to the created user
- `agreed_at`: Timestamp in **UTC**
- `ip_address`: Client IP address (check X-Forwarded-For for proxy scenarios)
- `user_agent`: Browser/client user agent string
- `terms_version`: Extract from filename (e.g., "v2026-01-03")
- `terms_url`: Full URL path to the PDF (e.g., "/docs/v2026-01-03.pdf")
- `terms_content_hash`: SHA-256 hash of the PDF file contents
## Acceptance Criteria
**Frontend:**
- [ ] Checkbox appears on signup form above submit button
- [ ] Checkbox styling matches existing form elements
- [ ] "Terms & Conditions" link opens PDF in new tab
- [ ] Form submission blocked if checkbox unchecked
- [ ] Inline error displays when validation fails
- [ ] Works correctly on desktop viewports
- [ ] Works correctly on mobile viewports
- [ ] Keyboard navigation functions properly
**Backend/Database:**
- [ ] Migration creates `terms_agreements` table with all required columns
- [ ] Registration endpoint captures IP address from request headers
- [ ] Registration endpoint captures User-Agent header
- [ ] Timestamp stored in UTC
- [ ] SHA-256 hash of PDF computed and stored
- [ ] Terms version extracted from filename and stored
- [ ] Terms URL stored as relative path
- [ ] Agreement record created atomically with user creation
## Files
- PDF location: `frontend/public/docs/v2026-01-03.pdf`
- Plan file: `.claude/plans/cryptic-swimming-clock.md`
## Scope
- **In scope**: Initial signup T&C agreement only
- **Out of scope**: Re-consent flow for existing users when T&C changes (future enhancement)
Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail. Creates a new terms-agreement feature capsule following the project's established architecture pattern. The checkbox blocks form submission until accepted, links to the existing PDF, and records all required audit fields (IP, user agent, timestamp, content hash) atomically with user creation.
Planning Context
Decision Log
Decision
Reasoning Chain
New terms-agreement feature capsule
doc-derived CLAUDE.md pattern -> feature capsules isolate concerns -> re-consent flows would require extraction from user-profile later -> separate capsule enables future extension without refactoring
Transaction for atomic creation
Legal requirement for audit integrity -> if profile creates but terms fails, user exists without consent record -> transaction ensures both succeed or both fail
PDF hash computed at startup
Hash is static (PDF doesn't change per request) -> computing per-request wastes CPU -> startup computation amortizes cost across all signups
IP from X-Forwarded-For first
Traefik reverse proxy sits in front of backend -> request.ip shows Traefik IP, not client -> X-Forwarded-For contains actual client IP
Zod literal(true) for checkbox
Must be checked to submit -> literal(true) fails validation if false/undefined -> provides clear error message on validation failure
Rejected Alternatives
Alternative
Why Rejected
Add to user-profile feature
Couples legal compliance with profile management; re-consent flows would require extraction; violates single-responsibility
Add to auth feature
Auth handles authentication (tokens, sessions), not legal compliance; would overload auth with unrelated concerns
MUI Checkbox component
SignupForm uses custom HTML inputs with Tailwind for consistent dark theme styling; MUI would break visual consistency
Hash PDF per request
PDF is static; per-request hashing wastes CPU cycles on identical computation
Constraints & Assumptions
PDF exists at frontend/public/docs/v2026-01-03.pdf (verified, 13573 bytes)
Mobile + desktop required (CLAUDE.md: 320px, 768px, 1920px viewports)
Create terms_agreements table with columns: id (UUID), user_id (VARCHAR, FK concept), agreed_at (TIMESTAMPTZ), ip_address (VARCHAR), user_agent (TEXT), terms_version (VARCHAR), terms_url (VARCHAR), terms_content_hash (VARCHAR)
Index on user_id for lookup
updated_at trigger
Acceptance Criteria:
Migration runs without error
Table exists with all columns
Index exists on user_id
Code Changes:
-- Terms Agreements Table
-- Stores legal audit trail for Terms & Conditions acceptance at signup
CREATETABLEIFNOTEXISTSterms_agreements(idUUIDPRIMARYKEYDEFAULTuuid_generate_v4(),user_idVARCHAR(255)NOTNULL,agreed_atTIMESTAMPWITHTIMEZONENOTNULLDEFAULTCURRENT_TIMESTAMP,ip_addressVARCHAR(45)NOTNULL,user_agentTEXTNOTNULL,terms_versionVARCHAR(50)NOTNULL,terms_urlVARCHAR(255)NOTNULL,terms_content_hashVARCHAR(64)NOTNULL,created_atTIMESTAMPWITHTIMEZONEDEFAULTCURRENT_TIMESTAMP,updated_atTIMESTAMPWITHTIMEZONEDEFAULTCURRENT_TIMESTAMP);-- Index for user lookup
CREATEINDEXIFNOTEXISTSidx_terms_agreements_user_idONterms_agreements(user_id);-- Trigger for updated_at
CREATEORREPLACEFUNCTIONupdate_terms_agreements_updated_at()RETURNSTRIGGERAS$$BEGINNEW.updated_at=CURRENT_TIMESTAMP;RETURNNEW;END;$$LANGUAGEplpgsql;CREATETRIGGERterms_agreements_updated_atBEFOREUPDATEONterms_agreementsFOREACHROWEXECUTEFUNCTIONupdate_terms_agreements_updated_at();
Sequential dependencies - each milestone depends on the previous.
Verdict: AWAITING_REVIEW | Next: Quality review of plan
## Plan: Terms & Conditions Checkbox
**Phase**: Planning | **Agent**: Planner | **Status**: AWAITING_REVIEW
## Overview
Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail. Creates a new `terms-agreement` feature capsule following the project's established architecture pattern. The checkbox blocks form submission until accepted, links to the existing PDF, and records all required audit fields (IP, user agent, timestamp, content hash) atomically with user creation.
## Planning Context
### Decision Log
| Decision | Reasoning Chain |
|----------|-----------------|
| New terms-agreement feature capsule | doc-derived CLAUDE.md pattern -> feature capsules isolate concerns -> re-consent flows would require extraction from user-profile later -> separate capsule enables future extension without refactoring |
| Transaction for atomic creation | Legal requirement for audit integrity -> if profile creates but terms fails, user exists without consent record -> transaction ensures both succeed or both fail |
| PDF hash computed at startup | Hash is static (PDF doesn't change per request) -> computing per-request wastes CPU -> startup computation amortizes cost across all signups |
| IP from X-Forwarded-For first | Traefik reverse proxy sits in front of backend -> request.ip shows Traefik IP, not client -> X-Forwarded-For contains actual client IP |
| Zod literal(true) for checkbox | Must be checked to submit -> literal(true) fails validation if false/undefined -> provides clear error message on validation failure |
### Rejected Alternatives
| Alternative | Why Rejected |
|-------------|--------------|
| Add to user-profile feature | Couples legal compliance with profile management; re-consent flows would require extraction; violates single-responsibility |
| Add to auth feature | Auth handles authentication (tokens, sessions), not legal compliance; would overload auth with unrelated concerns |
| MUI Checkbox component | SignupForm uses custom HTML inputs with Tailwind for consistent dark theme styling; MUI would break visual consistency |
| Hash PDF per request | PDF is static; per-request hashing wastes CPU cycles on identical computation |
### Constraints & Assumptions
- PDF exists at `frontend/public/docs/v2026-01-03.pdf` (verified, 13573 bytes)
- Mobile + desktop required (CLAUDE.md: 320px, 768px, 1920px viewports)
- Touch targets >= 44px (CLAUDE.md accessibility requirement)
- Database: snake_case, TypeScript: camelCase (CLAUDE.md naming conventions)
- Integration tests preferred (CLAUDE.md: CI/CD runs integration tests)
### Known Risks
| Risk | Mitigation | Anchor |
|------|------------|--------|
| X-Forwarded-For spoofing | Traefik is trusted proxy; header reflects actual client IP in production | Traefik config (trusted) |
| PDF hash mismatch after update | Version in filename (v2026-01-03) allows tracking; future PDF updates require new version | Manual process |
## Invisible Knowledge
### Architecture
```
+------------------+ +------------------+
| SignupForm | | SignupPage |
| (checkbox UI) | --> | (submit handler)|
+------------------+ +------------------+
|
v
+-----------------------+
| authApi.signup() |
| + termsAccepted field |
+-----------------------+
|
v
+------------------+ +------------------+
| AuthController | | AuthService |
| (extract IP, UA) | --> | signup() |
+------------------+ +------------------+
|
+-----------+-----------+
| |
v v
+------------------+ +---------------------+
| UserProfileRepo | | TermsAgreementRepo |
| create() | | create() |
+------------------+ +---------------------+
| |
v v
+------------------+ +---------------------+
| user_profiles | | terms_agreements |
+------------------+ +---------------------+
```
### Data Flow
1. User checks checkbox, clicks Create Account
2. Frontend sends `{email, password, termsAccepted: true}`
3. Controller extracts IP (`X-Forwarded-For || request.ip`), User-Agent
4. Service creates Auth0 user
5. In transaction: creates user_profile + terms_agreement
6. Returns success with userId
### Invariants
- Every user in `user_profiles` has exactly one record in `terms_agreements`
- `terms_agreements.agreed_at` is always UTC
- `terms_content_hash` matches SHA-256 of PDF at signup time
## Milestones
### Milestone 1: Database Layer
**Files**: `backend/src/features/terms-agreement/migrations/001_create_terms_agreements.sql`
**Requirements**:
- Create `terms_agreements` table with columns: id (UUID), user_id (VARCHAR, FK concept), agreed_at (TIMESTAMPTZ), ip_address (VARCHAR), user_agent (TEXT), terms_version (VARCHAR), terms_url (VARCHAR), terms_content_hash (VARCHAR)
- Index on user_id for lookup
- updated_at trigger
**Acceptance Criteria**:
- Migration runs without error
- Table exists with all columns
- Index exists on user_id
**Code Changes**:
```sql
-- Terms Agreements Table
-- Stores legal audit trail for Terms & Conditions acceptance at signup
CREATE TABLE IF NOT EXISTS terms_agreements (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id VARCHAR(255) NOT NULL,
agreed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
ip_address VARCHAR(45) NOT NULL,
user_agent TEXT NOT NULL,
terms_version VARCHAR(50) NOT NULL,
terms_url VARCHAR(255) NOT NULL,
terms_content_hash VARCHAR(64) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- Index for user lookup
CREATE INDEX IF NOT EXISTS idx_terms_agreements_user_id ON terms_agreements(user_id);
-- Trigger for updated_at
CREATE OR REPLACE FUNCTION update_terms_agreements_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER terms_agreements_updated_at
BEFORE UPDATE ON terms_agreements
FOR EACH ROW
EXECUTE FUNCTION update_terms_agreements_updated_at();
```
---
### Milestone 2: Backend Feature Capsule
**Files**:
- `backend/src/features/terms-agreement/data/terms-agreement.repository.ts`
- `backend/src/features/terms-agreement/domain/terms-agreement.types.ts`
- `backend/src/features/terms-agreement/index.ts`
- `backend/src/features/auth/domain/auth.service.ts` (modify)
- `backend/src/features/auth/api/auth.validation.ts` (modify)
- `backend/src/features/auth/api/auth.controller.ts` (modify)
- `backend/src/_system/migrations/run-all.ts` (modify)
**Requirements**:
- Create TermsAgreementRepository with create() method
- Implement mapRow() for snake_case to camelCase
- Add termsAccepted to signup validation schema
- Modify AuthService.signup() to accept terms data and create agreement in transaction
- Extract IP and User-Agent in controller
- Register terms-agreement in migration runner
**Acceptance Criteria**:
- Repository creates terms_agreement record
- Signup fails if termsAccepted is missing/false
- IP and User-Agent captured from request
- User profile and terms agreement created atomically
**Tests**:
- **Test files**: `backend/src/features/terms-agreement/tests/integration/terms-agreement.integration.test.ts`
- **Test type**: integration
- **Backing**: doc-derived (CLAUDE.md CI/CD)
- **Scenarios**:
- Normal: signup with termsAccepted=true creates both records
- Error: signup with termsAccepted=false returns 400
- Error: transaction rollback if terms_agreement insert fails
---
### Milestone 3: Frontend Integration
**Files**:
- `frontend/src/features/auth/components/SignupForm.tsx` (modify)
- `frontend/src/features/auth/types/auth.types.ts` (modify)
- `frontend/src/features/auth/api/auth.api.ts` (modify)
**Requirements**:
- Add termsAccepted boolean to SignupRequest type
- Add checkbox with link above submit button in SignupForm
- Add termsAccepted: z.literal(true) to Zod schema
- Display inline error when checkbox unchecked
- Link opens PDF in new tab
- 44px touch target for checkbox area
**Acceptance Criteria**:
- Checkbox visible above submit button
- "Terms & Conditions" text is clickable link opening PDF
- Form blocked if checkbox unchecked
- Error message displays: "You must agree to the Terms & Conditions to create an account"
- Works on mobile (320px) and desktop (1920px)
- Keyboard navigation functional
**Code Changes** (SignupForm.tsx, insert before submit button div):
```diff
--- a/frontend/src/features/auth/components/SignupForm.tsx
+++ b/frontend/src/features/auth/components/SignupForm.tsx
@@ -137,6 +137,28 @@ export const SignupForm: React.FC<SignupFormProps> = ({ onSubmit, loading }) =>
)}
</div>
+ <div className="flex items-start min-h-[44px]">
+ <label className="flex items-start cursor-pointer">
+ <input
+ {...register('termsAccepted')}
+ type="checkbox"
+ className="w-5 h-5 mt-0.5 rounded border-silverstone text-primary-600 focus:ring-abudhabi dark:border-silverstone dark:focus:ring-abudhabi"
+ aria-label="I agree to the Terms and Conditions"
+ />
+ <span className="ml-2 text-sm text-avus">
+ I agree to the{' '}
+ <a
+ href="/docs/v2026-01-03.pdf"
+ target="_blank"
+ rel="noopener noreferrer"
+ className="text-abudhabi hover:underline"
+ >
+ Terms & Conditions
+ </a>
+ </span>
+ </label>
+ </div>
+ {errors.termsAccepted && (
+ <p className="text-sm text-red-400">{errors.termsAccepted.message}</p>
+ )}
+
<div className="pt-4">
<Button type="submit" loading={loading} className="w-full min-h-[44px]">
```
---
### Milestone 4: Documentation
**Files**:
- `backend/src/features/terms-agreement/README.md`
- `backend/src/features/CLAUDE.md` (update index)
**Requirements**:
- Create README.md for terms-agreement feature
- Update features CLAUDE.md index with terms-agreement entry
**Acceptance Criteria**:
- README.md documents feature purpose, audit fields, and data flow
- CLAUDE.md index includes terms-agreement directory
## Milestone Dependencies
```
M1 (Database) --> M2 (Backend) --> M3 (Frontend) --> M4 (Docs)
```
Sequential dependencies - each milestone depends on the previous.
---
*Verdict*: AWAITING_REVIEW | *Next*: Quality review of plan
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Description
Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail.
Requirements
Frontend
Backend/Database
Store comprehensive legal audit record with the following fields:
user_id: Reference to the created useragreed_at: Timestamp in UTCip_address: Client IP address (check X-Forwarded-For for proxy scenarios)user_agent: Browser/client user agent stringterms_version: Extract from filename (e.g., "v2026-01-03")terms_url: Full URL path to the PDF (e.g., "/docs/v2026-01-03.pdf")terms_content_hash: SHA-256 hash of the PDF file contentsAcceptance Criteria
Frontend:
Backend/Database:
terms_agreementstable with all required columnsFiles
frontend/public/docs/v2026-01-03.pdf.claude/plans/cryptic-swimming-clock.mdScope
Plan: Terms & Conditions Checkbox
Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW
Overview
Add a required Terms & Conditions checkbox to the signup form with comprehensive legal audit trail. Creates a new
terms-agreementfeature capsule following the project's established architecture pattern. The checkbox blocks form submission until accepted, links to the existing PDF, and records all required audit fields (IP, user agent, timestamp, content hash) atomically with user creation.Planning Context
Decision Log
Rejected Alternatives
Constraints & Assumptions
frontend/public/docs/v2026-01-03.pdf(verified, 13573 bytes)Known Risks
Invisible Knowledge
Architecture
Data Flow
{email, password, termsAccepted: true}X-Forwarded-For || request.ip), User-AgentInvariants
user_profileshas exactly one record interms_agreementsterms_agreements.agreed_atis always UTCterms_content_hashmatches SHA-256 of PDF at signup timeMilestones
Milestone 1: Database Layer
Files:
backend/src/features/terms-agreement/migrations/001_create_terms_agreements.sqlRequirements:
terms_agreementstable with columns: id (UUID), user_id (VARCHAR, FK concept), agreed_at (TIMESTAMPTZ), ip_address (VARCHAR), user_agent (TEXT), terms_version (VARCHAR), terms_url (VARCHAR), terms_content_hash (VARCHAR)Acceptance Criteria:
Code Changes:
Milestone 2: Backend Feature Capsule
Files:
backend/src/features/terms-agreement/data/terms-agreement.repository.tsbackend/src/features/terms-agreement/domain/terms-agreement.types.tsbackend/src/features/terms-agreement/index.tsbackend/src/features/auth/domain/auth.service.ts(modify)backend/src/features/auth/api/auth.validation.ts(modify)backend/src/features/auth/api/auth.controller.ts(modify)backend/src/_system/migrations/run-all.ts(modify)Requirements:
Acceptance Criteria:
Tests:
backend/src/features/terms-agreement/tests/integration/terms-agreement.integration.test.tsMilestone 3: Frontend Integration
Files:
frontend/src/features/auth/components/SignupForm.tsx(modify)frontend/src/features/auth/types/auth.types.ts(modify)frontend/src/features/auth/api/auth.api.ts(modify)Requirements:
Acceptance Criteria:
Code Changes (SignupForm.tsx, insert before submit button div):
Milestone 4: Documentation
Files:
backend/src/features/terms-agreement/README.mdbackend/src/features/CLAUDE.md(update index)Requirements:
Acceptance Criteria:
Milestone Dependencies
Sequential dependencies - each milestone depends on the previous.
Verdict: AWAITING_REVIEW | Next: Quality review of plan