Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "anchor-dev",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"port": 3007,
"autoPort": true
}
]
}
10 changes: 10 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"mcp__Claude_Preview__preview_screenshot",
"mcp__Claude_Preview__preview_console_logs",
"Bash(npm run lint)",
"Bash(npm run typecheck)"
]
}
}
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Supabase — copy to .env.local and fill in from your project's
# Settings → API page. Leave blank to run the app fully local (no accounts/sync).
NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version-file: .node-version

- name: Install dependencies
run: npm ci

- name: Typecheck
run: npm run typecheck

- name: Lint
run: npm run lint

- name: Test
run: npm run test

- name: Build
run: npm run build
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,15 @@ pnpm-debug.log*
# typescript
*.tsbuildinfo
next-env.d.ts

# capacitor / native builds
/android
/electron/app
/electron/build
/electron/node_modules
/electron/dist
/electron/electron-plugins.js
.gstack/

# local-only Claude settings
.claude/settings.local.json
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24.15.0
36 changes: 36 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# CLAUDE.md

Anchor — a daily-ritual app (web + Capacitor mobile + Electron desktop) from one
Next.js 16 / React 19 / TS codebase. Portfolio piece, built as a *real* app.

**Read `ROADMAP.md` first** — it holds the current state, architecture, and the
parallelizable workstreams. This file is the short version for every session.

## Always run the gate before calling work "done"
```bash
npm run typecheck && npm run lint && npm run test && npm run build
```
Verify user-facing changes in the browser (`npm run dev`, or `npm start` for prod).

## Non-negotiable conventions
- **Layers**: domain logic lives in `lib/` (time / domain / store / notifications /
supabase / auth), never in components. See ROADMAP "Architecture".
- **zod schemas are the source of truth**; types are `z.infer`. Validate at the
storage boundary only.
- **Mutate state only via `lib/store/actions`** — never `setState` in a component.
- **Day keys are local** via `lib/time/today` — never `toISOString()` (UTC bug).
- **Limits/magic numbers** live in `lib/domain/validation` (`LIMITS`).
- **Radix Tailwind variants**: use `data-[orientation=…]` / `data-[state=…]`.
The legacy `data-horizontal` / `data-active` forms silently do nothing.
- **Graceful degradation**: no env (e.g. Supabase) must never crash the app.
- **No slop**: handle empty/loading/error states; no cosmetic-only features;
always read & correct AI-generated code (it fails on older patterns).

## Parallel work
Use a separate git worktree/branch per workstream. Anything touching `lib/store`
runs solo (no concurrent store edits). See ROADMAP "Workstreams".

## Commands
`dev` · `build` · `start` · `test` / `test:watch` · `typecheck` · `lint` ·
`build:native` (static export for Capacitor) · `cap:sync` · `desktop:dev` ·
`mobile:add:ios|android`.
85 changes: 75 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,86 @@
# Next.js template
# Anchor

This is a Next.js template with shadcn/ui.
A quiet daily ritual app — morning and evening check-ins, mood tracking, sleep,
journaling, intention. One unified flow across **web, mobile, and desktop**.

## Adding components
Live: deployed on Vercel · Mobile: iOS/Android via Capacitor · Desktop: macOS/Windows via Electron

To add components to your app, run the following command:
---

## Stack

- **Next.js 16** (App Router, Turbopack) + **React 19** + **TypeScript**
- **Tailwind CSS v4** + **Radix UI** primitives + **shadcn/ui** components
- **Framer Motion** for transitions
- **Capacitor 8** — iOS & Android native shells
- **@capacitor-community/electron** — macOS / Windows / Linux desktop shell
- Pure client-side state (localStorage-backed), so the same static bundle ships everywhere

## Routes

- `/` — marketing landing
- `/app` — main dashboard (greeting, streak, today's rituals)
- `/morning` — morning ritual flow (mood, sleep, intention, meditation, affirmation)
- `/evening` — evening ritual flow (mood, journal, habits, tomorrow's sleep window)
- `/timeline` — trends over time
- `/settings` — preferences

## Develop

```bash
npm install
npm run dev # http://localhost:3000
```

## Build for Vercel

```bash
npm run build # standard Next.js build
```

## Build the native static bundle (mobile + desktop share this)

```bash
npm run build:native # next build with BUILD_TARGET=native -> ./out
npm run cap:sync # build + sync into Capacitor platforms
```

## Desktop (Electron)

```bash
npx shadcn@latest add button
npm run desktop:dev # build, sync, and launch the desktop app
npm run desktop:pack # produce an unpacked desktop binary
npm run desktop:make # produce a distributable installer
```

This will place the ui components in the `components` directory.
## Mobile (Capacitor)

The mobile platforms are added on-demand because they require Xcode / Android Studio.

```bash
npm run mobile:add:ios # one-time: scaffold the ios/ folder
npm run mobile:open:ios # open in Xcode

npm run mobile:add:android # one-time: scaffold the android/ folder
npm run mobile:open:android # open in Android Studio
```

## Using components
After native code changes, re-sync: `npm run cap:sync`.

To use the components in your app, import them as follows:
## Project shape

```tsx
import { Button } from "@/components/ui/button";
```text
app/ # Next.js App Router routes
page.tsx # landing
app/page.tsx # dashboard
morning/, evening/, settings/, timeline/
components/
ui/ # shadcn primitives
morning/, evening/ # ritual step components
anchor-motif.tsx # brand SVG
lib/store.ts # client state
electron/ # generated by `cap add electron` — desktop shell
ios/, android/ # generated by `cap add ios|android` — mobile shells
capacitor.config.ts # shared Capacitor config
next.config.mjs # conditional static export when BUILD_TARGET=native
```
149 changes: 149 additions & 0 deletions ROADMAP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Anchor — Roadmap & Handoff

Single source of truth for what this app is, what's done, and what's next.
Written so a fresh session (or a parallel agent) can pick up cold.

**Anchor** is a daily-ritual app (morning/evening check-ins: mood, sleep,
intention, journal, meditation, habits) built as a portfolio piece that is
also a *real* app. Web (Vercel) + mobile (Capacitor) + desktop (Electron) from
one Next.js codebase.

---

## How to get oriented (do this first)

1. Read this file + `CLAUDE.md`.
2. Run the gate to confirm a green baseline:
```bash
npm install
npm run typecheck && npm run lint && npm run test && npm run build
```
3. Preview locally: `npm run dev` → http://localhost:3000
(prod: `npm start`; native static export: `npm run build:native`).

## Architecture (the conventions that must hold)

Layered, typed, testable. Do not put domain logic in components.

```text
lib/
time/ local-date keys (NOT UTC), time-of-day context. Pure + tested.
domain/ zod schemas + inferred types (entry, habit), selectors
(isMorningComplete, computeStreak…), validation. Pure + tested.
store/ state.ts (AppState schema + migrate), persistence.ts (StoragePort
— localStorage now, Capacitor seam later), store.ts (reactive,
useSyncExternalStore), actions.ts (the only mutators), index.ts.
notifications/ port.ts (web adapter + Capacitor seam), schedule.ts (pure +
tested), index.ts (reactive permission).
supabase/ client.ts (null when unconfigured → app stays local-only).
auth/ credentials.ts (zod validators, tested).
components/ ui/ (shadcn primitives), feature components, providers.
app/ / (landing), /login, (protected)/ group = gated app routes.
```

Rules:
- **Schemas are the source of truth**; types are `z.infer`. Validate at the
storage boundary, trust types inside.
- **Components never call `setState` directly** — only `lib/store/actions`.
- **No magic numbers** — limits live in `lib/domain/validation` (`LIMITS`).
- **Day keys are local** (`lib/time/today`), never `toISOString()`.
- **Radix data-attrs**: target `data-[orientation=…]` / `data-[state=…]`, never
the legacy `data-horizontal`/`data-active` (that bug class bit us twice).
- **Graceful degradation**: missing env (Supabase) must never break the app.
- **No slop**: every screen handles empty/loading/error; no fake/cosmetic
features; review AI output.

Quality gate (must pass before "done"): `typecheck` clean, `lint` 0 errors,
`test` green, `build` green. Verify user-facing changes in the browser.

---

## Done (this far)

- Landing `/`; dashboard moved to `/app` (clickable ritual cards).
- Capacitor + Electron wired; `BUILD_TARGET=native` → static export (`out/`).
- Fixed systemic Radix `data-*` mismatch (slider, tabs, separator, dialog, sheet).
- **Architecture refactor**: `lib/time`, `lib/domain`, `lib/store` layers.
- **zod + vitest**; 39 tests (time, selectors, migrate, validation, schedule, credentials).
- **Resilience**: `app/error.tsx`, `app/global-error.tsx`, `app/not-found.tsx`.
- **Input validation** wired (intention/journal limits, habit dedupe/cap, real word count).
- **Reminders** (honest): `lib/notifications` + `ReminderScheduler` + settings UI.
Web fires while open; native (Capacitor) seam documented.
- **Supabase Auth (A)**: client, `AuthProvider`/`useAuth`, `/login` (email+password,
validated), `(protected)` route-group gate, sign-out in settings, `.env.example`.

## Blocked / needs the human

- **Supabase keys** → put `NEXT_PUBLIC_SUPABASE_URL` + `NEXT_PUBLIC_SUPABASE_ANON_KEY`
in `.env.local`. Without them the app runs local-only (by design).
- **Decision**: Sentry DSN — wire now (no-op without DSN) or wait?

---

## Workstreams (parallelizable)

Each is self-contained. **To avoid collisions, run each in its own git
worktree/branch.** "Touches" lists the files; streams that touch `lib/store`
must not run concurrently with each other.

### WS-1 · Supabase cloud sync (B) — depends on keys
- Goal: entries sync across devices; localStorage stays offline cache.
- SQL (run in Supabase SQL editor) is in the bottom of this file.
- Touches: `lib/supabase/sync.ts` (new), `components/sync-controller.tsx` (new,
mount in layout), `lib/store/store.ts` (hydrate-from-remote hook).
- Approach: pull on sign-in, merge (union entries; whole-state LWW by
`updated_at` for conflicts), debounced push on change. Document the LWW caveat.
- ⚠️ Touches the store → do NOT run in parallel with other store-touching work.
- Done when: sign in on two browsers, an entry made in one appears in the other.

### WS-2 · Sentry (monitoring) — isolated, parallel-safe
- `@sentry/nextjs`; init via env DSN, no-op without it. Capture in `app/error.tsx`
+ `app/global-error.tsx`. Touches: sentry config files, `next.config.mjs`,
the two error files (1 line each).
- Done when: build green with and without DSN; a thrown error reports when DSN set.

### WS-3 · PWA / installability — isolated, parallel-safe
- `app/manifest.ts`, icons in `public/`, theme-color (already in layout), optional
service worker for offline shell. Makes "install to home screen / desktop" real.
- Done when: Lighthouse PWA installable; icon + manifest valid.

### WS-4 · Timeline charts (Recharts) — isolated, parallel-safe
- The JD lists Recharts. Add a small mood/sleep trend chart to `components/timeline-view.tsx`
(only this file). Keep the warm palette + reduced-motion respect.
- Done when: chart renders from real entries; empty state intact.

### WS-5 · Accessibility pass — HIGH collision risk, run solo
- Keyboard support for the mood grid (`components/morning/step-mood.tsx`), focus
rings, aria labels, color-contrast check, `prefers-reduced-motion` for framer.
- Touches many components → schedule when other UI streams are merged.

### WS-6 · Native build + showcase assets — parallel-safe (no app-code edits)
- `npm run mobile:add:ios` / `:android` (needs Xcode/Android Studio), run in
simulator, capture screen recordings. `npm run desktop:dev` for Electron caps.
- Produces the screenshots/video for LinkedIn + the portfolio case study.

### WS-7 · LinkedIn showcase — no code
- See `CLAUDE.md` is not the place; guidance lives in the chat handoff / a future
`docs/showcase.md`. Assets come from WS-6.

---

## Supabase sync SQL (run once, for WS-1)

```sql
create table public.user_state (
user_id uuid primary key references auth.users(id) on delete cascade,
state jsonb not null,
updated_at timestamptz not null default now()
);
alter table public.user_state enable row level security;
create policy "own state - select" on public.user_state
for select using (auth.uid() = user_id);
create policy "own state - insert" on public.user_state
for insert with check (auth.uid() = user_id);
create policy "own state - update" on public.user_state
for update using (auth.uid() = user_id) with check (auth.uid() = user_id);
```

Auth → Providers → Email: enabled. For frictionless testing, temporarily turn
off "Confirm email".
Loading
Loading