A dense, high-performance personal developer command center. "Spider-Man Noir" design system: grayscale-first, high-contrast chiaroscuro, 1930s art-deco pulp.
This repository is the foundation. Feature modules are built by separate
agents into the folders reserved for them in the boundary map below. Respect the
boundaries: shared code lives in src/components/ui, src/components/shell, and
src/lib; module code lives only in that module's own route folder(s).
npm install
npm run dev # http://localhost:3000
npm run build # production build (must stay green)
npm run lint # eslint (must stay clean)Stack: Next.js 15 (App Router) · TypeScript · Tailwind v4 · lucide-react · recharts · idb-keyval · zod · date-fns. Do not add dependencies without explicit approval.
.research/ holds API notes dropped by research agents (not shipped code).
Each module agent owns only the folders listed. Never edit another module's folder. Shared primitives/types/store are owned by the Foundation and should be imported, not modified (extend via new files if truly needed, and document it).
| Owner | Owns (create/edit here) | Reads (import only) |
|---|---|---|
| Foundation (this) | src/components/ui/**, src/components/shell/**, src/lib/**, src/app/layout.tsx, src/app/globals.css, src/app/settings/**, tailwind.config.ts, next.config.ts |
— |
| Competency | src/app/matrix/** |
@/components/ui, @/lib/store, @/lib/types, @/lib/seed (COMPETENCY_SEED) |
| Module A (Daily Execution + Exam) | src/app/tasks/**, src/app/exams/** |
@/components/ui, @/lib/store, @/lib/types, Task/Course seeds |
| Module B (GitHub Sync) | src/app/sync/** |
@/components/ui, @/lib/store (useSettings), @/lib/types (Settings) |
| Module C (Metrics + Calendar) | src/app/metrics/** |
@/components/ui, recharts, @/lib/store, @/lib/types (MetricPoint/Contest) |
| Module D (OpenRouter LLM) | src/app/llm/** |
@/components/ui, @/lib/store (useSettings), @/lib/types (Settings) |
| Wave 3 (Dashboard) | src/app/page.tsx |
everything (read-only wiring) |
Adding a route to the nav: register it in src/components/shell/nav.ts
(NAV_ITEMS). The sidebar renders from that list with active spotlight styling.
Replacing a stub: each module route currently renders
<ModulePlaceholder module="..." owner="..." />. Replace the entire page.tsx.
Canonical source: the @theme block in src/app/globals.css (Tailwind v4).
It generates both CSS custom properties (var(--color-*)) and utility
classes. tailwind.config.ts mirrors the palette for content globs + at-a-glance
reference (wired via @config).
Surfaces — noir-bg #0a0a0b · noir-panel #131315 · noir-raised
#1b1b1e · noir-hover #242427
Borders — rule-hairline #2c2c30 · rule-strong #3d3d42
Ink (aged paper) — ink-primary #e9e6dd · ink-secondary #9a9890 ·
ink-muted #67655e
Accent — spotlight #f5f3ec (active/focus) + soft white glow
Radius — rounded-deco (2px). Micro-padding px-2 py-1, hairline borders.
Utility classes: bg-noir-panel, text-ink-primary, border-rule-strong,
bg-spotlight, font-display / font-sans / font-mono, plus custom:
deco-rule (double-rule deco border), spotlight-active (glow), hatch-fill
(diagonal half-tone), .film-grain / .vignette (decorative overlays — wrap a
relative element; both are pointer-events-none).
Type — display: Cinzel (deco serif) · sans: Space Grotesk (grotesque) ·
mono: JetBrains Mono. All via next/font (variables in layout.tsx).
State badges (grayscale): not_started faint outline · in_progress
half-tone hatch · production_verified solid inverted "stamped" chip. Use
<Badge status={status} />.
All composable, fully typed, < 150 lines each. Import from the barrel.
| Component | Key props |
|---|---|
Panel |
title?, actions?, bodyClassName?, children |
Card |
title?, interactive?, children |
Badge |
status?: Status | variant?, children? |
Button |
variant?: "primary"|"secondary"|"ghost", size?: "sm"|"md", native button props |
IconButton |
label: string (required), active?, native button props |
Tabs |
items: TabItem[], activeId, onChange(id) |
Table<T> |
rows, columns: Column<T>[], rowKey(row), onRowClick?, activeKey?, empty? |
Modal |
open, onClose(), title?, footer?, children (Esc closes, scroll-locks) |
ToastProvider / useToast |
useToast().push({ message, tone?, durationMs? }) |
ProgressBar |
value, max?, label? |
SplitPane |
left, right, initialLeftPercent?, minPercent?, maxPercent? |
EmptyState |
title?, description?, icon?, action? (noir flavor) |
DecoFrame |
children (art-deco corner brackets) |
FilmGrain / Vignette |
children (wrap a positioned container) |
SpiderMark |
size?, title? (grayscale web-line emblem logo) |
Shell helpers (@/components/shell): AppShell, Sidebar, TopBar,
ModulePlaceholder, NAV_ITEMS.
Typed repository over IndexedDB (idb-keyval) with a localStorage fallback.
Immutable: inputs are never mutated; reads return fresh copies.
import { createStore, createSingletonStore, useStore, useSettings, STORE_KEYS } from "@/lib/store";
// Collection repo (entities must have an `id`)
const tasks = createStore<Task>(STORE_KEYS.tasks);
await tasks.findAll(); // T[]
await tasks.get(id); // T | null
await tasks.upsert(entity); // T[] (insert or replace by id)
await tasks.remove(id); // T[]
await tasks.replaceAll(list); // T[]
// React hook: hydrate on mount, persist on change, expose loading
const { items, loading, upsert, remove, replaceAll } = useStore<Task>(
STORE_KEYS.tasks,
TASK_SEED, // optional seed written once if storage is empty
);
// Settings singleton (IndexedDB only — secrets never hardcoded)
const { settings, loading, save } = useSettings();Store keys (STORE_KEYS, never hardcode): competency, tasks, courses,
contests, metrics, settings.
Seeds (@/lib/seed): COMPETENCY_SEED (real Java + Python matrices, status
not_started). TASK_SEED / COURSE_SEED / CONTEST_SEED / METRIC_SEED are
intentionally empty — show a noir EmptyState until the user adds records.
Domain types (@/lib/types) — every type ships a Zod schema; the TS type is
inferred from it so the two never drift. Validate at boundaries with
xSchema.parse(...).
CompetencyNode { id, matrix, category, title, detail, status, updatedAt }Task { id, title, problemUrl, complexityTag, estMinutes, priority, status, focusDate, scratchpad, createdAt }Course { id, name, examDate, syllabusUrl, notes }Contest { id, platform, name, startsAt, registrationDeadline, url, tier }MetricPoint { date, platform, solved }Settings { githubPat?, githubOwner?, githubRepo?, githubPath?, openRouterKey?, openRouterModel? }
Unions: Status = "not_started" | "in_progress" | "production_verified" ·
Matrix = "java" | "python" · Priority = "low" | "med" | "high" ·
MetricPlatform = "codeforces" | "leetcode" | "codechef".
Helpers: createId() (@/lib/id), cn(...) class combiner (@/lib/cn).