| name | apps-organiclever-www-developing-content |
| description | Comprehensive guide for developing organiclever-www, the OrganicLever life journal at www.organiclever.com. Covers DDD bounded-context architecture, PGlite local-first storage, Effect TS, XState, Next.js 16 App Router, and Vercel deployment. Essential for development tasks on organiclever-www. |
organiclever-www Development Skill
Purpose
This Skill provides guidance for developing and managing the organiclever-www Next.js 16 application — the OrganicLever life journal at www.organiclever.com. The app is a local-first productivity tracker with PGlite (Postgres-WASM) for in-browser data storage, structured around DDD bounded contexts.
When to use this Skill:
- Developing features for organiclever-www
- Understanding the bounded-context DDD architecture
- Working with PGlite storage or Effect TS service layer
- Configuring Vercel deployment
- Understanding organiclever-www specific conventions
Core Concepts
App Overview
organiclever-www (apps/organiclever-www/):
- Framework: Next.js 16 with App Router
- Architecture: DDD bounded contexts (
domain / application / infrastructure / presentation)
- Storage: PGlite (Postgres-WASM, IndexedDB-backed) — local-first, no backend required
- Effects: Effect TS for typed functional effects in infrastructure layer
- State machines: XState for UI FSMs (app-shell, workout-session)
- URL: https://www.organiclever.com/
- Role: Landing page + full life-journal app under
/app/
- Deployment: Vercel (
prod-organiclever-www branch)
Tech Stack Details
| Layer | Technology |
|---|
| Framework | Next.js 16 (App Router) |
| UI Runtime | React 19 |
| Styling | TailwindCSS + OL warm OKLCH design tokens |
| Components | @open-sharia-enterprise/web-ui |
| Storage | PGlite (Postgres-WASM, IndexedDB) |
| Effects | Effect TS (infrastructure layer only) |
| State | XState v5 (app-shell, workout-session) |
| BDD tests | @amiceli/vitest-cucumber + Vitest |
| Deployment | Vercel (auto-detected) |
Directory Structure
apps/organiclever-www/
├── src/
│ ├── app/ # Next.js App Router (thin wrappers only)
│ │ ├── app/ # /app/* routes (home, history, progress, settings, workout…)
│ │ └── system/status/be/ # Server-rendered diagnostic page
│ ├── contexts/ # Bounded-context implementations
│ │ ├── app-shell/ # Navigation chrome, i18n, entry-logging overlays
│ │ ├── health/ # Backend health diagnostic (dormant BE client)
│ │ ├── journal/ # Event log — system of record (PGlite)
│ │ ├── landing/ # Marketing landing page
│ │ ├── routine/ # Workout routine management (PGlite)
│ │ ├── routing/ # 404 guards (disabled routes)
│ │ ├── settings/ # User preferences — dark mode, language (PGlite)
│ │ ├── stats/ # History + progress projections (read-only from journal)
│ │ └── workout-session/ # Active workout session FSM (XState)
│ ├── shared/ # Cross-context utilities
│ │ ├── runtime/ # PgliteService Tag, AppRuntime, shared tagged errors
│ │ └── utils/ # format-relative-time, fmt
│ ├── generated-contracts/ # Auto-generated from OpenAPI spec (gitignored)
│ └── test/ # Test helpers and fixtures
├── test/unit/steps/ # Vitest-cucumber step implementations (per bounded context)
├── docs/explanation/ # Architecture docs (bounded-context map)
└── project.json # Nx project configuration
Bounded-Context Architecture
Every feature lives inside one bounded context under src/contexts/<bc>/:
src/contexts/<bc>/
├── domain/ # Pure types, invariants, tagged errors — no IO, no Effect
├── application/ # Use-cases, ports, XState orchestrating machines — depends on domain
├── infrastructure/ # PGlite stores, Effect Layers, live adapters — depends on domain + application + shared/runtime
└── presentation/ # React hooks + components — depends on domain + application
Layer rules (ESLint boundaries at error severity since Phase 8):
domain ← no project imports
application ← domain only
infrastructure ← domain + application + @/shared/runtime
presentation ← domain + application
- Cross-context coupling: only via the target's
application/index.ts or presentation/index.ts barrel
Published API barrels: each context exposes domain/index.ts, application/index.ts, infrastructure/index.ts, and presentation/index.ts. Consumers always import from the barrel, never from internal files.
Adding a feature (bounded-context-aware workflow)
- Identify which bounded context owns the feature. Consult
docs/explanation/bounded-context-map.md.
- Ensure the domain term appears in
specs/apps/organiclever/ddd/ubiquitous-language/<bc>.md. Add it if missing — same commit as the code change.
- Write or update the Gherkin spec in
specs/apps/organiclever/behavior/organiclever-www/gherkin/<bc>/.
- Implement: Red (failing step) → Green (minimal code) → Refactor.
- Keep all new code inside the correct context layer. If it touches IO, it goes in
infrastructure/. If it is a use-case, it goes in application/. Never break the layer rules.
- Run
nx run organiclever-www:lint to confirm 0 boundary errors before committing.
XState machine placement rule
- UI shell machine (no IO, no aggregate model — e.g.,
appMachine toggling dark mode) → presentation/
- Orchestrating machine (invokes
fromPromise actors hitting infrastructure — e.g., journalMachine, workoutSessionMachine) → application/
Design System
organiclever-www uses the OrganicLever warm OKLCH design system. All visual tokens come
from @open-sharia-enterprise/web-ui-token, and all UI components from
@open-sharia-enterprise/web-ui.
Token import chain
@import "tailwindcss";
@source "../../../../libs/web-ui/src/**/*.{ts,tsx}";
@import "@open-sharia-enterprise/web-ui-token/src/tokens.css";
@import "@open-sharia-enterprise/web-ui-token/src/organiclever.css";
@theme {
--font-sans: var(--font-nunito), ui-sans-serif, system-ui, sans-serif;
--font-mono: var(--font-jetbrains-mono), ui-monospace, monospace;
}
Fonts
Nunito (body) and JetBrains Mono (numerics) are self-hosted via next/font/google in
src/app/layout.tsx. They generate CSS variables --font-nunito and
--font-jetbrains-mono applied to <html> className, then consumed by @theme above.
Dark mode activation
document.documentElement.setAttribute("data-theme", "dark");
document.documentElement.removeAttribute("data-theme");
document.documentElement.classList.add("dark");
Both selectors activate the same warm-dark palette in organiclever.css.
Key tokens
var(--hue-teal)
var(--hue-sage)
var(--hue-teal-wash)
var(--hue-sage-wash)
var(--warm-0)
var(--warm-900)
var(--color-primary)
var(--color-ring)
web-ui component usage
Use components from @open-sharia-enterprise/web-ui — NOT from @/components/ui/. The
shared web-ui library owns the canonical component implementations.
import {
Button, Alert, Input, Icon, Toggle, ProgressRing,
Sheet, AppHeader, StatCard, InfoTip, HuePicker, TabBar, SideNav,
} from "@open-sharia-enterprise/web-ui";
<Button variant="teal">Primary action</Button>
<Button variant="sage" size="xl">Hero CTA</Button>
<Alert variant="success">Workout logged!</Alert>
<Alert variant="warning">Rest day recommended</Alert>
<Icon name="dumbbell" size={24} />
<Toggle value={isDark} onChange={setIsDark} label="Dark mode" />
<StatCard label="Streak" value={7} unit="days" hue="terracotta" icon="flame" />
<TabBar tabs={tabs} current={route} onChange={navigate} />
<SideNav brand={{ name: "OrganicLever", icon: "dumbbell", hue: "teal" }}
tabs={tabs} current={route} onChange={navigate} />
Dynamic hue backgrounds
When hue is a runtime variable, use inline style — Tailwind cannot detect template
literal class names at build time:
<div style={{ backgroundColor: `var(--hue-${hue})` }} />
<div className={`bg-[var(--hue-${hue})]`} />
Storybook
libs/web-ui/.storybook/preview.ts imports organiclever.css so all web-ui stories
render with the warm OL palette. Dark mode toggle uses .dark class (Storybook
addon-themes with withThemeByClassName({ dark: 'dark' })).
Component Architecture
Components live inside the bounded context that owns them, not in a global src/components/ folder.
Where components live
- Context-owned:
src/contexts/<bc>/presentation/components/ — components that belong to a specific bounded context
- Shared primitives:
@open-sharia-enterprise/web-ui — the shared design system library. Import from here, not from src/
- App routing chrome:
src/app/ — Next.js page.tsx and layout.tsx thin wrappers only; no business logic
import { JournalList } from "@/contexts/journal/presentation";
import { HistoryScreen } from "@/contexts/stats/presentation";
import { Button, StatCard, TabBar } from "@open-sharia-enterprise/web-ui";
import { SomeComponent } from "@/components/SomeComponent";
Server vs Client Components
Default: Server Components (no "use client" directive needed)
Use Client Components when:
- Interactive state (
useState, useReducer, XState useActor)
- Browser APIs (IndexedDB, window, localStorage)
- Event handlers (
onClick, onChange)
- React context consumers
The app layout mounts the PGlite runtime and XState appMachine in a client component (app-runtime-context.tsx). Per-tab page.tsx files are server components that render client presentation components.
Next.js App Router Conventions
Route Structure
src/app/
├── layout.tsx # Root layout — loads fonts, globals.css
├── page.tsx # Landing page (/) — server component
├── app/
│ ├── layout.tsx # App shell layout — mounts PGlite runtime + appMachine
│ ├── home/page.tsx # Home screen (/app/home)
│ ├── history/page.tsx # History screen (/app/history)
│ ├── progress/page.tsx # Progress screen (/app/progress)
│ ├── settings/page.tsx # Settings screen (/app/settings)
│ ├── workout/page.tsx # Active workout (/app/workout)
│ ├── workout/finish/page.tsx # Post-workout summary (/app/workout/finish)
│ └── routines/edit/page.tsx # Routine editor (/app/routines/edit)
└── system/status/be/page.tsx # Diagnostic page (force-dynamic, no cache)
Every page.tsx is a thin wrapper — it imports from the relevant bounded context's presentation/ barrel and renders the screen component. No business logic in page.tsx.
Vercel Deployment
Production Branch
Branch: prod-organiclever-www → https://www.organiclever.com/
Purpose: Deployment-only branch that Vercel monitors
Build System: Vercel (Next.js auto-detected, no builds array needed)
Security Headers: Configured in vercel.json
vercel.json Configuration
{
"version": 2,
"headers": [
{
"source": "/(.*)",
"headers": [
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" },
{
"key": "Referrer-Policy",
"value": "strict-origin-when-cross-origin"
}
]
}
]
}
Deployment Process
Step 1: Validate Current State
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
if [ "$CURRENT_BRANCH" != "main" ]; then
echo "❌ Must be on main branch"
exit 1
fi
if [ -n "$(git status --porcelain)" ]; then
echo "❌ Uncommitted changes detected"
exit 1
fi
Step 2: Force Push to Production
git push origin main:prod-organiclever-www --force
Step 3: Vercel Auto-Build
Vercel automatically:
Why Force Push
Safe for deployment branches:
- prod-organiclever-www is deployment-only (no direct commits)
- Always want exact copy of main branch
- Trunk-based development: main is source of truth
Comparison with Other Apps
| Aspect | organiclever-www | ayokoding-web | ose-web |
|---|
| Framework | Next.js 16 (App Router) | Next.js 16 (App Router) | Next.js 16 (App Router) |
| Architecture | DDD bounded contexts | Feature folders | Feature folders |
| Storage | PGlite (local-first, IndexedDB) | tRPC + database | tRPC + database |
| Auth | None (local-first) | None | None |
| State | XState + Effect TS | React state | React state |
| Build | Next.js (Vercel) | Next.js (Vercel) | Next.js (Vercel) |
| Prod Branch | prod-organiclever-www | prod-ayokoding-www | prod-ose-www |
| Languages | English | Bilingual (Indonesian/English) | English only |
| Complexity | Full DDD life journal + local storage | Fullstack bilingual platform | Simple landing page |
| Prod URL | www.organiclever.com | ayokoding.com | oseplatform.com |
| Primary Purpose | Local-first life journal + landing | Educational platform | Project landing page |
Development Commands
Option 1: Nx (host, recommended for frontend-only work)
nx dev organiclever-www
nx build organiclever-www
npx tsc --noEmit --project apps/organiclever-www/tsconfig.json
Option 2: Docker Compose (containerized, or running alongside the backend)
Runs the app inside a Node.js 24 Alpine container. Useful when you need the backend alongside the
frontend, or want an environment closer to CI.
npm run organiclever-www:dev
docker compose -f infra/dev/organiclever-www/docker-compose.yml up organiclever-www
First startup (~2-4 min): installs npm dependencies inside the container.
Subsequent starts: fast — node_modules is persisted in a named Docker volume.
node_modules is intentionally isolated from the host via a Docker named volume to prevent
Alpine Linux binary conflicts with macOS/Windows/Linux host binaries.
Common Patterns
Adding a feature to an existing bounded context
import { SomeScreen } from "@/contexts/<bc>/presentation";
Using web-ui components
import {
Button,
Alert,
Input,
Icon,
Toggle,
StatCard,
TabBar,
SideNav,
} from "@open-sharia-enterprise/web-ui";
<Button variant="teal">Primary action</Button>
<Button variant="sage" size="xl">Hero CTA</Button>
<Alert variant="success">Entry logged!</Alert>
<Icon name="dumbbell" size={24} />
<StatCard label="Streak" value={7} unit="days" hue="terracotta" icon="flame" />
Content Validation Checklist
Before committing changes:
Common Mistakes
❌ Mistake 1: Putting business logic in src/app/ page files
Wrong: Business logic in page.tsx
Right: Business logic in the bounded context's application/ or presentation/ layers; page.tsx only renders the screen component.
❌ Mistake 2: Importing from another context's internal files
Wrong: import { journalStore } from "@/contexts/journal/infrastructure/journal-store" from settings
Right: import { appendEntry } from "@/contexts/journal/application" — always go through the barrel
❌ Mistake 3: Forgetting "use client" for interactive components
export default function Counter() {
const [count, setCount] = useState(0);
}
("use client");
export default function Counter() {
const [count, setCount] = useState(0);
}
❌ Mistake 4: Direct commits to prod-organiclever-www
Wrong: git checkout prod-organiclever-www && git commit
Right: Commit to main, use apps-organiclever-app-web-deployer agent to force-push
Domain-Driven Design
apps/organiclever-www follows Domain-Driven Design. The bounded-context map is canonical at:
Bounded contexts
See the registry for the canonical list. Currently 9: journal, workout-session, routine, stats, settings, app-shell, health, landing, routing.
Layer rules (concise)
| Layer | Imports allowed | Imports forbidden |
|---|
domain | own domain/, shared/utils/** | anything in application/, infrastructure/, presentation/ |
application | own domain/, own infrastructure/ ports, other contexts' application/index.ts | other contexts' domain//infrastructure//presentation/ |
infrastructure | own domain/, own application/ ports | other contexts' anything; presentation/ |
presentation | own domain/, own application/, other contexts' presentation/index.ts | own infrastructure/; other contexts' domain//infrastructure/ |
Full layer rules: see DDD plan tech-docs.md § "Layer rules".
xstate machine placement
- Pure (no
fromPromise/IO actors) → domain/
- Orchestrating (invokes IO actors / cross-context calls) →
application/
- UI shell (no aggregate model, just view state) →
presentation/
Full xstate placement rules: see DDD plan tech-docs.md § "xstate machine placement".
Cross-context calls
- Only via target BC's
application/index.ts (or presentation/index.ts for hooks/components).
- NEVER hold a foreign machine's actor handle.
- NEVER import another context's
domain/, infrastructure/, or non-published files.
Glossary authoring rule
When you add a new domain term to code OR a Gherkin feature:
- Add a row to the right context's
specs/apps/organiclever/ddd/ubiquitous-language/<bc>.md Terms table.
- Set
Code identifier(s) to the actual symbol(s) in code (backtick-comma-list).
- Set
Used in features to the .feature filename(s) under that BC's Gherkin folder.
- If the term is also used by another context with a different meaning, add it to both glossaries' "Forbidden synonyms" sections cross-linking each other.
- The glossary update lands in the same commit as the code/feature change.
Pre-commit checklist
nx run organiclever-www:test:quick
If either reports a finding:
- Orphan code/glossary/gherkin — register it in
bounded-contexts.yaml or move it under an existing context.
- Stale code identifier — update the glossary
Code identifier(s) column to match the renamed symbol.
- Term collision — add cross-linked Forbidden synonyms to both glossaries.
- Layer subfolder mismatch — update the registry's
layers array OR remove the unused subfolder.
NEVER silence a finding by lowering severity in production. Use OSE_RHINO_DDD_SEVERITY=warn only for local exploratory work.
Reference Documentation
Project Configuration:
Infrastructure:
Related Skills:
repo-practicing-trunk-based-development - Git workflow and branch strategy
swe-programming-typescript - TypeScript coding standards
Related Agents:
apps-organiclever-app-web-deployer - Deploys organiclever-www to production
swe-typescript-dev - TypeScript/Next.js development
swe-e2e-dev - E2E testing with Playwright
This Skill packages essential organiclever-www development knowledge for building and deploying the OrganicLever landing and promotional website at www.organiclever.com.