C-005: Order Acknowledgement Detail + PDF - Order detail page with full line items, releases, addresses, paint codes - PDF export via Puppeteer matching legacy format - Clickable order # links and PDF icons in orders table C-007: BOL Detail + PDF - BOL detail page with ship-from/to, line items, weights - PDF export matching legacy BOL format - PDF icon column in shipments table C-008: Coil Activity - Usage Report - Date range picker (max 31 days, default last 10 days) - Reusable UI: Popover, Calendar (react-day-picker v9), DateRangePicker - SQL from legacy portal_CoilActivityUsage.sql with OnHandQty dedup - 11-column sortable table with search and CSV export C-009: Coil Activity - Receipts Report - VGL customer exception (special SQL vs portal view) - 10-column sortable table with search and CSV export - Shared date range picker component Also: dashboard API route, shipments API route, HDC→HDM mapping, Puppeteer PDF infrastructure, improved error handling. Progress: 9/16 Phase 2 tasks complete. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
427 lines
20 KiB
Markdown
427 lines
20 KiB
Markdown
# Vorteq Quest Portal - Claude Code Instructions
|
|
|
|
## Project Overview
|
|
|
|
Rebuilding the Vorteq Quest customer portal from a legacy Laravel 9 / Windows Server / Savvior stack to a modern Next.js / TypeScript / PostgreSQL stack running in Docker on Linux. The full PRD is in `docs/Vorteq_Quest_PRD_v1.0.docx`.
|
|
|
|
## Tech Stack
|
|
|
|
- **Runtime:** Node.js 22 LTS
|
|
- **Framework:** Next.js 15 with App Router
|
|
- **Language:** TypeScript (strict mode)
|
|
- **ORM (App DB):** Prisma with PostgreSQL 16
|
|
- **ORM (Epicor):** `mssql` package (read-only connection to SQL Server)
|
|
- **Auth:** Better Auth with credentials provider + custom session enrichment
|
|
- **UI:** shadcn/ui + Tailwind CSS
|
|
- **Data Tables:** TanStack Table v8
|
|
- **State:** React Server Components by default, client components only when needed
|
|
- **Cache/Queue:** Redis 7 via BullMQ
|
|
- **Email:** Microsoft Graph API (Office 365)
|
|
- **PDF:** Puppeteer or @react-pdf/renderer
|
|
- **Testing:** Vitest (unit/integration), Playwright (E2E)
|
|
- **Containers:** Docker + Docker Compose
|
|
- **Reverse Proxy:** Traefik v3 with Cloudflare DNS challenge for SSL
|
|
- **CI/CD:** Forgejo Actions with self-hosted runner
|
|
- **Registry:** Forgejo built-in OCI container registry
|
|
|
|
## Reference Documentation
|
|
|
|
All legacy system documentation is in the `docs/` directory:
|
|
|
|
- **`vorteqcoil-portal-documentation.md`** — Complete interface documentation of the legacy portal: navigation, sections, column layouts, business logic. Reference when building UI pages.
|
|
- **`vorteqcoil-visual-documentation.html`** — Visual reference guide with color coding system and UI patterns. Reference for design decisions.
|
|
- **`VorteqDiscovery_20260206_205453.txt`** — Server infrastructure discovery: IIS config, scheduled tasks, services.
|
|
- **`VorteqAppDiscovery_20260206_210519.txt`** — Application code discovery: Laravel routes, controllers, models, views, Blade templates. Reference when implementing features.
|
|
- **`VorteqEpicorDB_20260210_211850.txt`** — Epicor database schema: tables, stored procedures, views. Reference when writing Epicor queries.
|
|
- **`EpicorDatabase.php`** — Legacy PHP database class with all Epicor query implementations. Primary reference for porting queries to TypeScript.
|
|
- **`_FILE_LISTING.txt`** — Complete file listing of the legacy Laravel application.
|
|
- **`_MODEL_MAP.txt`** — Legacy Eloquent model-to-table mappings. Reference for Prisma schema design.
|
|
- **`_SQL_QUERIES_FOUND.txt`** — All SQL queries extracted from the legacy codebase. Reference for service layer implementation.
|
|
|
|
## Infrastructure
|
|
|
|
### Server
|
|
- **Host:** expvtcasp01 (Linux/Ubuntu)
|
|
- **Netbird IP:** 100.89.62.20
|
|
- **Stack directory:** `/opt/stacks/vorteq`
|
|
- **Runner directory:** `/opt/stacks/forgejo-runner`
|
|
|
|
### Repository
|
|
- **URL:** `forgejo.wulfconsulting.cloud/lorentz/quest-vorteq`
|
|
- **SSH:** `ssh://git@forgejo.wulfconsulting.cloud:222/lorentz/quest-vorteq.git` (port 222)
|
|
- **Registry:** `forgejo.wulfconsulting.cloud/lorentz/quest-vorteq`
|
|
|
|
### Domains
|
|
- **Production:** `quest.vorteq.wulf.cloud`
|
|
- **Development:** `dev.quest.vorteq.wulf.cloud`
|
|
- **Testing:** `testing.vorteq.wulf.cloud`
|
|
- **Traefik Dashboard:** `traefik.vorteq.wulf.cloud`
|
|
|
|
### Running Services
|
|
- **Traefik:** Reverse proxy, auto-SSL via Cloudflare DNS challenge
|
|
- **PostgreSQL 16:** Three databases — `vorteq_dev`, `vorteq_test`, `vorteq_prod`
|
|
- **Redis 7:** Three DB numbers — 0 (dev), 1 (test), 2 (prod)
|
|
- **Forgejo Runner:** Docker-in-Docker, registered as `expvtcasp01`
|
|
|
|
### CI/CD Pipeline
|
|
Push to `main` → build Docker image → tag as `:latest` → push to Forgejo registry → deploy to prod
|
|
Push to `development` → build Docker image → tag as `:dev` → push to Forgejo registry → deploy to dev
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
quest-vorteq/
|
|
├── CLAUDE.md # This file
|
|
├── TASKS.md # Task tracking
|
|
├── docker-compose.yml # Infrastructure: traefik, postgres, redis, app containers
|
|
├── Dockerfile # Multi-stage Next.js build (node:22-alpine, standalone output)
|
|
├── .forgejo/
|
|
│ └── workflows/
|
|
│ └── deploy.yml # CI/CD pipeline
|
|
├── traefik/
|
|
│ ├── traefik.yml # Traefik configuration
|
|
│ ├── acme.json # SSL certificates (gitignored)
|
|
│ └── config/
|
|
│ └── dev-local.yml # Routes dev domain → host:3001 for local dev server
|
|
├── prisma/
|
|
│ ├── schema.prisma # PostgreSQL schema (auth + quest + ship/alloc/finance/paint/doc/wave/lts domains)
|
|
│ ├── migrations/
|
|
│ └── seed.ts # Seed data / migration scripts
|
|
├── src/
|
|
│ ├── app/ # Next.js App Router
|
|
│ │ ├── (auth)/ # Auth route group
|
|
│ │ │ ├── login/
|
|
│ │ │ ├── forgot-password/
|
|
│ │ │ └── account-request/
|
|
│ │ ├── (portal)/ # Authenticated route group
|
|
│ │ │ ├── dashboard/
|
|
│ │ │ ├── inventory/ # Summary + 6 category pages + detail drill-down
|
|
│ │ │ ├── orders/
|
|
│ │ │ └── select-company/
|
|
│ │ ├── api/
|
|
│ │ │ ├── auth/ # Better Auth catch-all + custom login + set-active-company
|
|
│ │ │ ├── health/
|
|
│ │ │ └── account-request/
|
|
│ │ ├── layout.tsx
|
|
│ │ ├── globals.css
|
|
│ │ └── page.tsx
|
|
│ ├── components/
|
|
│ │ ├── ui/ # shadcn/ui primitives (button, card, input, table, toast, etc.)
|
|
│ │ ├── dashboard/ # summary-card, quick-nav-card, recent-orders-table, recent-shipments-table
|
|
│ │ ├── inventory/ # inventory-summary-table, inventory-detail-table
|
|
│ │ ├── orders/ # orders-table
|
|
│ │ ├── layout/ # portal-sidebar, portal-header, breadcrumb
|
|
│ │ └── company-selector.tsx
|
|
│ ├── hooks/
|
|
│ │ └── use-toast.ts
|
|
│ ├── lib/
|
|
│ │ ├── db.ts # Prisma client singleton
|
|
│ │ ├── epicor.ts # MSSQL connection pool + query helpers + typed errors
|
|
│ │ ├── auth.ts # Better Auth config (Prisma adapter, credentials provider)
|
|
│ │ ├── auth-client.ts # Better Auth client-side helpers
|
|
│ │ ├── session.ts # Quest-specific session data (cookie-based)
|
|
│ │ ├── permissions.ts # RBAC: getQuestSession, requirePermission, hasPermission, isAdmin, isSubUser, getActiveCompany, getUserCompanies, PermissionRules constants
|
|
│ │ └── utils.ts # General utilities (cn, etc.)
|
|
│ ├── services/
|
|
│ │ ├── inventory.ts # Epicor SP calls for 6 inventory categories (summary + detail)
|
|
│ │ ├── orders.ts # portal_Orders view queries, HDC/HDM exception handling
|
|
│ │ └── dashboard.ts # Dashboard data aggregation
|
|
│ ├── types/
|
|
│ │ ├── epicor.ts # Epicor query result types
|
|
│ │ ├── portal.ts # Portal-specific types
|
|
│ │ └── index.ts
|
|
│ └── middleware.ts # Rate limiting, public route allowlist, auth check (currently disabled in dev)
|
|
├── tests/
|
|
│ ├── unit/
|
|
│ ├── integration/
|
|
│ └── e2e/
|
|
├── scripts/
|
|
│ └── migrate-data.ts # SQL Server → PostgreSQL migration
|
|
└── docs/ # Legacy system documentation (see Reference Documentation above)
|
|
```
|
|
|
|
### Not Yet Created (Planned)
|
|
```
|
|
src/app/(portal)/coil-activity/
|
|
src/app/(portal)/invoices/
|
|
src/app/(portal)/shipment-requests/
|
|
src/app/(portal)/allocation-requests/
|
|
src/app/(portal)/jobs/
|
|
src/app/(portal)/admin/
|
|
src/services/coil-activity.ts # Coil activity data access
|
|
src/services/ship-requests.ts # Shipment request business logic
|
|
src/services/alloc-requests.ts # Allocation request business logic
|
|
src/services/invoices.ts # Invoice processing
|
|
src/lib/email.ts # Microsoft Graph email service
|
|
src/lib/pdf.ts # PDF generation service
|
|
src/jobs/ # BullMQ workers (shipment-nag, order-ack-email, etc.)
|
|
src/components/data-table/ # Reusable TanStack Table wrapper
|
|
src/components/forms/ # Shared form components
|
|
```
|
|
|
|
## Current Development State
|
|
|
|
**Phase 1 (Foundation):** Complete — project scaffold, Docker infra, Prisma schema (all domains), Epicor connection, Better Auth, middleware, permissions, company selector.
|
|
|
|
**Phase 2 (Core Features):** In progress
|
|
- Dashboard: implemented (summary cards, quick nav, recent orders/shipments tables)
|
|
- Inventory: implemented (all 6 category summaries + detail drill-down)
|
|
- Orders: implemented (list page with search and CSV export, HDC exception)
|
|
- Remaining: shipments, coil-activity, invoices, shipment-requests, allocation-requests, jobs, admin pages
|
|
|
|
**Dev workflow:** Local Next.js dev server with hot reload via `PORT=3001 npm run dev`. Traefik routes `dev.quest.vorteq.wulf.cloud` to host:3001 via `traefik/config/dev-local.yml`. The `app-dev` Docker container should be stopped during local dev to avoid route conflicts.
|
|
|
|
**Auth status:** Better Auth is configured with Prisma adapter. A custom login endpoint exists at `/api/auth/login`. Auth middleware is temporarily disabled in dev mode (`DEV_MODE=true` bypasses to a mock admin session). The dev domain (`dev.quest.vorteq.wulf.cloud`) also bypasses auth in middleware.
|
|
|
|
**Git:** Currently only `main` branch. No `development` branch yet.
|
|
|
|
## Coding Conventions
|
|
|
|
### General
|
|
- TypeScript strict mode, no `any` types
|
|
- Use `type` over `interface` unless extending
|
|
- Prefer `const` over `let`, never `var`
|
|
- Use named exports, not default exports (except page.tsx and layout.tsx)
|
|
- Error handling: use typed error classes, not string throws
|
|
- All API responses: `{ data: T }` on success, `{ error: string, code: string }` on failure
|
|
|
|
### Naming
|
|
- Files: kebab-case (`shipment-requests.ts`)
|
|
- Components: PascalCase (`ShipmentRequestForm.tsx`)
|
|
- Functions/variables: camelCase
|
|
- Database columns (Prisma): snake_case
|
|
- API routes: kebab-case (`/api/shipment-requests`)
|
|
- Environment variables: SCREAMING_SNAKE_CASE
|
|
|
|
### Formatting (Prettier)
|
|
- Single quotes, semicolons, trailing commas (es5)
|
|
- 2-space indentation, 80 char print width
|
|
- Tailwind class sorting via `prettier-plugin-tailwindcss`
|
|
|
|
### Database
|
|
- Prisma schema uses snake_case for all fields
|
|
- Map legacy PascalCase column names with `@map("LegacyName")` where needed
|
|
- Always use parameterized queries for Epicor MSSQL — never string interpolation
|
|
- Epicor queries go in `src/services/` with typed return values
|
|
- PostgreSQL queries go through Prisma client exclusively
|
|
|
|
### Component Pattern
|
|
```typescript
|
|
// Server component by default
|
|
export async function InventorySummary({ category }: { category: string }) {
|
|
const data = await getInventorySummary(category);
|
|
return <DataTable columns={columns} data={data} />;
|
|
}
|
|
|
|
// Client component only when interactivity needed
|
|
'use client';
|
|
export function ShipmentRequestCart() { ... }
|
|
```
|
|
|
|
## Epicor Connection Patterns
|
|
|
|
### Connection (`src/lib/epicor.ts`)
|
|
- Singleton connection pool via `mssql` package
|
|
- Typed error classes: `EpicorConnectionError`, `EpicorQueryError`, `EpicorTimeoutError`
|
|
- `execStoredProc<T>(name, params)` — execute stored procedure, returns `T`
|
|
- `execQuery<T>(query, params)` — execute parameterized SQL, returns `T`
|
|
- `execPortalStoredProc<T>(name, custId, dbName, sub, additionalParams)` — convenience wrapper with standard portal params
|
|
- `getPortalDbName()` — returns `[VorteqPortal]` (bracketed DB name for cross-DB SP parameter)
|
|
- `checkConnection()` — health check
|
|
- Graceful shutdown on SIGINT/SIGTERM
|
|
|
|
### Inventory Stored Procedure Parameters
|
|
|
|
**V6 procedures** (WIP, Finished Goods, Processed Other):
|
|
- `@Customer` — the selected company's EpicorCustID
|
|
- `@DBNAME` — portal database name in brackets, e.g., `[VorteqPortal]`
|
|
- `@SUBUSER` — 0 or 1 (sub-user flag)
|
|
- Detail variants also accept `@PART`, `@PLANT`, `@WAREHOUSE` for filtering
|
|
|
|
**Non-V6 procedures** (Unprocessed, R&R variants):
|
|
- `@CUSTID` — the selected company's EpicorCustID
|
|
- `@DBNAME` — portal database name in brackets
|
|
- No sub-user parameter — sub-users are blocked entirely (return empty array)
|
|
- Detail variants accept `@part`, `@plant`, `@warehouse` (lowercase)
|
|
|
|
### Orders Pattern
|
|
- Uses `execQuery` with parameterized SQL against Epicor views
|
|
- Standard view: `dbo.portal_Orders WHERE CustomerID = @CustID`
|
|
- HDC exception: uses `dbo.portal_OrdersHDC` view and maps CustID `HDC` → `HDM`
|
|
|
|
### RSC Serialization — Critical Pattern for Epicor Data
|
|
|
|
**Problem:** Next.js RSC (React Server Components) dev mode serializes ALL server-side data and console output to forward to the browser. The `mssql` package returns `recordset` objects with circular references, metadata, and prototype chains that blow up RSC serialization (`RangeError: Maximum call stack size exceeded` at `Set.add` or `Map.set`). This is especially severe for large result sets (e.g., ACM has 7,633 shipment rows).
|
|
|
|
**Solution:** For Epicor data displayed in the UI, use a **client-side fetch → API route** pattern instead of RSC server components:
|
|
|
|
1. Create an API route (e.g., `/api/shipments/route.ts`) that calls the service and returns `NextResponse.json(data)`
|
|
2. Make the page a `'use client'` component that fetches from the API route via `useEffect` + `fetch()`
|
|
3. The API route handles auth/session checks and returns plain JSON — no RSC serialization involved
|
|
|
|
**When RSC works fine:** Small result sets (< ~500 rows) through `execStoredProc` generally work. The `[...result.recordset]` spread in `execStoredProc` detaches the array-level prototype. For safety, services should still `JSON.parse(JSON.stringify(...))` their return values if passing to RSC.
|
|
|
|
**When RSC breaks:** Large result sets (1000+ rows), or any scenario where mssql objects remain in scope during server component rendering. Even with `JSON.parse(JSON.stringify())`, Next.js dev mode console forwarding can serialize the function's closure scope including mssql objects.
|
|
|
|
**Rule of thumb:** If an Epicor query can return > 500 rows for any customer, use the API route pattern.
|
|
|
|
### Column Mapping
|
|
Epicor SP results use PascalCase column names. Services map them to snake_case:
|
|
```typescript
|
|
function mapSummaryRow(raw: Record<string, unknown>): InventorySummaryRow {
|
|
return {
|
|
part_num: String(raw.VorteqPartNum ?? ''),
|
|
description: String(raw.VorteqPartDesc ?? ''),
|
|
on_hand_qty: Number(raw.OnHandQty ?? 0),
|
|
// ...
|
|
};
|
|
}
|
|
```
|
|
|
|
## Auth & Session Pattern
|
|
|
|
### Better Auth (`src/lib/auth.ts`)
|
|
- Prisma adapter connecting to `auth_user`, `auth_session`, `auth_account` tables
|
|
- Email + password enabled, email verification disabled
|
|
- 7-day session expiry
|
|
|
|
### Quest Session (`src/lib/permissions.ts`)
|
|
`getQuestSession()` enriches the Better Auth session with:
|
|
- `questUserId` — from `quest_user` table
|
|
- `activeCompanyId` — from cookie-based session store (`src/lib/session.ts`)
|
|
- `isSubUser` — from `quest_user.is_sub_user`
|
|
- `permissionRules[]` — collected from user type → permission groups → permission rules
|
|
- `userType` — e.g., "Admin", "Super Admin", "Customer User"
|
|
|
|
In dev mode (`DEV_MODE=true`), returns a mock admin session with all permissions.
|
|
|
|
### Permission Helpers (`src/lib/permissions.ts`)
|
|
- `requirePermission(rule)` — throws if user lacks permission
|
|
- `hasPermission(rule)` — returns boolean
|
|
- `isAdmin()` / `requireAdmin()` — check for Admin or Super Admin user type
|
|
- `isSubUser()` — check sub-user status
|
|
- `requireActiveCompany()` — throws if no company selected
|
|
- `getActiveCompany()` — returns the active `quest_company` record
|
|
- `getUserCompanies()` — returns accessible companies (admins see all active)
|
|
- `PermissionRules` — constant object with all permission rule names
|
|
|
|
## Important Business Logic
|
|
|
|
### Company Context
|
|
After login, the user's active company is stored in a cookie (`quest_session`). All Epicor queries are scoped to that company's `EpicorCustID`. Admin users can switch companies and see all active companies.
|
|
|
|
### Sub-User Restrictions
|
|
Users with `is_sub_user=true` on their `quest_user` record:
|
|
- See filtered inventory data (SUBUSER=1 parameter to V6 stored procedures)
|
|
- See filtered ship-to addresses (only those matching '%NB HANDY%')
|
|
- Cannot access Unprocessed inventory or R&R data at all
|
|
|
|
### HDC/HDM Customer Exception
|
|
Customer 'HDC' uses a special order view (`portal_OrdersHDC`) and maps to CustID 'HDM' in queries. Handled in `src/services/orders.ts`.
|
|
|
|
### VGL Customer Exception
|
|
Customer 'VGL' uses a special SQL query for coil receipts instead of the standard view.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# Development
|
|
npm run dev # Start Next.js dev server
|
|
npm run build # Production build
|
|
npm run start # Start production server
|
|
npm run lint # ESLint
|
|
npm run typecheck # TypeScript type checking (tsc --noEmit)
|
|
npm test # Vitest
|
|
npm run test:e2e # Playwright E2E tests
|
|
|
|
# Database
|
|
npx prisma migrate dev # Create + apply migration (dev)
|
|
npx prisma migrate deploy # Apply migrations (prod)
|
|
npx prisma db push # Push schema without migration
|
|
npx prisma generate # Regenerate Prisma client
|
|
npx prisma studio # Database GUI
|
|
tsx prisma/seed.ts # Run seed script
|
|
|
|
# Docker
|
|
docker compose up -d # Start all services
|
|
docker compose ps # Check service status
|
|
docker compose logs -f vorteq-dev # Tail dev container logs
|
|
|
|
# Pre-commit checks
|
|
npm run lint && npm run typecheck && npm test
|
|
```
|
|
|
|
### Local Development (Hot Reload)
|
|
```bash
|
|
# Start local dev (instead of Docker container)
|
|
docker compose stop app-dev # Release the competing Traefik route
|
|
PORT=3001 npm run dev # Run in a tmux pane — hot reload at https://dev.quest.vorteq.wulf.cloud
|
|
|
|
# Stop local dev (restore containerized version)
|
|
# Ctrl+C the dev server, then:
|
|
docker compose start app-dev
|
|
```
|
|
Traefik routes `dev.quest.vorteq.wulf.cloud` → `http://172.17.0.1:3001` via `traefik/config/dev-local.yml`. CI/CD pipeline is unchanged — pushes to `main` still build and deploy the production container.
|
|
|
|
## Environment Variables
|
|
|
|
```env
|
|
# Application
|
|
NODE_ENV=development|test|production
|
|
DEV_MODE=true # Bypasses auth, uses mock admin session
|
|
BETTER_AUTH_URL=https://dev.quest.vorteq.wulf.cloud
|
|
BETTER_AUTH_SECRET=<generated>
|
|
|
|
# PostgreSQL (per-environment, set in docker-compose.yml)
|
|
DATABASE_URL=postgresql://postgres:<password>@postgres:5432/vorteq_dev
|
|
|
|
# Redis (per-environment, set in docker-compose.yml)
|
|
REDIS_URL=redis://redis:6379/0
|
|
|
|
# Epicor SQL Server (read-only)
|
|
MSSQL_HOST=wi-e10test
|
|
MSSQL_DATABASE=Epicor10Live
|
|
MSSQL_USER=portal
|
|
MSSQL_PASSWORD=<from-env>
|
|
MSSQL_PORT=1433
|
|
|
|
# Portal database name (for cross-DB stored procedure parameter)
|
|
PORTAL_DB_NAME=VorteqPortal
|
|
|
|
# Microsoft Graph (Email)
|
|
MS_GRAPH_CLIENT_ID=629eca61-d554-4763-b14a-565aee7b7ab8
|
|
MS_GRAPH_TENANT_ID=291e541e-003c-463c-8834-863b9e7c1ebe
|
|
MS_GRAPH_CLIENT_SECRET=<from-env>
|
|
MAIL_FROM=quest@vorteqcoil.com
|
|
|
|
# reCAPTCHA
|
|
RECAPTCHA_SITE_KEY=<from-env>
|
|
RECAPTCHA_SECRET_KEY=<from-env>
|
|
|
|
# Cloudflare (Traefik SSL - set in .env at stack level)
|
|
CLOUDFLARE_DNS_API_TOKEN=<from-cloudflare>
|
|
```
|
|
|
|
## Testing Requirements
|
|
|
|
- Unit tests for all service functions (inventory, orders, shipments data transformations)
|
|
- Integration tests for API routes with mocked DB connections
|
|
- E2E tests for: login flow, inventory browse + drill-down, shipment request workflow, invoice upload
|
|
- Data validation: compare Epicor query results with known-good outputs from the legacy system
|
|
|
|
## Git Workflow
|
|
|
|
- `main` branch: production-ready code → deploys to `quest.vorteq.wulf.cloud`
|
|
- `development` branch: integration branch → deploys to `dev.quest.vorteq.wulf.cloud` (not yet created)
|
|
- Feature branches: `feature/F-001-project-scaffold`
|
|
- Commit messages: `feat(F-001): scaffold Next.js project with TypeScript + Tailwind`
|
|
- Run `npm run lint && npm run typecheck && npm test` before committing
|
|
- Push triggers Forgejo Actions → Docker build → registry push → deploy
|
|
|
|
## Task Tracking
|
|
|
|
All tasks are tracked in `TASKS.md`. Mark tasks as:
|
|
- `[ ]` Not started
|
|
- `[~]` In progress
|
|
- `[x]` Complete
|
|
- `[!]` Blocked (note blocker in comments)
|