From a19df4d10b3373d6b540cdad23f126097b57628a Mon Sep 17 00:00:00 2001 From: Elshad Toklayev Date: Sun, 31 May 2026 20:56:31 +0300 Subject: [PATCH] chore(repo): remove internal process docs and dead code from the tree These internal coordination artifacts do not belong in the project source, and one of them encoded commit-attribution and tooling-disclosure rules that should not govern the repository. - Remove HANDOFF.md, CHECKLIST.md, MERGE_PLAN.md (session/planning notes). - Remove scripts/commit-as.sh and scripts/.authors (the per-commit author rotation wrapper) and every reference to it. - Remove the empty CLAUDE.md. - Remove the dead integration/ tree: a one-time visual drop already merged into apps/web, sitting outside the pnpm workspace yet still linted by CI. - Rewrite CONTRIBUTING.md, the PR template, and CODEOWNERS to drop the commit-as.sh / HANDOFF / CHECKLIST workflow. - Fix ADR-0001's TanStack Query claim (not installed; the web app uses a fetch-based api-client) and drop commit-as.sh mentions from ADR-0001/0002 and the design explorations note. - Gitignore the two course handout PDFs so they are not accidentally committed. --- .github/CODEOWNERS | 2 - .github/pull_request_template.md | 6 +- .gitignore | 4 + CHECKLIST.md | 268 ------------------ CLAUDE.md | 0 CONTRIBUTING.md | 40 +-- HANDOFF.md | 218 -------------- MERGE_PLAN.md | 56 ---- docs/adr/0001-tech-stack-and-foundation.md | 50 ++-- .../adr/0002-design-system-and-token-layer.md | 2 +- docs/design/explorations.md | 2 +- integration/README.md | 172 ----------- .../apps/web/public/brand/statify-logo.svg | 20 -- .../apps/web/src/app/(auth)/layout.tsx | 51 ---- .../apps/web/src/app/(marketing)/page.tsx | 45 --- .../src/components/auth/AuthBackground.tsx | 175 ------------ .../auth/AuthBackgroundSpectrumMosaic.tsx | 187 ------------ .../src/components/auth/AuthBrandPanel.tsx | 62 ---- .../web/src/components/brand/StatifyLogo.tsx | 72 ----- .../web/src/components/marketing/About.tsx | 83 ------ .../apps/web/src/components/marketing/CTA.tsx | 50 ---- .../web/src/components/marketing/Demo.tsx | 184 ------------ .../web/src/components/marketing/Features.tsx | 211 -------------- .../web/src/components/marketing/Footer.tsx | 94 ------ .../web/src/components/marketing/Hero.tsx | 104 ------- .../components/marketing/HeroBackground.tsx | 173 ----------- .../src/components/marketing/HeroPreview.tsx | 116 -------- .../web/src/components/marketing/Stack.tsx | 143 ---------- .../src/components/marketing/StatsStrip.tsx | 50 ---- scripts/.authors | 4 - scripts/commit-as.sh | 44 --- 31 files changed, 36 insertions(+), 2652 deletions(-) delete mode 100644 CHECKLIST.md delete mode 100644 CLAUDE.md delete mode 100644 HANDOFF.md delete mode 100644 MERGE_PLAN.md delete mode 100644 integration/README.md delete mode 100644 integration/apps/web/public/brand/statify-logo.svg delete mode 100644 integration/apps/web/src/app/(auth)/layout.tsx delete mode 100644 integration/apps/web/src/app/(marketing)/page.tsx delete mode 100644 integration/apps/web/src/components/auth/AuthBackground.tsx delete mode 100644 integration/apps/web/src/components/auth/AuthBackgroundSpectrumMosaic.tsx delete mode 100644 integration/apps/web/src/components/auth/AuthBrandPanel.tsx delete mode 100644 integration/apps/web/src/components/brand/StatifyLogo.tsx delete mode 100644 integration/apps/web/src/components/marketing/About.tsx delete mode 100644 integration/apps/web/src/components/marketing/CTA.tsx delete mode 100644 integration/apps/web/src/components/marketing/Demo.tsx delete mode 100644 integration/apps/web/src/components/marketing/Features.tsx delete mode 100644 integration/apps/web/src/components/marketing/Footer.tsx delete mode 100644 integration/apps/web/src/components/marketing/Hero.tsx delete mode 100644 integration/apps/web/src/components/marketing/HeroBackground.tsx delete mode 100644 integration/apps/web/src/components/marketing/HeroPreview.tsx delete mode 100644 integration/apps/web/src/components/marketing/Stack.tsx delete mode 100644 integration/apps/web/src/components/marketing/StatsStrip.tsx delete mode 100644 scripts/.authors delete mode 100755 scripts/commit-as.sh diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9c93f0e..0e45935 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,5 +6,3 @@ /packages/db/prisma/schema.prisma @aykhan019 /apps/api/src/modules/auth/ @aykhan019 /apps/api/src/modules/analytics/ @aykhan019 -/HANDOFF.md @aykhan019 -/CHECKLIST.md @aykhan019 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 2014260..f84dc61 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -32,8 +32,6 @@ ## Checklist -- [ ] Updated `CHECKLIST.md` (ticked completed items) -- [ ] Updated `HANDOFF.md` Section 2 if work is mid-flight -- [ ] Commits authored via `scripts/commit-as.sh` -- [ ] No external tool/model/product names in any artifact +- [ ] Tests added or updated where appropriate +- [ ] Docs or ADRs updated if behavior or architecture changed - [ ] CI is green diff --git a/.gitignore b/.gitignore index 427eaf0..bbb8316 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,7 @@ data/raw/ # Sentry source map uploads .sentryclirc + +# Course handout PDFs (not part of the project source) +/Guidelines-for-Group-Project (1).pdf +/Sample-Project-Report.pdf diff --git a/CHECKLIST.md b/CHECKLIST.md deleted file mode 100644 index c341875..0000000 --- a/CHECKLIST.md +++ /dev/null @@ -1,268 +0,0 @@ -# Statify, Master Checklist - -> **Convention:** Aykhan drives every task. The "Commit author" column records which team member's identity the resulting commits are attributed to, not who does the work. Effort tags (S/M/L/XL) reflect approximate session time, not external workload distribution. - -## Current State (updated every session) - -- **Phase 4 status:** complete. Seed script and initial Prisma migration merged to `dev`. -- **Phase 5 status:** complete (M1-M8 all on `dev`). -- **Phase 6 status:** M1 ✓ (PR #28), M2 ✓ (PR #29), M3 ✓ (PR #30), M4 ✓ (`98ad518`), M5 ✓ (PR #32, `6ce01b3`), M6 ✓ (PR #33, `b9f3858`), M7 ✓ (PR #34, `871dd49`), M8 ✓ (PR #35, `ae9309f`), M9 ✓ (state vocabulary, PR #37), M10 ✓ (PR #38, `f16bb46`), M11 ✓ (PR #39, `ec35ed2`), M12 ✓ (PR #40, `ea76c55`), M13 ✓ (PR #41, `a8a49a3`). Phase 6 frontend redesign is complete. (Note: redesign is Phase 6; the existing "Deployment and submission" section stays unnumbered.) -- **Current milestone:** none; Phase 6 is complete. -- **Last shipped:** P6-M13 Accessibility pass. Adds root JSX accessibility lint coverage; fixes heading hierarchy, table semantics, search region wiring, media control labels, heatmap narration, focus rings, and contrast-safe section aliases; records `docs/design/a11y-audit.md` plus the DESIGN.md contrast table. Verification: `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm test`, `pnpm build`, CI, and local accessibility smoke for signup, login, global search, track preview, playlist create, admin users, landmarks, and keyboard focus all passed. -- **Last maintenance fix:** API CORS now permits the shared CSRF request header, unblocking local browser mutation smoke for playlist/admin flows. -- **Last local change:** Expired hard-coded Apple artwork sample URLs on the redesigned landing surfaces were replaced with local tokenized decorative cover tiles; `/styleguide` cover demos use local data images; `apps/web/next.config.mjs` now allows `i.scdn.co` for Spotify artwork. Verification passed: `pnpm lint`, `pnpm --filter @statify/web typecheck`, `pnpm --filter @statify/web test`, `pnpm --filter @statify/web build`, and local HTTP smoke for `/` plus `/styleguide` on port 3001 with no remaining `mzstatic` sample references. -- **Previous local UI change:** Public `/` and authed `/me` landings are redesigned, and manual light/dark mode is wired through root `data-theme`, `statify_theme` persistence, and shared theme toggles in public, auth, desktop app, and mobile app chrome. Verification passed: `pnpm format:check`, `pnpm lint`, `pnpm --filter @statify/web typecheck`, `pnpm --filter @statify/web test`, `pnpm --filter @statify/web build`, and local HTTP smoke for `/`, `/login`, dark-mode `/login`, and authenticated `/me` on port 3001. -- **Previous local change:** ADR-003 records Spotify as the album/artist artwork source and iTunes as the preview source; `packages/db/src/scripts/backfill-media.ts` now bulk-fills album and artist `image_url` values from Spotify with idempotent skipping, chunked Web API requests, and 429 backoff. A live probe reached Spotify successfully but Spotify returned `403 Active premium subscription required for the owner of the app`; run again after the Spotify app owner has Premium or swap in credentials from a Premium-owned app. Verification passed: `pnpm format:check`, `pnpm lint`, `pnpm --filter @statify/db test`, `pnpm --filter @statify/db typecheck`, `pnpm --filter @statify/api test`, and `pnpm --filter @statify/api typecheck`. -- **Last merge (non-milestone):** PR #36 `chore: add pnpm setup one-shot bootstrap` rebase-merged into `dev` 2026-05-25 (HEAD `3ad5c16`). Adds `pnpm setup` + `scripts/setup.sh`; not a milestone task, so no Phase 6 milestone status changed this session. -- **Open file/component:** none. -- **Locked decisions feeding Phase 6:** - - Design direction: Vivid Workshop (picked 2026-05-24). - - Entity media field shape: single nullable `image_url` on `tracks`, `albums`, `artists`. ADR-003 supersedes the original ADR-002 iTunes artwork-source assumption: Spotify populates album and artist artwork, iTunes only populates track preview metadata, and track `image_url` remains nullable with UI fallback to album artwork. - - Playlist media shape: list/detail DTOs expose `coverImages: string[]` derived from the first four member tracks' `track.imageUrl ?? album.imageUrl`; UI repeats fewer than four to fill the 2x2 collage and falls back to the playlist letter when none exist. Landed in P6-M7. - - Form primitives: built on RHF + native control elements (no @radix-ui/react-switch / -select), styled through CSS-variable + state-color tokens; spec lives in DESIGN.md §9. Landed in P6-M8. - - Motion library: `tailwindcss-animate`; `framer-motion` was not introduced during P6-M12. - - Webfonts: self-hosted via `next/font`; families locked in P6-M2 DESIGN.md. - - Existing UI during Phase 6: destructively replaced as each P6 milestone lands. -- **Blocker:** none. -- **Next concrete action:** resume the unnumbered Deployment and submission section; start with production env vars for the web/API hosts, then production smoke. - ---- - -## Phase 1, Discovery - -- [x] Stack chosen (NestJS, Next.js 15, Postgres on Neon, Argon2 + JWT) - S - aykhan -- [x] Constraints, rubric, dataset stance captured - S - aykhan - -## Phase 2, Architecture Decision Record - -- [x] ADR-001 drafted - L - aykhan -- [x] ADR-001 approved - S - aykhan -- [x] Open items resolved (dataset subset, repo visibility, no-AI-references, lazy iTunes-derived genres) - S - aykhan - -## Phase 3, Scaffolding - -- [x] Create directory tree at `/Users/aykhan/Documents/projects/statify/` - S - aykhan -- [x] Write `HANDOFF.md` - S - aykhan -- [x] Write `CHECKLIST.md` - S - aykhan -- [x] Write `docs/adr/0001-tech-stack-and-foundation.md` - L - aykhan -- [x] Write `README.md` - S - aykhan -- [x] Write `CONTRIBUTING.md` - S - aykhan -- [x] Write `LICENSE` (MIT) - S - aykhan -- [x] Write `.gitignore`, `.editorconfig`, `.nvmrc`, `.env.example` - S - aykhan -- [x] Write `scripts/commit-as.sh` and `scripts/.authors`, make executable - S - aykhan -- [x] `git init`, first commit (foundation docs) - S - aykhan -- [x] Initialize pnpm workspace: `pnpm-workspace.yaml`, root `package.json`, root `tsconfig.base.json` - M - elshad -- [x] Add Prettier config (`.prettierrc`, `.prettierignore`) - S - elshad -- [x] Add ESLint config (root `eslint.config.mjs`, shared rules) - M - elshad -- [x] Add commitlint config (`commitlint.config.cjs`) - S - elshad -- [x] Add Husky + lint-staged (`.husky/pre-commit`, `.husky/commit-msg`) - S - elshad -- [x] Scaffold `packages/shared`: error codes enum, AppError base, Pagination DTO - M - aykhan -- [x] Scaffold `packages/db`: Prisma init, seed stub, ingest CLI stub directory - L - eljan -- [x] Prisma schema commits (the 12 tables from ADR-001 Section 3.2) - L - aykhan -- [x] Scaffold `apps/api` (NestJS): ConfigModule, PrismaModule, LoggerModule (Pino), AllExceptionsFilter, ZodValidationPipe, /healthz endpoint - L - eljan -- [x] Scaffold `apps/web` (Next.js 15 App Router): Tailwind 4, base layout, theme tokens, /healthz route - L - rahila -- [x] Add `docker-compose.yml` for local Postgres + adminer - S - elshad -- [x] Add `.github/workflows/ci.yml` (typecheck, lint, build) - M - elshad -- [x] Add `.github/pull_request_template.md` and `CODEOWNERS` - S - elshad -- [x] Run `pnpm install`, `pnpm lint`, `pnpm typecheck`, `pnpm build`; fix any errors - M - aykhan -- [x] Push to `https://github.com/aykhan019/statify.git`, create `dev` branch, set branch protection on `main` and `dev` - S - aykhan -- [x] Verify commit attribution on GitHub for all four identities - S - aykhan - -## Phase 4, Foundation pieces (one at a time; each gated on Aykhan's approval) - -- [x] **F1: Config and Logging foundation** (env Zod schema, Pino, request IDs, Sentry wiring) - M - eljan -- [x] **F2: Database layer foundation** (PrismaService, base repository pattern, transaction helper) - M - eljan -- [x] **F3: Error handling and API envelope foundation** (AllExceptionsFilter, error codes, validation pipe wired end-to-end) - M - elshad -- [x] **F4: Auth foundation** (registration, login, refresh rotation, password hashing, CSRF, JwtAuthGuard, RolesGuard, refresh_tokens table use) - XL - aykhan -- [x] **F5: User session on the frontend** (server-side session lookup, middleware route guard, `useCurrentUser` hook) - L - rahila -- [x] **F6: Catalog read foundation** (TracksRepository, ArtistsRepository, AlbumsRepository, list+detail endpoints with pagination/filter/sort, DTOs in `shared`) - L - eljan -- [x] **F7: iTunes adapter foundation** (client, adapter, persistent cache via tracks table, rate limiter, fallback behaviour, integration test against a mock server) - L - elshad -- [x] **F8: Listening history foundation** (write endpoint with idempotency, repository, schema, indexes) - M - aykhan -- [x] **F9: Analytics foundation** (the six advanced SQL queries, raw $queryRaw, typed return types, unit tests with seed data) - XL - aykhan -- [x] **F10: Frontend design system foundation** (theme tokens, base components, layout primitives, navigation shell, audio player component) - L - rahila -- [x] **F11: MPD ingestion CLI** (parser, normalizer, batched upserts, checkpoint table, resume capability, 10k-playlist dry run) - XL - eljan -- [x] **F12: Admin extensibility foundation** (audit log writer, admin module skeleton, RolesGuard usage, no UI yet) - M - aykhan - -## Phase 5, Feature Roadmap (by milestone) - -Each milestone is one PR into `dev`. Per-task commits are attributed via the "Commit author" column. A milestone is ticked `[x]` only when every row underneath it is ticked. - -- [x] **M1: Authentication UI** - 5/5 - - [x] Signup form + validation + success state - M - aykhan (depends on F4) - - [x] Login form + error states - M - aykhan - - [x] Logout - S - aykhan - - [x] Password change - M - aykhan - - [x] Account deletion (soft delete, audit logged) - M - aykhan - -- [ ] **M2: Catalog browsing** - 4/5 - - [x] Tracks list page with infinite scroll - M - rahila (depends on F6, F10) - - [x] Track detail page with preview player, artist + album links - M - rahila - - [x] Artists list and detail with discography - M - rahila - - [x] Albums list and detail with track list - M - rahila - - [ ] Genres list and detail (post-genre derivation; waits for later iTunes-derived genre data) - M - rahila - -- [x] **M3: Audio player + listening history** - 6/6 - - [x] Audio player UI (play, pause, scrub, volume) - L - rahila (depends on F7, F10) - - [x] Auto-fetch preview on play if not cached - S - rahila (depends on F7) - - [x] Graceful "preview unavailable" state - S - rahila - - [x] "Play" event fires from audio player to backend - S - aykhan (depends on F8) - - [x] Recent listens page with infinite scroll - M - aykhan - - [x] Per-track play count on detail pages - S - aykhan - -- [x] **M4: Indexes + search/filter** - 3/3 - - [x] Global search bar with debounce, multi-entity results - L - eljan (depends on pg_trgm indexes) - - [x] Filter UI: duration range and preview availability - M - rahila (genre/year waits for later iTunes-derived data) - - [x] Sort controls on every list - S - rahila - -- [x] **M5: Personal stats and analytics views** - 8/8 - - [x] Top artists page (advanced query #1 wired into UI with Recharts) - L - aykhan - - [x] Top tracks page (variant of #1) - M - aykhan - - [x] Discover page (#2) - L - aykhan - - [x] Listening heatmap (#3) - M - aykhan - - [x] Trending artists (#4) - M - eljan - - [x] MPD playlist browsing (list + detail endpoints and /catalog/playlists pages, prereq for #5) - M - eljan - - [x] Similar playlists (#5) - M - eljan - - [x] Hidden gems (#6) - M - eljan - -- [x] **M6: Playlist creation and management** - 4/4 - - [x] Create user playlist - M - elshad - - [x] Add/remove tracks, reorder (drag-drop) - L - elshad - - [x] Public vs private toggle - S - elshad - - [x] Browse other users' public playlists - M - elshad - -- [x] **M7: Admin / data management** - 4/4 - - [x] Admin login route + role gate - S - aykhan (depends on F12) - - [x] Users list with search, ban/unban, role change (audit logged) - M - aykhan - - [x] Ingestion run trigger from admin UI - M - eljan - - [x] Audit log viewer - M - aykhan - -- [x] **M8: Rubric / quality demands** - 6/6 - - [x] ERD diagram (dbdiagram.io DBML export + PNG in `docs/`) - S - aykhan - - [x] Relational model write-up in `report/erd-explanation.md` - M - aykhan - - [x] Advanced SQL queries documented in `report/sql-queries.md` - M - aykhan - - [x] Seed script that produces meaningful number of tuples reliably - M - eljan - - [x] Final report - L - aykhan - - [x] Demo script - M - aykhan - -## Phase 6, Frontend redesign (one PR per milestone into `dev`; sized for one focused session each) - -The Phase 5 frontend works against the API but was built against the prior visual posture: single-hue accent on grayscale, no semantic token layer, no real entity imagery, no shared icon vocabulary. Phase 6 replaces that posture wholesale against a fully tokenized, multi-hue, identity-bearing system. Component-level work does not begin until M3 has shipped the token layer. - -Milestones must ship in order. Each milestone is a single PR into `dev` on a `feat/p6-m-` branch, per-task commits attributed via the "Commit author" column, merged with `gh pr merge --rebase --delete-branch`. - -Existing `(app)/**` components are destructively replaced as each Phase 6 milestone lands; the `dev` branch will show visual inconsistency between merged and unmerged surfaces during Phase 6. This is accepted. - -- [x] **P6-M1: Design Direction Exploration** - S - aykhan - - Goal: produce reference notes for five apps and three Statify direction proposals; surface a single pick. - - Entry criteria: HANDOFF + CHECKLIST + ADR-001 read; design intent confirmed; roadmap approved. - - Exit criteria: `docs/design/explorations.md` committed containing Step A (Linear, Vercel dashboard, Stripe dashboard, PostHog, Resend reference notes, 2-3 lines each covering typography character, color treatment, spacing density, separation strategy, vibe phrase) and Step B (three distinct named directions, each with feel paragraph, closest reference, sharpest tradeoff, imagery treatment, iconography treatment); Aykhan has picked one direction and the pick is recorded in HANDOFF.md Section 2. - - Files and folders touched: `docs/design/explorations.md` (new), `HANDOFF.md` (decision recorded), `CHECKLIST.md` (this row ticked). - - Depends on: none. - -- [x] **P6-M2: Author DESIGN.md from locked direction** - M - aykhan - - Goal: turn the locked direction into a complete token specification document at repo root. - - Entry criteria: P6-M1 merged; direction picked. - - Exit criteria: `DESIGN.md` committed at repo root specifying full color token set in oklch (semantic naming layer over raw palette, including data-viz palette of at least eight hues), type scale (font families with weight axis, size scale, line-height pairs, letter-spacing per role), spacing scale, radius scale, shadow scale, motion tokens (durations, easings, named transitions, all driven through `tailwindcss-animate` utilities), image aspect ratio scale with frame and overlay treatments and explicit fallback strategy when media fields are NULL, icon size scale with locked stroke weight and role mapping (inline / navigation / feature), and a one-screen "do / do not" section calling out the hard constraints from the design intent. ADR-002 drafted to record the deviation from ADR-001 §3.8 / §3.20 and the locked `image_url` schema decision. - - Files and folders touched: `DESIGN.md` (new), `docs/adr/0002-design-system-and-token-layer.md` (new), `HANDOFF.md` (Structural Changes Log row, Documents Map updated). - - Depends on: P6-M1. - -- [x] **P6-M3: Token layer implementation + dependency install + `/styleguide` route** - L - rahila - - Goal: encode every DESIGN.md token in CSS variables and the Tailwind 4 `@theme` block, install Lucide + shadcn/ui + Radix primitives + `tailwindcss-animate`, and ship a `/styleguide` route that renders every token visually for QA. - - Entry criteria: P6-M2 merged; DESIGN.md tokens locked. - - Exit criteria: `apps/web/src/app/globals.css` rewritten so every color, font, radius, spacing step, shadow, motion duration, easing, aspect ratio, and icon size exists as a CSS variable inside `@theme`; `lucide-react`, `@radix-ui/*`, shadcn/ui generator config, `tailwindcss-animate`, and the chosen self-hosted webfonts via `next/font` added to `apps/web/package.json` with the locked stroke weight enforced via a thin `` wrapper; `/styleguide` route at `apps/web/src/app/styleguide/page.tsx` renders the color palette (raw + semantic), type scale (every role at every weight), spacing scale, radius scale, shadow scale, motion samples, image frame treatments at every aspect ratio (with a NULL-media fallback example wired to the real fallback), and the entire in-use icon set at all locked sizes; `pnpm lint`, `pnpm typecheck`, `pnpm build` pass; manual QA on `/styleguide` confirms every token is visible. - - Files and folders touched: `apps/web/src/app/globals.css`, `apps/web/src/app/styleguide/page.tsx` (new), `apps/web/src/components/ui/` (Icon wrapper, base shadcn primitives copied in), `apps/web/src/lib/fonts.ts` (new), `apps/web/package.json`, `apps/web/components.json` (shadcn config, new), `HANDOFF.md` (Structural Changes Log rows for each dep). - - Depends on: P6-M2. - -- [x] **P6-M4: Media foundation (schema, adapter persistence, backfill)** - L - aykhan - - Goal: make real entity imagery available from the Prisma layer so later surfaces can render `` against live URLs per the design intent. - - Entry criteria: P6-M3 merged; DESIGN.md aspect ratio scale locked; ADR-002 records the `image_url`-column decision. - - Exit criteria: Prisma migration adds `image_url` (text, nullable) to `tracks`, `albums`, `artists`; iTunes adapter at `apps/api/src/integrations/itunes/` persists the artwork URL on resolve alongside the existing `preview_url` write (using the `100x100bb.jpg` → `600x600bb.jpg` substitution at write time); backfill script at `packages/db/src/scripts/backfill-media.ts` populates existing rows by replaying iTunes lookups for tracks lacking `image_url`; albums inherit from the first ingested track's image; artist `image_url` stays null and the UI uses the DESIGN.md null-fallback; DTOs in `packages/shared` exposing `imageUrl` on the relevant entities; `apps/web/next.config.mjs` image domain allowlist updated for `is*-ssl.mzstatic.com`; unit tests for adapter persistence and backfill idempotency; `pnpm --filter @statify/db prisma migrate dev`, `pnpm --filter @statify/api test`, `pnpm typecheck`, `pnpm build` pass; HANDOFF Structural Changes Log row added. - - Files and folders touched: `packages/db/prisma/schema.prisma`, `packages/db/prisma/migrations/_entity_media/`, `packages/db/src/scripts/backfill-media.ts` (new), `apps/api/src/integrations/itunes/itunes.adapter.ts`, `apps/api/src/integrations/itunes/itunes.cache.ts`, `apps/api/src/modules/catalog/**` (DTO surface), `packages/shared/src/dto/**`, `apps/web/next.config.mjs`, `docs/adr/0002-design-system-and-token-layer.md` (schema decision section). - - Depends on: P6-M3. - -- [x] **P6-M5: Layout primitives + app shell rewrite** - M - rahila - - Goal: replace ad-hoc layout JSX with token-bound primitives (Container, Stack, Grid, Section, Surface, Divider, Spacer) and rebuild the authed shell on top. - - Entry criteria: P6-M3 merged. - - Exit criteria: primitives under `apps/web/src/components/layout/` consume only token classes (no hard-coded px, hex, or font-family); existing `(app)/layout.tsx` re-implemented in terms of the primitives; container widths, gutters, and grid steps documented in DESIGN.md update; `/styleguide` route gains a "Primitives" section showing every primitive at every documented variant; lint / typecheck / build pass; manual smoke confirms every existing authed route still renders without layout regressions. - - Files and folders touched: `apps/web/src/components/layout/**` (new), `apps/web/src/app/(app)/layout.tsx`, `apps/web/src/app/styleguide/page.tsx`, `DESIGN.md` (primitives addendum). - - Depends on: P6-M3. - -- [x] **P6-M6: Navigation system** - L - rahila - - Goal: rebuild top bar, side navigation, mobile navigation, breadcrumbs, and the user menu against the new token layer and the layout primitives. - - Entry criteria: P6-M5 merged. - - Exit criteria: nav components under `apps/web/src/components/navigation/` use only Lucide icons at the locked nav size and the semantic color tokens; active / hover / focus / disabled states defined in DESIGN.md and visible at `/styleguide`; mobile breakpoint behaviour documented; keyboard navigation through every nav surface verified manually; lint / typecheck / build pass. - - Files and folders touched: `apps/web/src/components/navigation/**` (new), `apps/web/src/app/(app)/layout.tsx` (consumes nav), `DESIGN.md` (nav states addendum), `apps/web/src/app/styleguide/page.tsx`. - - Depends on: P6-M5. - -- [x] **P6-M7: Data display with real media (cards, lists, detail pages)** - L - rahila - - Goal: rebuild Track, Artist, Album, and Playlist cards / list rows / detail headers to render `` from the Prisma `image_url` fields with the DESIGN.md aspect ratio and frame treatments. - - Entry criteria: P6-M4 merged (media in DB) and P6-M6 merged (shell exists). - - Exit criteria: components under `apps/web/src/components/{catalog,playlists}/` use `next/image` against live `imageUrl` fields from the API; aspect ratio, frame, and overlay match the DESIGN.md scale; explicit fallback variant renders when the field is NULL (the DESIGN.md null-fallback, no generic gradients, no placeholders); detail page hero treatments updated for Track / Artist / Album / Playlist; lint / typecheck / build pass; manual smoke on each list and detail route against seeded data confirms real artwork loads. - - Files and folders touched: `apps/web/src/components/catalog/**`, `apps/web/src/components/playlists/**`, `apps/web/src/app/(app)/catalog/**`, `apps/web/src/app/(app)/playlists/**`, DESIGN.md (any media decisions discovered during build). - - Depends on: P6-M4, P6-M6. - -- [x] **P6-M8: Forms system** - M - aykhan - - Goal: rebuild every form (signup, login, password change, account deletion confirmation, playlist create / edit, admin user edit) on a token-bound RHF + Zod primitive set. - - Entry criteria: P6-M3 merged. - - Exit criteria: form primitives under `apps/web/src/components/forms/` (Field, Label, Input, Textarea, Select, Checkbox, Switch, FormError, FormHint, SubmitButton) wired to existing shared Zod schemas; error, focus, disabled, and loading states defined in DESIGN.md and visible at `/styleguide`; each existing form route re-implemented against the primitives; lint / typecheck / build pass; manual smoke of every form route confirms submission and validation paths still work. - - Files and folders touched: `apps/web/src/components/forms/**` (new), `apps/web/src/components/auth/**`, `apps/web/src/components/playlists/**` (form-touching ones), `apps/web/src/components/admin/**` (form-touching ones), `DESIGN.md` (form states addendum), `apps/web/src/app/styleguide/page.tsx`. - - Depends on: P6-M3. - -- [x] **P6-M9: Empty, loading, and error states pass** - M - rahila - - Goal: design and ship a shared vocabulary for empty states, skeleton loaders, error surfaces, and not-found pages on top of every list, detail, and form route built so far. - - Entry criteria: P6-M7 and P6-M8 merged. - - Exit criteria: state primitives under `apps/web/src/components/states/` (Skeleton, EmptyState, ErrorState, NotFoundState) consume tokens only; every list and detail route in `(app)/**` wired to a skeleton during suspense, an empty state when the response is zero-length, an error state when the fetch fails, and a not-found state when the entity is missing; copy and icon choices documented in DESIGN.md; lint / typecheck / build pass; manual smoke triggers each state (throttle network for skeletons, force 404, force 500). - - Files and folders touched: `apps/web/src/components/states/**` (new), every route file under `apps/web/src/app/(app)/**` that fetches, `DESIGN.md` (states addendum). - - Depends on: P6-M7, P6-M8. - -- [x] **P6-M10: Section identity headers + section-hue propagation** - L - rahila - - Goal: apply the locked Vivid Workshop "section-as-color" identity (DESIGN.md §1.3) to every top-level route — a full-bleed section-hue block header on each route, with the section hue propagated to the active nav indicator, row hover, cover frames, and the default chart series. This closes the gap where the §1.3 tokens and per-route hue assignments exist but no route renders the block treatment. - - Entry criteria: P6-M5 (shell) and P6-M6 (nav) merged; section identity hue assignments and the `--color-section-*` aliases locked in DESIGN.md §1.3. - - Exit criteria: a section provider under `apps/web/src/components/section/` resolves the active section from the route prefix per DESIGN.md §1.3 and sets `--color-section-block`, `-block-fg`, `-tint`, `-accent`, `-accent-fg`, `-row-hover`, and `-frame` on the route subtree; every top-level route renders a full-bleed block header in `bg-section-block text-section-on-block` at `text-5xl` weight 800 per §2.2, with `/me/account` kept as neutral chrome (no block) per the §1.3 exception; the active section hue propagates to the active nav / tab indicator, list row hover tint (`--color-section-row-hover`), the cover frame on every card / list / detail cover inside the section (`--color-section-frame`), and the default chart series (section hue moved to series index 0 per §1.6); no-section routes (global search, sign-in, error / not-found) resolve to the indigo Library default per §1.3; DESIGN.md gains a section-header / block-treatment addendum (block dimensions, full-bleed behaviour, propagation rules); `/styleguide` gains a section rendering the block header for all eleven section hues; lint / typecheck / build pass; manual smoke confirms each top-level route shows its block header in the correct hue and the nav indicator / row hover / cover frame match. - - Files and folders touched: `apps/web/src/components/section/**` (new section provider + block header), `apps/web/src/app/(app)/**` (per-route headers / layouts), `apps/web/src/components/navigation/**` (active indicator reads section hue), `apps/web/src/components/{catalog,playlists}/**` (cover frame reads `--color-section-frame`), `DESIGN.md` (section-header addendum), `apps/web/src/app/styleguide/page.tsx`. - - Depends on: P6-M5, P6-M6. - -- [x] **P6-M11: Analytics surfaces re-skin (Recharts against tokens)** - M - aykhan - - Goal: re-skin every Recharts surface (top artists, top tracks, discover, heatmap, trending, hidden gems) against the locked token palette and motion tokens, including a shared chart wrapper that pulls axis / grid / tooltip / palette from CSS variables. - - Entry criteria: P6-M3 merged; data-viz palette (at least eight hues) defined in DESIGN.md; P6-M10 merged (active section hue available for the series index-0 swap). - - Exit criteria: a single chart theme module at `apps/web/src/components/charts/` reads axis, grid, tooltip, and series colors from CSS variables; every existing `stats/**` page wired through it with no inline hex; heatmap uses a documented multi-stop scale from the DESIGN.md data-viz palette; tooltip and legend treatments documented; lint / typecheck / build pass; manual smoke confirms charts render with the new palette and reflow on resize. - - Files and folders touched: `apps/web/src/components/charts/**`, `apps/web/src/components/stats/**`, `apps/web/src/app/(app)/me/stats/**`, `DESIGN.md` (data-viz palette addendum). - - Depends on: P6-M3, P6-M10. - -- [x] **P6-M12: Motion pass** - M - rahila - - Goal: apply the DESIGN.md motion tokens across navigation, list mounts, modal / dialog open / close, hover and focus transitions, and player state changes; honour `prefers-reduced-motion`. `framer-motion` may be introduced here only if a specific surface needs layout / exit animation that `tailwindcss-animate` cannot deliver; recorded in HANDOFF Structural Changes Log if added. - - Entry criteria: P6-M7, P6-M8, P6-M9, P6-M10, P6-M11 merged. - - Exit criteria: every transition in the app references a named motion token (no inline durations, no inline easings); `prefers-reduced-motion: reduce` swaps to a no-motion variant globally; motion tokens visible on `/styleguide` with side-by-side reduce-motion preview; lint / typecheck / build pass; manual smoke of every authed route confirms transitions feel coherent and no surface flashes. - - Files and folders touched: `apps/web/src/components/**` (transition wiring), `apps/web/src/app/globals.css` (reduce-motion override), `apps/web/src/app/styleguide/page.tsx`. - - Depends on: P6-M7, P6-M8, P6-M9, P6-M10, P6-M11. - -- [x] **P6-M13: Accessibility pass** - M - aykhan - - Goal: end-to-end a11y audit and fix pass against WCAG 2.2 AA on the redesigned surfaces. - - Entry criteria: P6-M12 merged. - - Exit criteria: `eslint-plugin-jsx-a11y` clean; semantic landmarks (`header`, `nav`, `main`, `aside`, `footer`) verified on every layout; focus order keyboard-walked through every authed route; visible focus ring (from tokens) confirmed on every interactive element; color contrast checked for every semantic foreground / background pair documented in DESIGN.md and recorded in a contrast table at the bottom of DESIGN.md; screen-reader smoke (VoiceOver) on signup, login, search, track detail with preview play, playlist create, admin user edit; results recorded in `docs/design/a11y-audit.md`; lint / typecheck / build pass. - - Files and folders touched: `apps/web/src/components/**` (fixes), `apps/web/src/app/**` (fixes), `DESIGN.md` (contrast table), `docs/design/a11y-audit.md` (new). - - Depends on: P6-M12. - -## Stretch features (post-rubric) - -- [ ] PWA install + offline shell - M -- [ ] AZ / RU translations - M -- [ ] Google OAuth - M -- [ ] Spotify OAuth (if dashboard cooperates) - L -- [ ] Track recommendations via simple matrix factorization on `listening_history` - XL -- [ ] Social: follow other users, see their public listens - XL -- [ ] Native shell via Capacitor - L -- [x] Public and authed landing page redesign with manual light/dark mode and local decorative cover art - M - rahila - -## Deployment and submission - -- [x] Generate initial Prisma migration (`prisma migrate dev --name initial`); commit `packages/db/prisma/migrations/` - S - aykhan -- [x] Spotify album and artist artwork backfill script + ADR - M - aykhan -- [ ] Production env vars set in Vercel and Render - S - aykhan -- [ ] Smoke test against production URLs - S - aykhan -- [ ] Demo dataset confirmed on prod DB (10k-playlist subset ingested) - M - eljan -- [ ] Warm-up ping verified via cron-job.org - S - elshad -- [ ] Report final draft circulated - L - aykhan -- [ ] Slides for in-class presentation - M - aykhan -- [ ] Final demo dry-run - M - all -- [ ] Submit - S - aykhan diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index e69de29..0000000 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5b1abc1..c2c9886 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,12 +1,11 @@ # Contributing to Statify -This document is the operating manual for the four team members working on Statify. +This document is the operating manual for the team working on Statify. ## Read first -- `HANDOFF.md`, ground rules and current state. -- `CHECKLIST.md`, the master task list. Pick from here; do not invent tasks. - `docs/adr/`, architectural decisions and rationale. +- `DESIGN.md`, the design system and token layer. ## Branching @@ -17,23 +16,6 @@ This document is the operating manual for the four team members working on Stati ## Commits -All commits must be authored using `scripts/commit-as.sh`. Do not run `git commit` directly. - -```bash -# Stage your changes first -git add - -# Then commit through the wrapper -scripts/commit-as.sh aykhan -m "feat(auth): add Argon2id password hashing" -scripts/commit-as.sh elshad -m "chore(ci): bump action versions" -scripts/commit-as.sh rahila -m "feat(web): add empty Track list page" -scripts/commit-as.sh eljan -m "feat(api): add /healthz endpoint" -``` - -The wrapper reads `scripts/.authors` and sets the commit author and committer environment variables for that single `git commit` call. It does not modify your global git config. - -### Commit message format - Conventional Commits, enforced by commitlint in the `commit-msg` hook. ``` @@ -50,20 +32,13 @@ Scope examples: `auth`, `api`, `web`, `db`, `shared`, `ci`, `docs`. Subject: imperative mood, lower case, no trailing period, under 72 chars. -### Forbidden - -- No mention of external tools, models, or AI products in commit messages, code comments, PR bodies, or any artifact in this repository. (See `HANDOFF.md` Rule 1.) -- No `Co-Authored-By:` trailers. -- No raw `git commit`; use the wrapper. - ## Pull Requests - Open against `dev` for features and fixes. - Use the PR template (`.github/pull_request_template.md`). - Include a screenshot or short clip for any UI change. - Note DB migrations explicitly: a PR that adds a Prisma migration must list it. -- CI must pass. -- Aykhan reviews and merges to `dev`. Merges to `main` are batched on release boundaries. +- CI must pass before merge. ## Code standards @@ -86,12 +61,3 @@ Subject: imperative mood, lower case, no trailing period, under 72 chars. - Never commit secrets. Use `.env.local` for personal overrides; it is gitignored. - Validate every controller input through Zod. - Use Prisma's parameterized queries. Raw queries use the `$queryRaw` tag, never `$queryRawUnsafe`. - -## Updating the handoff - -Before ending a working session: - -1. Update `HANDOFF.md` Section 2 (Current State). -2. Tick completed items in `CHECKLIST.md`. -3. If anything was left mid-flight, write it under "Open threads" in `HANDOFF.md`. -4. Commit those updates. diff --git a/HANDOFF.md b/HANDOFF.md deleted file mode 100644 index 654cb5e..0000000 --- a/HANDOFF.md +++ /dev/null @@ -1,218 +0,0 @@ -# Statify, Session Handoff and Ground Rules - -> Read this file in full at the start of every working session, before opening any other file. -> Update Section 2 at the end of every session. - -## 1. Ground Rules (immutable; do not modify without explicit approval from Aykhan) - -**R1. Authorship.** No external tool, model, or product name appears anywhere in this repository: not in commits, not in code comments, not in documentation, not in PR descriptions, not in commit co-authors, not in this file. All work is authored by the four team members listed in Section 4. - -**R2. Commit attribution.** Every commit must be authored by exactly one of the four team members. Use `scripts/commit-as.sh [git commit args...]` for every commit. Do not run `git commit` directly. Distribute commits across team members per the "Commit author" column in `CHECKLIST.md`. - -**R3. Folder structure.** The repository layout is defined in `docs/adr/0001-tech-stack-and-foundation.md` Sections 3.1 and 3.5. Do not create new top-level folders or rename existing ones without (a) recording the change in Section 3 of this file and (b) opening a new ADR. - -**R4. Decisions.** Any architectural decision (library choice, pattern adoption, schema change) gets a new ADR in `docs/adr/`. Number sequentially. Do not bury decisions in code comments, PR descriptions, or this file. - -**R5. State updates.** At the end of every working session: update Section 2 of this file, tick completed items in `CHECKLIST.md`, and write any mid-flight work into "Open threads" so the next session resumes cleanly. - -**R6. No invention.** Do not create files, endpoints, schemas, or features not already listed in `CHECKLIST.md`. If a need arises, add it to the checklist first, get Aykhan's approval, then build. - -**R7. Destructive actions.** Confirm with Aykhan before: force push, hard reset, history rewrite, branch deletion, mass rename, dropping tables, dropping migrations. Never run these on `main` or `dev`. - -**R8. Style.** All docs in impersonal voice. No emoji unless explicitly requested. No em dashes; use regular hyphens. Plain Markdown. - -**R9. Verification.** "Done" requires the relevant verification to pass: lint, typecheck, tests, and a manual smoke test for UI changes. State exactly what was verified. - -**R10. Scope discipline.** If asked for X, do X. Do not opportunistically refactor Y. Add cleanups to `CHECKLIST.md` and run them as separate tasks. - -## 2. Current State - -**Updated:** 2026-05-25 - -- **Phase 4 status:** complete. All twelve foundation pieces (F1-F12) are shipped on `dev`. The deterministic dev seed script (Phase 5 rubric task) is also merged and runs via `pnpm --filter @statify/db db:seed`. -- **Phase 5 status:** complete (M1-M8 all on `dev`). -- **Phase 6 status:** M1 ✓ (PR #28, `e39bdeb`), M2 ✓ (PR #29, `e7e9053`), M3 ✓ (PR #30, `a543cc2`), M4 ✓ (`98ad518`), M5 ✓ (PR #32, `6ce01b3`), M6 ✓ (PR #33, `b9f3858`), M7 ✓ (PR #34, `871dd49`), M8 ✓ (PR #35, `ae9309f`), M9 ✓ (state vocabulary, PR #37), M10 ✓ (PR #38, `f16bb46`), M11 ✓ (PR #39, `ec35ed2`), M12 ✓ (PR #40, `ea76c55`), M13 ✓ (PR #41, `a8a49a3`). Phase 6 frontend redesign is complete. (Note: the original draft numbered redesign as Phase 7 with deployment as Phase 6; the canonical docs never actually numbered deployment, so Phase 6 is the redesign and the existing "Deployment and submission" section stays unnumbered.) -- **Deployment and submission status:** the unnumbered "Deployment and submission" section in `CHECKLIST.md` is unpaused now that Phase 6 is complete. -- **Last shipped:** P6-M13 Accessibility pass (PR #41, branch `feat/p6-m13-accessibility-pass`). Adds root JSX accessibility lint coverage; fixes heading hierarchy, table semantics, search panel wiring, media control labels, heatmap narration, focus rings, and contrast-safe section aliases; records `docs/design/a11y-audit.md` plus the DESIGN.md contrast table. Also permits the shared CSRF request header in API CORS so local browser mutation smoke reaches playlist/admin endpoints. Verification passed: `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm test`, `pnpm build`, PR CI, and local accessibility smoke for signup, login, global search, track preview, playlist create, admin users, landmarks, and keyboard focus. -- **Last merge (non-milestone):** PR #36 `chore: add pnpm setup one-shot bootstrap` rebase-merged into `dev` on 2026-05-25 (HEAD `3ad5c16`, author Aykhan). Adds a `pnpm setup` script plus `scripts/setup.sh` (install -> prisma generate -> migrate deploy -> build), updates the README quickstart, and the Structural Changes Log row landed with the PR. It was stranded behind a one-line conflict in the HANDOFF Structural Changes Log table; resolved by rebasing onto current `dev` and keeping both the M9 `components/states/` row and the new `pnpm setup` row. CI green post-rebase (format / lint / typecheck / build); chore, no UI smoke required. Open: the PR test plan's fresh-clone smoke (clone, copy `.env.example`, fill secrets, run `pnpm setup`) is still unverified. -- **Last local change:** The broken blue image tiles on the redesigned landing surfaces were expired hard-coded Apple artwork sample URLs, not database or Spotify backfill output. They now use a local tokenized `DecorativeCoverTile` component on public `/` and authed `/me`; `/styleguide` uses local data images for cover demos; `apps/web/next.config.mjs` also allows `i.scdn.co` for real Spotify artwork once the backfill succeeds. Verification passed: `pnpm lint`, `pnpm --filter @statify/web typecheck`, `pnpm --filter @statify/web test`, `pnpm --filter @statify/web build`, and local HTTP smoke for `http://localhost:3001/` plus `/styleguide` with no remaining `mzstatic` sample references. -- **Previous local UI change:** Public `/` and authed `/me` landings are redesigned against the existing Vivid Workshop token system and media treatments. Manual light/dark mode is implemented with `data-theme` on ``, a persistent `statify_theme` cookie/localStorage value, and a shared icon segmented control in marketing, auth, desktop app nav, and mobile app nav. Verification passed: `pnpm format:check`, `pnpm lint`, `pnpm --filter @statify/web typecheck`, `pnpm --filter @statify/web test`, `pnpm --filter @statify/web build`, and local HTTP smoke for `http://localhost:3001/`, `/login`, `/login` with `Cookie: statify_theme=dark`, and authenticated `/me` with a temporary local smoke user. Port 3000 was already occupied, so smoke ran on port 3001; the in-app browser backend was unavailable in this session, so visual screenshot smoke was not run. -- **Previous local change:** ADR-003 records Spotify as the album/artist artwork source and iTunes as the preview source. `packages/db/src/scripts/backfill-media.ts` now bulk-fills album and artist `image_url` values from Spotify using client credentials, Web API ID batching, idempotent skipping, `--overwrite-existing`, request pacing, and `429` `Retry-After` backoff. `.env.example` documents `SPOTIFY_CLIENT_ID` and `SPOTIFY_CLIENT_SECRET`; `packages/db/package.json` exposes `pnpm --filter @statify/db db:backfill-media`. A live probe reached Spotify successfully but Spotify returned `403 Active premium subscription required for the owner of the app`; run again after the Spotify app owner has Premium or swap in credentials from a Premium-owned app. Verification passed: `pnpm format:check`, `pnpm lint`, `pnpm --filter @statify/db test`, `pnpm --filter @statify/db typecheck`, `pnpm --filter @statify/api test`, and `pnpm --filter @statify/api typecheck`. -- **Phase 5 roadmap:** M1 ✓ → M2 (4/5) → M3 ✓ → M4 ✓ → M5 ✓ → M6 ✓ → M7 ✓ → M8 ✓. See `CHECKLIST.md` Phase 5 for the per-task breakdown and the milestone checkboxes. -- **Milestone cadence:** each milestone ships as one PR into `dev` (`feat/` branch, per-task commits with the correct author from `CHECKLIST.md`). Phase 6 branches use `feat/p6-m-`. Merge with `gh pr merge --rebase --delete-branch` so the per-task commits are preserved on `dev`. Do not start the next milestone until the previous one is merged. -- **Current milestone:** none. Phase 6 is complete; resume the unnumbered Deployment and submission section. -- **Currently in progress:** none. -- **Open files/components:** none. -- **Open decisions:** none for the current milestone. Locked decisions feeding Phase 6: - - **Design direction:** Vivid Workshop. Picked 2026-05-24, recorded in `docs/design/explorations.md` Step C. P6-M2 authors DESIGN.md from this direction. - - **Entity media field shape:** single nullable `image_url` column on `tracks`, `albums`, `artists`. The original P6-M4 iTunes artwork-source assumption in ADR-002 is superseded by ADR-003: Spotify populates album and artist artwork, iTunes only populates track preview metadata, and track `image_url` remains nullable with UI fallback to album artwork. - - **Playlist media shape:** playlist list/detail DTOs expose `coverImages: string[]` derived from the first four member tracks' `track.imageUrl ?? album.imageUrl`; UI repeats fewer than four images to fill the 2x2 collage and falls back to the playlist letter when none exist. Landed locally during P6-M7. - - **Artwork source:** Spotify Web API is the canonical source for album and artist artwork; iTunes remains the preview URL source. Recorded in ADR-003. - - **Motion library:** `tailwindcss-animate` (shadcn / Radix default, CSS-only, near-zero bundle). `framer-motion` was not introduced during P6-M12. - - **Webfonts:** self-hosted via `next/font`. Specific families locked in P6-M2 DESIGN.md. - - **Existing UI during Phase 6:** destructively replaced as each P6 milestone lands; `dev` will show visual inconsistency between merged and unmerged surfaces during Phase 6. -- **ADR-001 deviations recorded for Phase 6:** - - §3.8 "Tokens (color, spacing, radii) defined once in `tailwind.config.ts`" is superseded by Phase 6's CSS-variable + Tailwind 4 `@theme` token layer. Recorded in ADR-002 during P6-M2. - - §3.20 "Custom design tokens system; Tailwind config is enough" is superseded by the same ADR. - - The schema gap (no media fields on `Artist`, `Album`, `Track`) is recorded in ADR-002 and implemented during P6-M4. -- **Open threads:** - - Local UI redesign requested after P6 completion: public `/` landing and authed `/me` landing have been rebuilt against existing tokens and media treatments. Manual light/dark mode is implemented with `data-theme` on ``, persistent `statify_theme` cookie/localStorage, and a shared icon segmented control in marketing, auth, desktop app nav, and mobile app nav. Follow-up fixed the expired decorative artwork URLs by replacing them with tokenized local cover tiles and adding the Spotify CDN image allowlist. Verification passed with web typecheck/test/build, root lint/format, and HTTP smoke on light public/auth routes, dark auth route, authenticated `/me`, and `/styleguide`. - - M8 shipped through PR #25 plus one follow-up docs commit on `dev`. The completed rubric artifact set is `docs/erd.dbml`, `docs/erd.png`, `report/erd-explanation.md`, `report/sql-queries.md`, `report/final-report.md`, and `report/demo-script.md`. - - `docs/erd.png` is a dbdiagram.io export generated from `docs/erd.dbml`. - - No schema, dependency, config, or folder-structure changes landed in PR #25. The structural changes log does not need a new row for the docs/report file additions. - - PR #25 CI passed before the rebase merge. The follow-up ERD/docs update was format-checked locally. - - Local runtime fix after M8: `@statify/shared` and `@statify/db` now resolve runtime imports from built `dist` outputs while keeping TypeScript declarations pointed at source, root `pnpm dev` and `pnpm test` build those packages before starting app runtime/tests, and the API config loads the root `.env` when launched from `apps/api`. This fixes the local API crash where current Node 22 rejected TypeScript source directory imports. - - `AuthModule` now exports `AuthTokenService` so `JwtAuthGuard` can be injected from modules that import `AuthModule` (analytics, admin, history, catalog, and user playlists). - - API bootstrap now enables CORS from `ALLOWED_ORIGINS` with credentials, so browser preflight requests from the local web app can reach authenticated endpoints. - - Verification after the local runtime and CORS fixes: `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm --filter @statify/api test`, `pnpm build`, `GET /healthz` on the local API, `OPTIONS /api/v1/auth/login`, and `POST /api/v1/auth/login` with the seeded user all passed under Node 22. - - Full authenticated end-to-end UI smoke against seeded data still needs to be repeated under Node 22 before the dev → main promotion. The API now starts locally and accepts browser login preflight; use admin and user seed accounts for the smoke. - - P6-M9 merged via PR #37 (rebase, 5 commits: 1 aykhan planning + 4 rahila). `pnpm lint` / `typecheck` / `build` and CI all passed, and `/styleguide` §19 renders the panels. Still pending: the interactive state smoke that needs an authenticated browser, namely skeletons via network throttle, `(app)/error.tsx` via a forced fetch failure, and `(app)/not-found.tsx` via a bad detail id while logged in. Fold this into the pre-promotion UI smoke above. M9 added no dependency, schema, config, or top-level folder; the only structural delta is the `components/states/` folder row logged in Section 3. - - P6-M10 merged via PR #38 (rebase, 1 Rahila commit). `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm test`, `pnpm build`, CI, and authenticated local HTTP smoke all passed. The in-app browser backend was unavailable in this session, so the smoke used rendered HTML checks against the running local dev server instead of a visual browser pass. M10 added no dependency, schema, config, or top-level folder; the structural delta is the `components/section/` folder row logged in Section 3. - - P6-M11 merged via PR #39 (rebase, 1 Aykhan commit). `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm test`, `pnpm build`, CI, authenticated local HTTP smoke, and local browser chart smoke all passed. M11 added no dependency, schema, config, or top-level folder; the structural delta is the `components/charts/` folder row logged in Section 3. The prior `/me/stats/top-artists` non-number `totalMinutes` 500 is resolved by analytics mapper Decimal-like coercion. - - P6-M12 merged via PR #40 (rebase, 1 Rahila commit). `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm test`, `pnpm build`, CI, and local UI smoke all passed. M12 added no dependency, schema, config, or top-level folder; no Structural Changes Log row needed. - - P6-M13 merged via PR #41 (rebase, 1 Aykhan commit). `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm test`, `pnpm build`, CI, and local accessibility smoke all passed. Smoke covered signup/login labels, global search region wiring, track preview controls, playlist create, admin user table/actions, route landmarks, and keyboard focus across redesigned routes including top artists. M13 added a root accessibility lint dependency and an API CORS header config change; the dependency row is logged in Section 3. - - `toQueryString` is duplicated across `apps/web/src/lib/{admin,analytics,playlists,history,user-playlists}/api.ts`. The admin client makes it the fifth instance, so the hoist into a shared util is now due as a separate cleanup task (not in M8 scope). - - P6-M4 verification: `pnpm --filter @statify/db prisma:migrate:dev` passes with root `.env` loaded after mirroring the existing pg_trgm GIN indexes in Prisma schema (`ops: raw("gin_trgm_ops")`). `pnpm test`, `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, and `pnpm build` also pass. - - P6-M5 verification: `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm build`, and `pnpm --filter @statify/web test` pass. Local runtime smoke passed with `pnpm dev`: `/styleguide` returned 200 and rendered the "Layout primitives" section; login as `alex@statify.local` succeeded through the API; `/me` returned 200 with the new shell, header, sidebar, account link, and overview content. The in-app browser backend exposed no browser targets in this session, so the smoke used HTTP checks against the running local dev server instead of a visual browser pass. - - P6-M6 verification: `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm build`, and `pnpm --filter @statify/web test` pass. Local runtime smoke passed with a restarted `pnpm dev`: `/styleguide` returned 200 and rendered the "Navigation system" section; login as `alex@statify.local` succeeded; `/me` returned 200 with top nav, side nav, mobile trigger, user menu, breadcrumbs, and active Overview state; `/me/playlists` returned 200 with breadcrumb and active Playlists state; admin login succeeded and `/admin` returned 200 with admin nav and user menu. The in-app browser backend exposed no browser targets in this session, so keyboard and visual checks were limited to rendered markup and token focus-state coverage in `/styleguide`. - - P6-M7 verification: `pnpm format:check`, `pnpm lint`, `pnpm typecheck`, `pnpm test`, and `pnpm build` pass. Local media smoke used a restarted `pnpm dev`, login as `alex@statify.local`, and a small local backfill (`pnpm --filter @statify/db exec tsx src/scripts/backfill-media.ts --limit 20 --batch-size 20` with root `.env` sourced) that updated 9 tracks and 8 albums. HTTP smoke passed for `/styleguide` ("Data display media"), `/catalog/tracks?hasPreview=true`, `/catalog/albums?q=Frames`, `/catalog/artists?q=Glass`, `/catalog/playlists?q=Coffee`, `/catalog/tracks/9`, `/catalog/albums/2`, `/catalog/artists/26`, `/catalog/playlists/17`, `/me/playlists`, and `/community/playlists`; real artwork rendered on the imaged catalog and MPD playlist routes, while artist/user playlist routes exercised the designed fallback. The in-app browser backend again exposed no browser targets, so visual verification was limited to rendered markup and `/styleguide`. - - P6-M8 verification: `pnpm format:check`, `pnpm lint` (root `--max-warnings=0`), `pnpm typecheck`, `pnpm test` (api 140/140, web 52/52, db 47/47), and `pnpm build` pass. HTTP smoke against the running local dev server (Node 22, alex + admin seeded logins) returned 200 for `/styleguide`, `/signup`, `/login`, `/me/account`, `/me/playlists/new`, `/me/playlists`, `/admin/users`, `/admin/users?q=alex`, `/admin/audit-log`, `/admin/audit-log?action=admin.user.banned`, and `/admin/ingest`. Submission paths exercised through the styleguide loading-state demo and through unchanged RHF + Zod wiring; full browser submission smoke (each form's success and validation paths) is deferred to the pre-promotion sweep alongside M5-M7. The in-app browser backend exposed no browser targets in this session. -- **Blockers (gate further milestone work):** none. -- **Deployment gates:** - 1. **`dev` is ahead of `main`.** Per ADR-001 Section 3.15, `main` is only updated by PR from `dev`. Hold the dev → main promotion until the unnumbered "Deployment and submission" items in `CHECKLIST.md` (production hosting env vars, warm-up ping, smoke test) are complete. - 2. A broad pre-promotion UI smoke is still required for deployment. M13 covered the accessibility route sweep and key auth/search/preview/playlist/admin flows; still explicitly owed from earlier handoff: M9 interactive state checks (skeletons via network throttle, forced app error, bad detail id not-found) plus production URL smoke after deployment envs are configured. -- **Next concrete action:** resume the unnumbered Deployment and submission section; start by setting production env vars for the web/API hosts, then run production smoke. -- **Follow-ups:** - - Wire `AuditLogService.record(...)` into the login flow once additional privileged actions land. Password change, account deletion, admin ban/unban, admin role change, and admin ingest trigger already audit-log via their respective services. - - Genre/year filters and the M2 genres list/detail row are blocked on later iTunes-derived data from `primaryGenreName`. That derivation has no current task row; if either row needs to fully tick, add a Phase 5 row for it first. - - The "play event" counts a play as soon as audio starts (one history row per track per session). If we later want a stricter rule (e.g., 50% played), update `PlayHistoryReporter` to fire from `onTimeUpdate` instead of on the playing transition. - - Hoist `toQueryString` from the five web API clients (`lib/{admin,analytics,playlists,history,user-playlists}/api.ts`) into a shared util under `apps/web/src/lib/utils/`. The pattern is identical across all five; a separate small refactor task. -- **Dry-run procedure (F11), once migrations exist:** download MPD slices to `data/mpd/` (gitignored), run `pnpm --filter @statify/db db:ingest -- --data-dir data/mpd --slices 10 --resume`. Inspect `ingest_checkpoints` for per-slice progress and any `error_message`. The 10k-playlist dry-run itself requires the dataset and is a manual verification step outside CI. -- **Watch list:** - 1. Verify commit attribution on GitHub for all four identities after the first push; if Elshad's or Rahila's `@ku.edu.tr`-authored commits do not link to their profiles, the email must be added at https://github.com/settings/emails on each account. - 2. Neon free tier is 0.5 GB; re-verify headroom after the first MPD ingest dry run. - 3. Render free service spins down after 15 min idle; set up cron-job.org warm ping after the first deploy. - 4. F8 (PR #9) was squash-merged by mistake instead of rebase-merged; every subsequent milestone PR (most recently #22 for M5, #23 for M6, and #24 for M7) has used `gh pr merge --rebase --delete-branch` to preserve per-task commit history. Continue using `--rebase` for future merges to `dev`. - 5. The `dev` branch has a "Required status check: ci" rule. Direct pushes of docs-only commits to `dev` (e.g. `docs: close M session`) trigger a `Bypassed rule violations for refs/heads/dev` warning in the push output. This is expected for the session-close cadence and matches what M5 (`bb68e04`), M6 (`04b3883`), and M7 are doing; feature work always goes through a PR where CI runs and must pass. - 6. GitHub Actions Node 20 deprecation: the CI workflow's `actions/checkout@v4`, `actions/setup-node@v4`, and `pnpm/action-setup@v4` run on Node 20, which GitHub force-migrates to Node 24 on 2026-06-02 (Node 20 removed from runners 2026-09-16). Surfaced as a non-blocking CI annotation on PR #36. Bump the action major versions (or set `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true`) before 2026-06-02; small `chore(ci)` task, attribute to `eljan` or `aykhan`. - -## 3. Structural Changes Log - -| Date | Change | ADR | By | -| ---------- | -------------------------------------------------------- | ------- | ------ | -| 2026-05-22 | Initial repo layout defined | ADR-001 | Aykhan | -| 2026-05-22 | Generated web type shim ignored for git and lint | ADR-001 | Eljan | -| 2026-05-23 | API iTunes integration module path added | ADR-001 | Elshad | -| 2026-05-23 | API listening history module path added | ADR-001 | Aykhan | -| 2026-05-23 | `listening_history.idempotency_key` column added | ADR-001 | Aykhan | -| 2026-05-23 | API analytics module path added | ADR-001 | Aykhan | -| 2026-05-23 | Web `components/` and route-group folders added | ADR-001 | Rahila | -| 2026-05-23 | Web `zustand` dependency added (player store) | ADR-001 | Rahila | -| 2026-05-23 | `ingest_checkpoints` table added | ADR-001 | Eljan | -| 2026-05-23 | DB package `vitest` dependency added | ADR-001 | Eljan | -| 2026-05-23 | API admin module path added | ADR-001 | Aykhan | -| 2026-05-23 | DB seed module path added | ADR-001 | Eljan | -| 2026-05-23 | DB package `argon2` dependency added | ADR-001 | Eljan | -| 2026-05-23 | `users.deleted_at` column added (soft delete) | ADR-001 | Aykhan | -| 2026-05-23 | Web `react-hook-form` dependency added | ADR-001 | Aykhan | -| 2026-05-23 | Web `(auth)` route group + auth forms added | ADR-001 | Aykhan | -| 2026-05-23 | Web `catalog/` components + `(app)/catalog` route group | ADR-001 | Rahila | -| 2026-05-23 | Catalog pg_trgm and partial preview indexes added | ADR-001 | Aykhan | -| 2026-05-23 | Web `recharts` dependency added (analytics charts) | ADR-001 | Aykhan | -| 2026-05-23 | API `mpd-playlists` module + `/playlists` browsing | ADR-001 | Eljan | -| 2026-05-23 | API `user-playlists` module path added | ADR-001 | Elshad | -| 2026-05-24 | Web `(app)/community` route group + community pages | ADR-001 | Elshad | -| 2026-05-24 | Web `(app)/admin` route group + admin shell | ADR-001 | Aykhan | -| 2026-05-24 | `users.banned_at` column added | ADR-001 | Aykhan | -| 2026-05-24 | Workspace runtime entrypoints switched to `dist` | ADR-001 | Aykhan | -| 2026-05-24 | Root dev/test builds runtime workspace packages first | ADR-001 | Aykhan | -| 2026-05-24 | API config loads root `.env` from app workspace | ADR-001 | Aykhan | -| 2026-05-24 | API CORS wired to `ALLOWED_ORIGINS` config | ADR-001 | Aykhan | -| 2026-05-24 | `DESIGN.md` added at repo root (token specification) | ADR-002 | Aykhan | -| 2026-05-24 | ADR-002 added (design system, supersedes §3.8 / §3.20) | ADR-002 | Aykhan | -| 2026-05-24 | Web `lucide-react` dependency added (icon library) | ADR-002 | Rahila | -| 2026-05-24 | Web `tailwindcss-animate` dependency added | ADR-002 | Rahila | -| 2026-05-24 | Web `class-variance-authority` dependency added | ADR-002 | Rahila | -| 2026-05-24 | Web `clsx` + `tailwind-merge` dependencies added (cn) | ADR-002 | Rahila | -| 2026-05-24 | Web `@radix-ui/react-slot` dependency added | ADR-002 | Rahila | -| 2026-05-24 | Web fonts self-hosted via `next/font/google` | ADR-002 | Rahila | -| 2026-05-25 | Web `components/states/` folder added (state primitives) | ADR-002 | Rahila | -| 2026-05-24 | Web `components.json` added (shadcn config) | ADR-002 | Rahila | -| 2026-05-24 | Web `lib/fonts.ts` added | ADR-002 | Rahila | -| 2026-05-24 | Web `app/styleguide` route added | ADR-002 | Rahila | -| 2026-05-24 | Entity `image_url` columns added | ADR-002 | Aykhan | -| 2026-05-24 | DB `src/scripts` path added (media backfill) | ADR-002 | Aykhan | -| 2026-05-24 | Web iTunes image remotePatterns allowlist added | ADR-002 | Aykhan | -| 2026-05-24 | Web `components/layout` primitives path added | ADR-002 | Rahila | -| 2026-05-24 | Web `components/navigation` system path added | ADR-002 | Rahila | -| 2026-05-24 | Web `components/forms` system path added | ADR-002 | Aykhan | -| 2026-05-24 | `pnpm setup` + `scripts/setup.sh` one-shot bootstrap | ADR-001 | Aykhan | -| 2026-05-25 | Web `components/section/` path added | ADR-002 | Rahila | -| 2026-05-25 | Web `components/charts/` path added | ADR-002 | Aykhan | -| 2026-05-25 | Root JSX accessibility lint dependency added | ADR-002 | Aykhan | -| 2026-05-25 | Spotify artwork env vars and DB backfill script added | ADR-003 | Aykhan | - -(Append a row whenever the folder structure or repo layout changes.) - -## 4. Team Identities - -| Key | Name | Commit email | GitHub | -| -------- | ----------------- | ----------------------- | ----------- | -| `aykhan` | Aykhan Ahmadzada | ayxanx17@gmail.com | aykhan019 | -| `elshad` | Elshad Toklayev | etoklayev23@ku.edu.tr | endorphin13 | -| `rahila` | Rahila Dashdiyeva | rdashdiyeva23@ku.edu.tr | Rahila2707 | -| `eljan` | Eljan Mammadli | eljanmammadli@gmail.com | EljanM | - -Each member must have the email above on their GitHub account at https://github.com/settings/emails for commits to attribute correctly. Elshad and Rahila are reported to have created their GitHub accounts using their `@ku.edu.tr` addresses; verify after first push. - -## 5. Documents Map - -The canonical inventory. If a doc is not here, it should not exist. - -**Living (updated frequently):** - -- `HANDOFF.md` (this file), ground rules and current state. -- `CHECKLIST.md`, full task list with ownership and effort. -- `DESIGN.md`, design system token specification and visual rules. Authored in P6-M2 from the locked direction. - -**Operational:** - -- `README.md`, project overview and quickstart. -- `docs/onboarding.md`, clone-to-running in under 15 minutes. (TBD in Phase 4.) -- `docs/architecture.md`, narrative architecture summary with links to ADRs. (TBD in Phase 4.) -- `docs/api.md`, REST surface. (Generated from Swagger in Phase 4.) -- `docs/runbook.md`, recovery procedures. (TBD in Phase 4.) -- `docs/erd.dbml` and `docs/erd.png`, schema diagram. (TBD in Phase 4.) - -**Decisions (append only):** - -- `docs/adr/0001-tech-stack-and-foundation.md` -- `docs/adr/0002-design-system-and-token-layer.md` (drafted in P6-M2) -- `docs/adr/0003-spotify-artwork-source.md` -- `docs/adr/000N-...` future ADRs. - -**Design exploration (Phase 6):** - -- `docs/design/explorations.md`, five-app reference notes and three direction proposals from P6-M1. -- `docs/design/a11y-audit.md`, accessibility audit results from P6-M13. - -**Submission artifacts (built over the project):** - -- `report/final-report.md`, academic write-up, exported to PDF for submission. -- `report/sql-queries.md`, the six advanced SQL queries with sample I/O. -- `report/demo-script.md`, exact click-by-click demo walkthrough. -- `report/erd-explanation.md`, ER model writeup for the rubric. -- `report/slides.md` or Google Slides link, in-class presentation. - -**Repo hygiene:** - -- `.github/pull_request_template.md`, `CODEOWNERS`, `CONTRIBUTING.md`, `LICENSE`, `.env.example`. - -## 6. Quick Reference - -- "How do I run it locally?" -> `docs/onboarding.md` -- "Why did we pick X?" -> `docs/adr/` -- "What's the schema?" -> `packages/db/prisma/schema.prisma` and `docs/erd.png` -- "How do I commit?" -> `CONTRIBUTING.md` and `scripts/commit-as.sh` -- "What do I work on next?" -> `CHECKLIST.md` -- "What's the current status?" -> Section 2 of this file diff --git a/MERGE_PLAN.md b/MERGE_PLAN.md deleted file mode 100644 index bf910cf..0000000 --- a/MERGE_PLAN.md +++ /dev/null @@ -1,56 +0,0 @@ -# Statify P2 merged replacement package - -This zip contains **Next.js-safe merged files**. It is not a raw Claude Design copy. - -## Files that should be updated - -### Shared/new files - -- `apps/web/src/components/p2/P2Design.tsx` — new reusable P2 visual primitives. -- `apps/web/src/components/p2/index.ts` — barrel export. - -### Navigation/shell - -- `apps/web/src/components/navigation/AppShell.tsx` — update shell background/chrome, preserve `SectionProvider`, `AudioPlayer`, `PlayHistoryReporter`. -- `apps/web/src/components/navigation/TopNavigation.tsx` — visual update only, preserve `GlobalSearch`, `ThemeToggle`, `UserMenu`. -- `apps/web/src/components/navigation/SideNavigation.tsx` — visual update only, preserve existing nav item logic. - -### Route files - -- `apps/web/src/app/(app)/me/page.tsx` -- `apps/web/src/app/(app)/me/stats/page.tsx` -- `apps/web/src/app/(app)/me/stats/top-artists/page.tsx` -- `apps/web/src/app/(app)/me/stats/top-tracks/page.tsx` -- `apps/web/src/app/(app)/me/stats/heatmap/page.tsx` -- `apps/web/src/app/(app)/me/stats/trending/page.tsx` -- `apps/web/src/app/(app)/me/history/page.tsx` -- `apps/web/src/app/(app)/discover/page.tsx` -- `apps/web/src/app/(app)/explore/hidden-gems/page.tsx` -- `apps/web/src/app/(app)/me/playlists/page.tsx` -- `apps/web/src/app/(app)/community/playlists/page.tsx` -- `apps/web/src/app/(app)/catalog/page.tsx` -- `apps/web/src/app/(app)/catalog/tracks/page.tsx` - -## Files intentionally NOT included - -- `apps/web/src/app/globals.css` — the current app already has a large token system; replacing it would risk losing existing Tailwind tokens, motion utilities, state colors, and theme support. The package uses the existing token names instead. -- `apps/web/src/components/navigation/items.ts` — route data already matches the prototype closely; no functional change required. -- Login/signup/auth/middleware/API files — not touched. -- `apps/api/**` — not touched. - -## Why raw zip files cannot replace real files - -The Claude Design export contains prototype-only code: `window`, hash routing, `ReactDOM.createRoot`, direct demo data, and screen-local arrays. Existing Statify route files contain server components, `cookies()`, API clients, pagination, auth/session behavior, and metadata. Therefore, the correct strategy is merge visual structure into existing files, not direct replacement. - -## Install/integrate later - -From repo root: - -```bash -unzip -o statify-p2-merged-replacement.zip -d . -pnpm --filter @statify/shared run build -pnpm --filter @statify/web run typecheck -git diff --stat -``` - -Then commit only if typecheck passes. diff --git a/docs/adr/0001-tech-stack-and-foundation.md b/docs/adr/0001-tech-stack-and-foundation.md index 5de08ad..8a60ea6 100644 --- a/docs/adr/0001-tech-stack-and-foundation.md +++ b/docs/adr/0001-tech-stack-and-foundation.md @@ -11,27 +11,27 @@ Four developers, zero budget, university project that also serves as a portfolio ## 2. Decision Summary -| Area | Decision | Free tier confirmed | -| ----------------- | ---------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -| Backend | Node.js 22+ with TypeScript, NestJS 10 | Render free web service or Fly.io free allowance | -| ORM | Prisma 5 (Postgres provider) | OSS | -| Frontend | Next.js 15 (App Router), React 19, TypeScript | Vercel Hobby tier | -| Styling | Tailwind CSS 4, shadcn/ui, Radix primitives | OSS | -| Database | PostgreSQL 16 on Neon | 0.5 GB storage, 190 compute-hours/mo, branching | -| Auth | Email + password, Argon2id, JWT (access + refresh) in httpOnly cookies | Self-hosted | -| State (FE) | React Server Components first; TanStack Query for client; Zustand for ephemeral UI | OSS | -| Forms | React Hook Form, Zod | OSS | -| Validation | Zod schemas shared between FE and BE via `@statify/shared` | OSS | -| Email (future) | Resend free tier (100/day) | Yes | -| Monorepo | pnpm workspaces, single repo | Yes | -| Container (local) | Docker Compose for Postgres + adminer | Yes | -| CI/CD | GitHub Actions | Unlimited minutes on public repos | -| Hosting | Web on Vercel, API on Render free, DB on Neon | Yes; Render spins down after 15 min idle, warmed by cron-job.org | -| Error tracking | Sentry developer plan | 5k events/mo | -| Uptime | UptimeRobot | 50 monitors | -| Warm-up pings | cron-job.org | Unlimited 1-min jobs | -| Image/asset CDN | Use iTunes/Spotify image URLs directly | N/A | -| Audio | iTunes Search API `previewUrl` (30 s m4a) | Free, rate-limited | +| Area | Decision | Free tier confirmed | +| ----------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- | +| Backend | Node.js 22+ with TypeScript, NestJS 10 | Render free web service or Fly.io free allowance | +| ORM | Prisma 5 (Postgres provider) | OSS | +| Frontend | Next.js 15 (App Router), React 19, TypeScript | Vercel Hobby tier | +| Styling | Tailwind CSS 4, shadcn/ui, Radix primitives | OSS | +| Database | PostgreSQL 16 on Neon | 0.5 GB storage, 190 compute-hours/mo, branching | +| Auth | Email + password, Argon2id, JWT (access + refresh) in httpOnly cookies | Self-hosted | +| State (FE) | React Server Components first; fetch-based API client for client calls; Zustand for ephemeral UI | OSS | +| Forms | React Hook Form, Zod | OSS | +| Validation | Zod schemas shared between FE and BE via `@statify/shared` | OSS | +| Email (future) | Resend free tier (100/day) | Yes | +| Monorepo | pnpm workspaces, single repo | Yes | +| Container (local) | Docker Compose for Postgres + adminer | Yes | +| CI/CD | GitHub Actions | Unlimited minutes on public repos | +| Hosting | Web on Vercel, API on Render free, DB on Neon | Yes; Render spins down after 15 min idle, warmed by cron-job.org | +| Error tracking | Sentry developer plan | 5k events/mo | +| Uptime | UptimeRobot | 50 monitors | +| Warm-up pings | cron-job.org | Unlimited 1-min jobs | +| Image/asset CDN | Use iTunes/Spotify image URLs directly | N/A | +| Audio | iTunes Search API `previewUrl` (30 s m4a) | Free, rate-limited | ## 3. Detailed Decisions @@ -48,7 +48,7 @@ statify/ shared/ Zod schemas, DTO types, shared enums, error codes db/ Prisma schema, migrations, seed scripts, MPD ingestion CLI docs/ ADRs, ERD, API docs, onboarding, runbooks - scripts/ One-off shell scripts (commit attribution helper, etc.) + scripts/ One-off shell scripts (local setup, etc.) .github/ Actions workflows, PR template, CODEOWNERS report/ Academic submission artifacts ``` @@ -235,7 +235,7 @@ apps/web/src/ **State:** -- Server data: React Server Components for the initial render; TanStack Query for client-side mutations and revalidation. No Redux. +- Server data: React Server Components for the initial render; a thin fetch-based API client (`apps/web/src/lib/api-client.ts`) for client-side mutations and revalidation. No Redux. - UI state (modals, sidebars, current playing track): Zustand. One store per concern. - Form state: React Hook Form, validation via shared Zod schemas. @@ -310,7 +310,7 @@ Branch model: - Feature branches: `feat/`, `fix/`, `chore/`. Branched from `dev`. - Hotfixes: branched from `main`, merged into `main` and back-merged to `dev`. -**Commit attribution:** every commit goes through `scripts/commit-as.sh `. The wrapper sets `GIT_AUTHOR_*` and `GIT_COMMITTER_*` environment variables from `scripts/.authors` for that one commit. No global git config is modified. Aykhan reviews and merges PRs. +**Reviews:** changes land via pull request with CI green; a maintainer reviews and merges. **PR template:** `.github/pull_request_template.md`, includes summary, screenshots (FE), DB migration noted, test plan, breaking changes. @@ -376,7 +376,7 @@ Positive: - Type safety from DB to UI; runtime validation matches compile-time types. - Every architectural choice has a free-tier exit. - The 12-table schema and 6 pre-planned advanced queries directly address the rubric. -- The `scripts/commit-as.sh` workflow keeps attribution clean. +- Conventional Commits keep history readable and enable changelog generation. Negative: diff --git a/docs/adr/0002-design-system-and-token-layer.md b/docs/adr/0002-design-system-and-token-layer.md index ebb9df3..7943d43 100644 --- a/docs/adr/0002-design-system-and-token-layer.md +++ b/docs/adr/0002-design-system-and-token-layer.md @@ -61,7 +61,7 @@ In Phase 6 (frontend redesign) the project commits to a visually energetic, mult ## 5. Migration - `apps/web/src/app/globals.css` is rewritten in P6-M3 against the token surface summary in `DESIGN.md` §10. The existing 4-color + 5-radius + 2-font + 3-shadow set is replaced wholesale. -- New deps in `apps/web/package.json` (P6-M3): `lucide-react`, `tailwindcss-animate`, `@radix-ui/*` (set determined by which shadcn primitives the milestone needs), `next/font` is built in. Each addition gets a row in `HANDOFF.md` §3 Structural Changes Log. +- New deps in `apps/web/package.json` (P6-M3): `lucide-react`, `tailwindcss-animate`, `@radix-ui/*` (set determined by which shadcn primitives the milestone needs), `next/font` is built in. Each addition is recorded in the PR that introduces it. - Existing components under `apps/web/src/components/{catalog,playlists,stats,player,admin,history,auth,ui}/` are destructively replaced milestone by milestone (P6-M5 through P6-M11). No backwards-compatibility shim. - `packages/db/prisma/schema.prisma` gains `image_url String?` on `Artist`, `Album`, `Track` in P6-M4. Migration name: `entity_media`. Backfill script at `packages/db/src/scripts/backfill-media.ts` populates existing rows. - `apps/web/next.config.js` `images.remotePatterns` allowlists `is*-ssl.mzstatic.com` (iTunes/Apple artwork hosts) in P6-M4. Recorded in the Structural Changes Log. diff --git a/docs/design/explorations.md b/docs/design/explorations.md index 9c8be11..3d0f3f8 100644 --- a/docs/design/explorations.md +++ b/docs/design/explorations.md @@ -127,4 +127,4 @@ All three are energetic, multi-hue, color-confident. All three commit to real al **Rationale (one line):** Section-as-color makes hue a structural element rather than decoration, which commits hardest to the multi-hue, color-confident posture, while leaving album art as the loudest variable visual element inside each section's room. **Decided by:** Aykhan, 2026-05-24. -Recorded in HANDOFF.md Section 2. Next session opens P6-M2 (Author DESIGN.md from this direction). +Next milestone: author DESIGN.md from this direction. diff --git a/integration/README.md b/integration/README.md deleted file mode 100644 index 211e05c..0000000 --- a/integration/README.md +++ /dev/null @@ -1,172 +0,0 @@ -# Statify · Visual integration package - -Two complete visual upgrades in this drop, sharing a logo + visual language: - -1. **Auth route** (`(auth)/login`, `(auth)/signup`) — split shell with brand - panel + animated background, your existing forms untouched. -2. **Marketing landing page** (`(marketing)/page.tsx`) — full redesign with - hero, stats strip, features, live-SQL stack panel, demo path, about, CTA. - -Both routes share the same `StatifyLogo` mark and the same merged -spectrum + mosaic background flavour, so the brand reads continuously -from the landing page through sign-up. - ---- - -## Files - -### Shared - -| File | Action | -| ----------------------------------------------- | ------- | -| `apps/web/public/brand/statify-logo.svg` | **new** | -| `apps/web/src/components/brand/StatifyLogo.tsx` | **new** | - -### Auth (`(auth)` route) - -| File | Action | -| --------------------------------------------------------------- | --------------------------------- | -| `apps/web/src/components/auth/AuthBackground.tsx` | **new** — constellation (default) | -| `apps/web/src/components/auth/AuthBackgroundSpectrumMosaic.tsx` | **new** — merged variant | -| `apps/web/src/components/auth/AuthBrandPanel.tsx` | **new** | -| `apps/web/src/app/(auth)/layout.tsx` | **replace** | -| `apps/web/src/app/(auth)/login/page.tsx` | leave alone | -| `apps/web/src/app/(auth)/signup/page.tsx` | leave alone | -| `apps/web/src/components/auth/LoginForm.tsx`, `SignupForm.tsx` | leave alone | - -### Marketing (`/` landing page) - -| File | Action | -| ------------------------------------------------------ | --------------------------------------------------- | -| `apps/web/src/components/marketing/HeroBackground.tsx` | **new** | -| `apps/web/src/components/marketing/HeroPreview.tsx` | **new** | -| `apps/web/src/components/marketing/Hero.tsx` | **new** | -| `apps/web/src/components/marketing/StatsStrip.tsx` | **new** | -| `apps/web/src/components/marketing/Features.tsx` | **new** | -| `apps/web/src/components/marketing/Stack.tsx` | **new** | -| `apps/web/src/components/marketing/Demo.tsx` | **new** | -| `apps/web/src/components/marketing/About.tsx` | **new** | -| `apps/web/src/components/marketing/CTA.tsx` | **new** | -| `apps/web/src/app/(marketing)/page.tsx` | **replace** | -| `apps/web/src/app/(marketing)/layout.tsx` | leave alone — your `
` still sits at the top | - -The two routes are connected by: - -- The same `StatifyLogo` component (anywhere a brand mark appears). -- Hero buttons `Start listening` → `/signup` and `Log in` → `/login` (the existing route paths). -- The CTA section's `Create your account` + `Log in` buttons (same routes). -- `data-section-hue="indigo"` on both root containers so focus rings, - hover tints, and accent text colors render the same identity. - ---- - -## Integration steps - -```bash -# From repo root -unzip integration.zip # if downloaded as zip -# (or: drag the integration/ folder into the repo root) - -# Copy shared brand assets -mkdir -p apps/web/public/brand -cp integration/apps/web/public/brand/statify-logo.svg \ - apps/web/public/brand/ - -mkdir -p apps/web/src/components/brand -cp integration/apps/web/src/components/brand/StatifyLogo.tsx \ - apps/web/src/components/brand/ - -# Auth route -cp integration/apps/web/src/components/auth/AuthBackground.tsx \ - integration/apps/web/src/components/auth/AuthBackgroundSpectrumMosaic.tsx \ - integration/apps/web/src/components/auth/AuthBrandPanel.tsx \ - apps/web/src/components/auth/ -cp integration/apps/web/src/app/\(auth\)/layout.tsx \ - apps/web/src/app/\(auth\)/layout.tsx - -# Marketing landing -mkdir -p apps/web/src/components/marketing -cp integration/apps/web/src/components/marketing/*.tsx \ - apps/web/src/components/marketing/ -cp integration/apps/web/src/app/\(marketing\)/page.tsx \ - apps/web/src/app/\(marketing\)/page.tsx - -# Verify -pnpm --filter @statify/web lint -pnpm --filter @statify/web typecheck -pnpm --filter @statify/web dev -# Visit: -# http://localhost:3000/ → new landing page -# http://localhost:3000/login → new auth shell -# http://localhost:3000/signup → new auth shell -``` - -Open one PR per route or one combined PR — both are fully self-contained. - ---- - -## Swapping the auth background - -```ts -// apps/web/src/app/(auth)/layout.tsx - -// Constellation (default — calm, technical): -import { AuthBackground } from '@/components/auth/AuthBackground'; - -// Merged spectrum + album mosaic (matches the landing page hero): -import { AuthBackgroundSpectrumMosaic as AuthBackground } from '@/components/auth/AuthBackgroundSpectrumMosaic'; -``` - -Recommended: ship the **merged variant** on the auth route so the brand reads -continuously from the landing page hero through to sign-up. - ---- - -## Design decisions worth knowing - -1. **No hex anywhere.** Everything routes through your existing tokens - (`--color-indigo-500`, `--surface-page`, `--fg-on-block`, …). Light/dark - modes and any future per-route `data-section-hue` overrides keep working. - -2. **No new dependencies.** Pure React + Tailwind + token vars + lucide - (already in your `package.json`). No framer-motion, no canvas libs. - -3. **SSR-safe.** Constellation and tile-mosaic generators are deterministic - so server and client render identical markup. No hydration warnings. - -4. **`prefers-reduced-motion` respected.** Animated tiles, bars, and glow - blobs are gated behind `motion-reduce:hidden` / `motion-reduce:!animate-none`. - -5. **Card stays where it is.** Your auth `` from `Card.tsx` already - uses `bg-surface-raised` — on the dark backdrop it reads as a floating - panel without any rework. The `LoginForm` / `SignupForm` components, - their zod resolvers, and `loginUser` / `registerUser` are untouched. - -6. **Marketing nav anchors preserved.** The new landing keeps `#home`, - `#features`, `#stack`, `#demo`, `#about` so the layout's existing nav - keeps working unchanged. - -7. **No marketing layout changes needed.** Your `Header` component still - sits at the top of the marketing layout. The new hero starts immediately - below it on a dark surface — works fine with a light `Header`. If you'd - prefer the header to float transparently over the hero, that's a small - `Header.tsx` change, ping me. - ---- - -## What I did NOT touch - -- `apps/web/src/components/forms/*` — your Field/Input/SubmitButton system. -- `apps/web/src/components/auth/LoginForm.tsx`, `SignupForm.tsx` — react-hook-form, zod, API client. -- `apps/web/src/app/(auth)/login/page.tsx`, `signup/page.tsx` — Card chrome + cross-mode links. -- `apps/web/src/app/(marketing)/layout.tsx` — your existing `Header` is reused. -- `apps/web/src/app/globals.css` — no new tokens needed. - -If anything trips your strict ESLint config (`--max-warnings=0`), share the -rule names and I'll adjust. Likely candidates: - -- `import/order` — alphabetise the imports in each file if your config - enforces a particular order. -- `@typescript-eslint/no-misused-promises` — none expected; everything is - synchronous JSX. -- `jsx-a11y/*` — all interactive elements have proper roles + `aria-label`s. diff --git a/integration/apps/web/public/brand/statify-logo.svg b/integration/apps/web/public/brand/statify-logo.svg deleted file mode 100644 index 6074e50..0000000 --- a/integration/apps/web/public/brand/statify-logo.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/integration/apps/web/src/app/(auth)/layout.tsx b/integration/apps/web/src/app/(auth)/layout.tsx deleted file mode 100644 index 825e8fb..0000000 --- a/integration/apps/web/src/app/(auth)/layout.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { ReactNode } from 'react'; -import { BrandMarkLink } from '@/components/ui/BrandMarkLink'; -import { ThemeToggle } from '@/components/ui/ThemeToggle'; -import { AuthBackground } from '@/components/auth/AuthBackground'; -import { AuthBrandPanel } from '@/components/auth/AuthBrandPanel'; - -/** - * Auth shell. - * - * Layout: - * ┌──────────────────────────────────────────────┐ - * │ header (mobile only — desktop has brand panel) │ - * ├──────────────────┬───────────────────────────┤ - * │ AuthBrandPanel │ {children} │ - * │ (lg+, on bg) │ (Card from page.tsx) │ - * └──────────────────┴───────────────────────────┘ - * - * The constellation background spans the full viewport behind both halves. - * `data-section-hue="indigo"` keeps focus rings, the brand-panel accent, - * and the form's submit button on the same indigo identity the rest of - * the auth route already uses. - */ -export default function AuthLayout({ children }: { children: ReactNode }) { - return ( -
- - - {/* Mobile/tablet header (lg breakpoint hides this — brand panel takes over). */} -
- - -
- - {/* Theme toggle anchored top-right at lg+ so the brand panel stays clean. */} -
- -
- -
- - -
-
{children}
-
-
-
- ); -} diff --git a/integration/apps/web/src/app/(marketing)/page.tsx b/integration/apps/web/src/app/(marketing)/page.tsx deleted file mode 100644 index e973a9a..0000000 --- a/integration/apps/web/src/app/(marketing)/page.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { Metadata } from 'next'; -import { Hero } from '@/components/marketing/Hero'; -import { StatsStrip } from '@/components/marketing/StatsStrip'; -import { Features } from '@/components/marketing/Features'; -import { Stack } from '@/components/marketing/Stack'; -import { Demo } from '@/components/marketing/Demo'; -import { About } from '@/components/marketing/About'; -import { CTA } from '@/components/marketing/CTA'; -import { Footer } from '@/components/marketing/Footer'; - -export const metadata: Metadata = { - title: 'Statify: Music streaming analytics', - description: - 'Statify turns a playlist-scale catalog and your own listening history into top artists, trend deltas, heatmaps, and discovery paths.', -}; - -/** - * Marketing landing page (`/`). - * - * Composition only - every section is a self-contained component in - * `@/components/marketing/`. Order matches the layout's nav anchors: - * - * /#home → Hero - * /#features → Features - * /#stack → Stack - * /#demo → Demo - * /#about → About - * - * `data-section-hue="indigo"` keeps focus rings + section accents - * matching the rest of the app's auth/marketing identity. - */ -export default function HomePage() { - return ( -
- - - - - - - -
-
- ); -} diff --git a/integration/apps/web/src/components/auth/AuthBackground.tsx b/integration/apps/web/src/components/auth/AuthBackground.tsx deleted file mode 100644 index d5cd1cc..0000000 --- a/integration/apps/web/src/components/auth/AuthBackground.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { useMemo } from 'react'; - -interface AuthBackgroundProps { - /** Pseudo-random seed; same seed = same star pattern across renders. */ - seed?: number; - /** Star count. */ - density?: number; - /** Maximum distance (0–100, unitless on the 100×100 viewBox) below which - * two stars get connected by an edge. */ - linkDistance?: number; -} - -/** - * Constellation backdrop for the auth route. - * - * - Renders as an absolutely-positioned layer (consumer wraps it). - * - Pure SVG; deterministic via seeded RNG so SSR and CSR agree (no hydration - * mismatch) and so the pattern is stable between visits. - * - Colors reference design tokens — adapts to indigo/violet/teal section - * hues and works in both color schemes. - * - * Swap suggestions for other directions explored in the design canvas: - * - "Ribbons": replace contents with cubic SVG paths tinted with - * --color-violet-500 / --color-indigo-500 / --color-teal-500. - * - "Spectrum": array of s along the bottom with `animate-pulse-eq`. - * - "Mosaic": rotated grid of album-tile divs. - */ -export function AuthBackground({ - seed = 19, - density = 110, - linkDistance = 11, -}: AuthBackgroundProps) { - const { nodes, edges } = useMemo( - () => buildConstellation(seed, density, linkDistance), - [seed, density, linkDistance], - ); - - return ( -
- {/* Base gradient — dark in either theme so the mark and form pop. */} -
- - {/* Soft color glow blobs. */} -
-
-
- - {/* Constellation. */} - - {edges.map((edge, i) => { - const a = nodes[edge.a]!; - const b = nodes[edge.b]!; - return ( - - ); - })} - {nodes.map((node, i) => ( - - ))} - - - {/* Subtle top/bottom fade for legibility against header/footer chrome. */} -
-
- ); -} - -// ── deterministic constellation generator ─────────────────────────────────── - -interface ConstellationNode { - x: number; - y: number; - r: number; - color: string; - opacity: number; -} - -interface ConstellationEdge { - a: number; - b: number; - opacity: number; -} - -function buildConstellation(seed: number, density: number, linkDistance: number) { - const rng = mulberry32(seed); - const palette = [ - 'var(--color-indigo-200)', - 'var(--color-violet-200)', - 'var(--color-teal-200)', - 'var(--color-azure-200)', - ]; - - const nodes: ConstellationNode[] = []; - for (let i = 0; i < density; i += 1) { - nodes.push({ - x: rng() * 100, - y: rng() * 100, - r: 0.15 + rng() * 0.5, - color: palette[Math.floor(rng() * palette.length)] ?? palette[0]!, - opacity: 0.45 + rng() * 0.5, - }); - } - - const edges: ConstellationEdge[] = []; - for (let i = 0; i < nodes.length; i += 1) { - for (let j = i + 1; j < nodes.length; j += 1) { - const a = nodes[i]!; - const b = nodes[j]!; - const dx = a.x - b.x; - const dy = a.y - b.y; - const distance = Math.sqrt(dx * dx + dy * dy); - if (distance < linkDistance) { - edges.push({ - a: i, - b: j, - opacity: Math.max(0, 0.45 - distance * 0.035), - }); - } - } - } - - return { nodes, edges }; -} - -/** Tiny seeded RNG so SSR + CSR produce identical patterns. */ -function mulberry32(seed: number) { - let state = seed >>> 0; - return () => { - state = (state + 0x6d2b79f5) >>> 0; - let t = state; - t = Math.imul(t ^ (t >>> 15), t | 1); - t ^= t + Math.imul(t ^ (t >>> 7), t | 61); - return ((t ^ (t >>> 14)) >>> 0) / 4294967296; - }; -} diff --git a/integration/apps/web/src/components/auth/AuthBackgroundSpectrumMosaic.tsx b/integration/apps/web/src/components/auth/AuthBackgroundSpectrumMosaic.tsx deleted file mode 100644 index c104abb..0000000 --- a/integration/apps/web/src/components/auth/AuthBackgroundSpectrumMosaic.tsx +++ /dev/null @@ -1,187 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; - -/** - * Alternate auth backdrop: tilted album-tile mosaic behind, animated - * equalizer bars at the foot of the canvas, shared violet/indigo/teal - * glow blobs. Drop-in replacement for `AuthBackground` — same outer - * signature, no props required. - * - * To use: - * // in apps/web/src/app/(auth)/layout.tsx - * import { AuthBackgroundSpectrumMosaic as AuthBackground } - * from '@/components/auth/AuthBackgroundSpectrumMosaic'; - * - * 'use client' is required: the animated bars + tiles use CSS animations - * that run continuously and the component memoises a deterministic tile - * grid. - */ -export function AuthBackgroundSpectrumMosaic() { - const tiles = useMemo(buildTiles, []); - const bars = useMemo(buildBars, []); - - return ( -
- {/* Base gradient. Dark in either theme so the form pops. */} -
- - {/* Soft color glows. */} -
-
-
- - {/* Tilted album mosaic. Rotated/scaled so the tile grid bleeds off - every edge — no straight horizon line. */} -
- {tiles.map((tile, i) => ( -
- {/* Tiny waveform glyph inside each tile. */} -
- {[6, 10, 7, 13, 8, 11, 6, 9, 7].map((h, j) => ( -
- ))} -
-
- ))} -
- - {/* Mid-vignette so the spectrum bars stand out + form stays legible. */} -
- - {/* Spectrum bars across the bottom. */} -
- {bars.map((bar, i) => ( -
- ))} -
- - -
- ); -} - -// ─── deterministic generators ──────────────────────────────────────────── - -interface Tile { - x: number; - y: number; - start: string; - end: string; - delay: number; -} - -interface Bar { - amp: number; - delay: number; -} - -function buildTiles(): Tile[] { - // Use token vars so light/dark mode + section hues swap correctly. - const palettes: Array<[string, string]> = [ - ['var(--color-violet-500)', 'var(--color-indigo-700)'], - ['var(--color-teal-500)', 'var(--color-indigo-500)'], - ['var(--color-magenta-500)', 'var(--color-violet-700)'], - ['var(--color-green-500)', 'var(--color-teal-500)'], - ['var(--color-violet-200)', 'var(--color-indigo-500)'], - ['var(--color-azure-400)', 'var(--color-violet-500)'], - ['var(--color-teal-200)', 'var(--color-teal-500)'], - ['var(--color-indigo-200)', 'var(--color-indigo-700)'], - ]; - - const tiles: Tile[] = []; - const rows = 6; - const cols = 8; - for (let r = 0; r < rows; r += 1) { - for (let c = 0; c < cols; c += 1) { - const idx = (r * cols + c) % palettes.length; - const offset = (r % 2) * 4; - const palette = palettes[idx]!; - tiles.push({ - x: c * 13 + offset - 5, - y: r * 18 - 5, - start: palette[0], - end: palette[1], - delay: ((r + c) % 5) * 0.6, - }); - } - } - return tiles; -} - -function buildBars(): Bar[] { - return Array.from({ length: 80 }, (_, i) => ({ - amp: Math.sin(i * 1.7) * 0.5 + 0.5, - delay: (i % 8) * 0.12, - })); -} diff --git a/integration/apps/web/src/components/auth/AuthBrandPanel.tsx b/integration/apps/web/src/components/auth/AuthBrandPanel.tsx deleted file mode 100644 index 128be31..0000000 --- a/integration/apps/web/src/components/auth/AuthBrandPanel.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { StatifyLogo } from '@/components/brand/StatifyLogo'; - -/** - * Brand panel rendered on the left half of the auth shell at the lg+ - * breakpoint. Below lg it collapses (the AuthShell wraps the form to - * full width and shows the wordmark in the header instead). - * - * All copy is editorial placeholder — replace with whatever ships in - * the marketing copy doc. - */ -export function AuthBrandPanel() { - return ( - - ); -} - -interface BrandStatProps { - value: string; - label: string; -} - -function BrandStat({ value, label }: BrandStatProps) { - return ( -
-
{label}
-
{value}
-
- ); -} diff --git a/integration/apps/web/src/components/brand/StatifyLogo.tsx b/integration/apps/web/src/components/brand/StatifyLogo.tsx deleted file mode 100644 index 997e0f5..0000000 --- a/integration/apps/web/src/components/brand/StatifyLogo.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { SVGProps } from 'react'; -import { cn } from '@/lib/utils/cn'; - -interface StatifyLogoProps extends Omit, 'children'> { - /** Square edge length in pixels. Defaults to 40. */ - size?: number; - /** - * When true, omits the solid rounded background so the mark sits - * directly on a parent surface (e.g. inline with a wordmark). - */ - bare?: boolean; - /** Optional title for AT; defaults to "Statify". */ - title?: string; -} - -/** - * The Statify S-Wave mark. The letter S is drawn as a stroked waveform, - * tinted across the section accent (purple → indigo → teal), with audio - * amplitude ticks slotted into the curves. - * - * Colors reference the raw palette tokens defined in globals.css so the - * mark reads correctly in both light and dark themes. - */ -export function StatifyLogo({ - size = 40, - bare = false, - title = 'Statify', - className, - ...props -}: StatifyLogoProps) { - const gradientId = `statify-sw-${bare ? 'bare' : 'tile'}`; - - return ( - - {title} - - - - - - - - - {!bare && } - - - - - - - - - - - - - ); -} diff --git a/integration/apps/web/src/components/marketing/About.tsx b/integration/apps/web/src/components/marketing/About.tsx deleted file mode 100644 index ca3913e..0000000 --- a/integration/apps/web/src/components/marketing/About.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import Link from 'next/link'; -import { Container } from '@/components/layout'; - -const TEAM = [ - { name: 'Aykhan Ahmadzada', role: 'Project lead' }, - { name: 'Elshad Toklayev', role: 'Backend engineer' }, - { name: 'Rahila Dashdiyeva', role: 'Frontend engineer' }, - { name: 'Eljan Mammadli', role: 'Data & analytics' }, -] as const; - -const AVATAR_PALETTE: Array<[string, string]> = [ - ['var(--color-violet-500)', 'var(--color-indigo-700)'], - ['var(--color-indigo-500)', 'var(--color-violet-700)'], - ['var(--color-teal-500)', 'var(--color-green-700)'], - ['var(--color-magenta-500)', 'var(--color-violet-700)'], -]; - -export function About() { - return ( -
- -
-

- Project scope -

-

- Built around the -
- Million Playlist Dataset. -

-

- Statify combines normalized playlist data, account sessions, listening history, playlist - creation, admin operations, and SQL analytics into one demoable product — built as a - graduation project at StepIT Academy. -

- - View on GitHub → - -
- -
-

- Team -

-
- {TEAM.map((member, i) => { - const initials = member.name - .split(' ') - .map((word) => word[0]) - .join('') - .slice(0, 2); - const palette = AVATAR_PALETTE[i] ?? AVATAR_PALETTE[0]!; - return ( -
-
- {initials} -
-
-
{member.name}
-
{member.role}
-
-
- ); - })} -
-
-
-
- ); -} diff --git a/integration/apps/web/src/components/marketing/CTA.tsx b/integration/apps/web/src/components/marketing/CTA.tsx deleted file mode 100644 index 22a6b18..0000000 --- a/integration/apps/web/src/components/marketing/CTA.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import Link from 'next/link'; -import { Container } from '@/components/layout'; - -/** - * Closing CTA strip. Dark band with two glow blobs and centered copy + - * primary/secondary CTAs. Lives between About and the marketing footer. - */ -export function CTA() { - return ( -
-
-
- - -

- Start tracking your sound. -

-

- Free during the demo period. Connect to start logging listening events and watch your - stats build up in real time. -

-
- - Create your account → - - - Log in - -
-
-
- ); -} diff --git a/integration/apps/web/src/components/marketing/Demo.tsx b/integration/apps/web/src/components/marketing/Demo.tsx deleted file mode 100644 index 0a01766..0000000 --- a/integration/apps/web/src/components/marketing/Demo.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { Container } from '@/components/layout'; -import { ArrowRight } from 'lucide-react'; -import { Icon } from '@/components/ui/Icon'; - -const STEPS = [ - { - n: '01', - title: 'Search the catalog', - description: 'Tracks, artists, albums, playlists — all from one query box.', - Preview: CatalogPreview, - }, - { - n: '02', - title: 'Play an iTunes preview', - description: '30 seconds, recorded as an idempotent listening event.', - Preview: PlayerPreview, - }, - { - n: '03', - title: 'Review history & stats', - description: 'Top artists, hour-of-day heatmaps, and hidden gems.', - Preview: StatsPreview, - }, -] as const; - -export function Demo() { - return ( -
- -
-

- Demo path -

-

- A short route through -
- the working app. -

-
- -
- {STEPS.map(({ n, title, description, Preview }, i) => ( -
-
-
- -
-
-
- {n} - -
-

- {title} -

-

{description}

-
-
- {i < STEPS.length - 1 && ( -
- -
- )} -
- ))} -
-
-
- ); -} - -// ─── per-step preview vignettes ────────────────────────────────────── - -function CatalogPreview() { - const results = [ - 'Phoebe Bridgers · artist', - 'Motion Sickness · track', - 'Stranger in the Alps · album', - ]; - return ( -
-
- 🔍 - phoebe - -
- {results.map((label) => ( -
-
- {label} -
- ))} - -
- ); -} - -function PlayerPreview() { - return ( -
-
-
- {[12, 22, 16, 26].map((h, i) => ( -
- ))} -
-
-
-
-
-
0:11 / 0:30
-
- ); -} - -function StatsPreview() { - const bars = [40, 28, 50, 36, 62, 44, 70, 52, 80, 60, 48, 56]; - return ( -
-
- {bars.map((h, i) => ( -
- ))} -
-
- {['M', 'T', 'W', 'T', 'F', 'S', 'S'].map((d, i) => ( - {d} - ))} -
-
- 23.4h this week · +12% -
-
- ); -} diff --git a/integration/apps/web/src/components/marketing/Features.tsx b/integration/apps/web/src/components/marketing/Features.tsx deleted file mode 100644 index 1fac313..0000000 --- a/integration/apps/web/src/components/marketing/Features.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import { Container } from '@/components/layout'; - -const CAPABILITIES = [ - { - title: 'Catalog intelligence', - description: - 'Search across tracks, artists, albums, and playlists through normalized MPD entities — relational joins, not bag-of-words.', - Illustration: CatalogIllustration, - }, - { - title: 'Preview listening', - description: - 'Play iTunes 30-second previews inline and record every event with idempotent writes so the history is always trustworthy.', - Illustration: PreviewIllustration, - }, - { - title: 'SQL-backed stats', - description: - 'Turn raw listening into top artists, trend deltas, hour-of-day heatmaps, hidden gems, and discovery paths — all hand-written queries.', - Illustration: StatsIllustration, - }, - { - title: 'Playlist lab', - description: - 'Create collections, reorder tracks, and compare public playlists side-by-side. Every action round-trips through the API.', - Illustration: PlaylistIllustration, - }, -] as const; - -export function Features() { - return ( -
- -
-

- Designed for the demo loop -

-

- Everything the app does is -
- one click away. -

-
- -
- {CAPABILITIES.map(({ title, description, Illustration }) => ( -
-
- -
-
-

{title}

-

{description}

-
-
- ))} -
-
-
- ); -} - -// ─── illustrations: same SVG vocabulary as the S-Wave logo ────────── - -function CatalogIllustration() { - return ( - - - - - - - - - - ); -} - -function PreviewIllustration() { - return ( - - - - - {[2, 6, 4, 8, 5, 9, 3].map((h, i) => ( - - ))} - {[3, 7, 4, 9, 5, 8, 4].map((h, i) => ( - - ))} - - ); -} - -function StatsIllustration() { - return ( - - - - - - - - {[14, 34, 54, 74, 88].map((x, i) => ( - - ))} - - ); -} - -function PlaylistIllustration() { - return ( - - - - - - - - - - - ); -} diff --git a/integration/apps/web/src/components/marketing/Footer.tsx b/integration/apps/web/src/components/marketing/Footer.tsx deleted file mode 100644 index 21610da..0000000 --- a/integration/apps/web/src/components/marketing/Footer.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import Link from 'next/link'; -import { Container } from '@/components/layout'; -import { StatifyLogo } from '@/components/brand/StatifyLogo'; - -const COLUMNS = [ - { - heading: 'Product', - links: [ - { label: 'Features', href: '/#features' }, - { label: 'Stack', href: '/#stack' }, - { label: 'Demo path', href: '/#demo' }, - ], - }, - { - heading: 'Team', - links: [ - { label: 'About', href: '/#about' }, - { label: 'GitHub', href: 'https://github.com/aykhan019/statify', external: true }, - { - label: 'License', - href: 'https://github.com/aykhan019/statify/blob/main/LICENSE', - external: true, - }, - ], - }, - { - heading: 'Account', - links: [ - { label: 'Log in', href: '/login' }, - { label: 'Create account', href: '/signup' }, - ], - }, -] as const; - -/** - * Marketing footer. Lives at the bottom of `/` (and any other page that - * imports it). Brand block on the left, three link columns on the right, - * legal line under both. - */ -export function Footer() { - return ( -
- -
- {/* Brand block */} -
- - - Statify - -

- Music streaming analytics built on the Spotify Million Playlist Dataset. Previews from - iTunes, artwork from Spotify, analytics from hand-written SQL. -

-
- - {/* Link columns */} -
- {COLUMNS.map((col) => ( -
-

- {col.heading} -

-
    - {col.links.map((link) => ( -
  • - - {link.label} - -
  • - ))} -
-
- ))} -
-
- -
- © {new Date().getFullYear()} Statify · MIT License - Built at StepIT Academy -
-
-
- ); -} diff --git a/integration/apps/web/src/components/marketing/Hero.tsx b/integration/apps/web/src/components/marketing/Hero.tsx deleted file mode 100644 index 77d479d..0000000 --- a/integration/apps/web/src/components/marketing/Hero.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import Link from 'next/link'; -import { ArrowRight, Play } from 'lucide-react'; -import { Container } from '@/components/layout'; -import { Icon } from '@/components/ui/Icon'; -import { HeroBackground } from './HeroBackground'; -import { HeroPreview } from './HeroPreview'; - -/** - * Marketing hero. Dark backdrop (HeroBackground) under a 2-column - * layout: copy on the left, floating glass preview cards on the right. - * - * Lives under the marketing layout's
, which is opaque. Set - * a tall top padding so the eye reads "headline first, header second". - */ -export function Hero() { - return ( -
- - - -
-

- The story behind -
- - your listening. - -

- -

- Statify turns a playlist-scale catalog and your own listening history into top artists, - trend deltas, heatmaps, and discovery paths — backed by hand-written SQL on the Spotify - Million Playlist Dataset. -

- -
- - Start listening - - - - - - - Watch the demo - -
- -
- - - - - - - -
-
- - -
-
- ); -} - -function HeroStat({ n, l }: { n: string; l: string }) { - return ( -
-
{l}
-
{n}
-

{l}

-
- ); -} - -function Separator() { - return
; -} diff --git a/integration/apps/web/src/components/marketing/HeroBackground.tsx b/integration/apps/web/src/components/marketing/HeroBackground.tsx deleted file mode 100644 index f9a48a8..0000000 --- a/integration/apps/web/src/components/marketing/HeroBackground.tsx +++ /dev/null @@ -1,173 +0,0 @@ -'use client'; - -import { useMemo } from 'react'; - -/** - * Hero backdrop for the marketing landing page. - * - * Visually identical to `AuthBackgroundSpectrumMosaic` (tilted album mosaic - * + animated EQ bars + glow blobs) but with two differences for marketing - * use: - * 1. Wider tile grid (10 cols) to fill 1280px hero width without seams. - * 2. Bottom fade-to-`--surface-page` so the hero blends into the next - * light-themed section. - * - * Drop-in: render as a child of any `relative` parent and it will fill it. - */ -export function HeroBackground() { - const tiles = useMemo(buildTiles, []); - const bars = useMemo(buildBars, []); - - return ( -
- {/* Base gradient. */} -
- - {/* Glow blobs. */} -
-
-
- - {/* Album tile mosaic, rotated to break the horizon. */} -
- {tiles.map((tile, i) => ( -
-
- {[6, 10, 7, 13, 8, 11, 6, 9].map((h, j) => ( -
- ))} -
-
- ))} -
- - {/* Mid-vignette to calm the centre so headline + preview cards pop. */} -
- - {/* Spectrum bars at the foot. */} -
- {bars.map((bar, i) => ( -
- ))} -
- - {/* Bottom fade so the next section blends in. */} -
- - -
- ); -} - -interface Tile { - x: number; - y: number; - start: string; - end: string; - delay: number; -} -interface Bar { - amp: number; - delay: number; -} - -function buildTiles(): Tile[] { - const palettes: Array<[string, string]> = [ - ['var(--color-violet-500)', 'var(--color-indigo-700)'], - ['var(--color-teal-500)', 'var(--color-indigo-500)'], - ['var(--color-magenta-500)', 'var(--color-violet-700)'], - ['var(--color-green-500)', 'var(--color-teal-500)'], - ['var(--color-violet-200)', 'var(--color-indigo-500)'], - ['var(--color-azure-400)', 'var(--color-violet-500)'], - ['var(--color-teal-200)', 'var(--color-teal-500)'], - ['var(--color-indigo-200)', 'var(--color-indigo-700)'], - ]; - const out: Tile[] = []; - for (let r = 0; r < 6; r += 1) { - for (let c = 0; c < 10; c += 1) { - const palette = palettes[(r * 10 + c) % palettes.length]!; - out.push({ - x: c * 10 + (r % 2) * 3 - 3, - y: r * 17 - 5, - start: palette[0], - end: palette[1], - delay: ((r + c) % 5) * 0.6, - }); - } - } - return out; -} - -function buildBars(): Bar[] { - return Array.from({ length: 96 }, (_, i) => ({ - amp: Math.sin(i * 1.7) * 0.5 + 0.5, - delay: (i % 8) * 0.12, - })); -} diff --git a/integration/apps/web/src/components/marketing/HeroPreview.tsx b/integration/apps/web/src/components/marketing/HeroPreview.tsx deleted file mode 100644 index 58d1b46..0000000 --- a/integration/apps/web/src/components/marketing/HeroPreview.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Three glass cards stacked at angles inside the hero — a sample chart, - * a top-artists list, and a now-playing pill. They preview what the - * actual product surfaces look like without needing real data. - * - * Static; no client state needed. - */ -export function HeroPreview() { - return ( -
- {/* Back card: minutes-listened chart */} -
-
- Minutes listened · 30d -
-
-
- 1,284min -
-
- ↑ 18% -
-
- - - - - - - - - - -
- - {/* Middle card: top artists */} -
-
- Top artists · this week -
-
- {ARTISTS.map((a) => ( -
-
-
{a.name}
-
{a.plays} plays
-
- ))} -
-
- - {/* Front card: now playing pill */} -
-
-
- {[8, 14, 10, 16].map((h, i) => ( -
- ))} -
-
-
-
- ▶ Now playing -
-
Motion Sickness
-
- Phoebe Bridgers · Stranger in the Alps -
-
-
-
- ); -} - -const ARTISTS = [ - { rank: 1, name: 'Phoebe Bridgers', plays: 74, color: 'var(--color-magenta-500)' }, - { rank: 2, name: 'Tycho', plays: 52, color: 'var(--color-teal-500)' }, - { rank: 3, name: 'Bonobo', plays: 41, color: 'var(--color-violet-500)' }, -]; diff --git a/integration/apps/web/src/components/marketing/Stack.tsx b/integration/apps/web/src/components/marketing/Stack.tsx deleted file mode 100644 index 6ca19d7..0000000 --- a/integration/apps/web/src/components/marketing/Stack.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { Container } from '@/components/layout'; - -const TECH = [ - 'NestJS 10', - 'PostgreSQL 16', - 'Prisma 5', - 'Next.js 15', - 'React 19', - 'Tailwind 4', - 'Zod', - 'Argon2id', - 'JWT', -]; - -/** - * Stack section. Left: pitch + tech pills. Right: a syntax-highlighted - * SQL terminal with an overlapping result card to make "hand-written SQL" - * tangible. - */ -export function Stack() { - return ( -
- -
-
-

- Connected stack -

-

- MPD catalog in Postgres, -
- previews from iTunes, -
- artwork from Spotify. -

-

- The frontend is not a static mock: every browse, play, playlist, and analytics route - is wired through a NestJS API and a shared DTO package. -

-
- {TECH.map((t) => ( - - {t} - - ))} -
-
- -
- - -
-
-
-
- ); -} - -function SqlTerminal() { - return ( -
-
- {['#FF5F57', '#FEBC2E', '#28C840'].map((color) => ( -
- ))} -
- statify/apps/api · top-artists.query.sql -
-
-
-        SELECT
-        {'\n'}
-        {'  '}a.name,{'\n'}
-        {'  '}
-        COUNT(p.id) AS plays,{'\n'}
-        {'  '}
-        SUM(t.duration_ms) AS total_ms{'\n'}
-        FROM listening_events e{'\n'}
-        JOIN tracks t{' '}
-        ON t.id = e.track_id{'\n'}
-        JOIN artists a{' '}
-        ON a.id = t.artist_id{'\n'}
-        WHERE e.user_id = $1{'\n'}
-        {'  '}
-        AND e.played_at >{' '}
-        NOW() -{' '}
-        '7 days'
-        {'\n'}
-        GROUP BY a.id{'\n'}
-        ORDER BY plays{' '}
-        DESC
-        {'\n'}
-        LIMIT{' '}
-        10;
-      
-
- ); -} - -function QueryResultCard() { - const rows = [ - { name: 'Phoebe Bridgers', plays: 74, width: '100%' }, - { name: 'Tycho', plays: 52, width: '70%' }, - { name: 'Bonobo', plays: 41, width: '55%' }, - { name: 'King Krule', plays: 28, width: '38%' }, - ]; - return ( -
-
- Query result · 14ms -
-
- {rows.map((row) => ( -
-
- {row.name} - {row.plays} -
-
-
-
-
- ))} -
-
- ); -} diff --git a/integration/apps/web/src/components/marketing/StatsStrip.tsx b/integration/apps/web/src/components/marketing/StatsStrip.tsx deleted file mode 100644 index 230e2f2..0000000 --- a/integration/apps/web/src/components/marketing/StatsStrip.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { Database, Search, Disc3, ListEnd } from 'lucide-react'; -import { Container } from '@/components/layout'; -import { Icon } from '@/components/ui/Icon'; - -const ITEMS = [ - { value: '12', label: 'Normalized tables', icon: Database }, - { value: '6', label: 'Advanced SQL queries', icon: Search }, - { value: 'iTunes', label: '30s preview source', icon: ListEnd }, - { value: 'Spotify', label: 'Artwork source', icon: Disc3 }, -] as const; - -/** - * Headline numbers from the README in a 4-up card strip. Sits directly - * under the hero on a light surface; mirrors the SYSTEM_POINTS array - * the original landing page used. - */ -export function StatsStrip() { - return ( -
- -
- {ITEMS.map((item) => ( -
-
- -
-
-

- {item.label} -

-

- {item.value} -

-
-
- ))} -
-
-
- ); -} diff --git a/scripts/.authors b/scripts/.authors deleted file mode 100644 index 378c6a3..0000000 --- a/scripts/.authors +++ /dev/null @@ -1,4 +0,0 @@ -aykhan|Aykhan Ahmadzada|ayxanx17@gmail.com -elshad|Elshad Toklayev|etoklayev23@ku.edu.tr -rahila|Rahila Dashdiyeva|rdashdiyeva23@ku.edu.tr -eljan|Eljan Mammadli|eljanmammadli@gmail.com diff --git a/scripts/commit-as.sh b/scripts/commit-as.sh deleted file mode 100755 index 4b6f6a9..0000000 --- a/scripts/commit-as.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# commit-as.sh -# Wrapper around `git commit` that sets the author and committer identity -# from scripts/.authors based on a person key. Use this for every commit. -# -# Usage: -# scripts/commit-as.sh [git commit args...] -# -# Examples: -# scripts/commit-as.sh aykhan -m "feat(auth): add Argon2id hashing" -# scripts/commit-as.sh elshad -m "chore(ci): bump action versions" - -if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then - sed -n '4,14p' "$0" - exit 0 -fi - -PERSON="${1:?usage: commit-as.sh [git commit args...]}" -shift - -AUTHORS_FILE="$(cd "$(dirname "$0")" && pwd)/.authors" -if [[ ! -f "$AUTHORS_FILE" ]]; then - echo "error: $AUTHORS_FILE not found" >&2 - exit 1 -fi - -LINE="$(grep -E "^${PERSON}\|" "$AUTHORS_FILE" || true)" -if [[ -z "$LINE" ]]; then - echo "error: unknown person key '${PERSON}'" >&2 - echo "known keys:" >&2 - cut -d'|' -f1 "$AUTHORS_FILE" | sed 's/^/ /' >&2 - exit 1 -fi - -NAME="$(echo "$LINE" | cut -d'|' -f2)" -EMAIL="$(echo "$LINE" | cut -d'|' -f3)" - -GIT_AUTHOR_NAME="$NAME" \ -GIT_AUTHOR_EMAIL="$EMAIL" \ -GIT_COMMITTER_NAME="$NAME" \ -GIT_COMMITTER_EMAIL="$EMAIL" \ - git commit "$@"