Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
6b52e1e
added new api variables
Hum2a Jul 3, 2026
e266df3
new
Hum2a Jul 3, 2026
f4ce1f3
Enhance build and deployment scripts
Hum2a Jul 3, 2026
4ab5e91
Update deployment workflows for staging and production
Hum2a Jul 3, 2026
386b17e
changed c: → extends
Hum2a Jul 3, 2026
ed3f602
Update README.md for setup instructions and performance optimizations
Hum2a Jul 3, 2026
55e59b2
reorganised the display of the submission
Hum2a Jul 3, 2026
342ed2b
Add new session creation feature and enhance UI components
Hum2a Jul 3, 2026
7afa0cf
updated for submission.md
Hum2a Jul 3, 2026
75a3185
tier 2
Hum2a Jul 3, 2026
e53c1b7
Implement media upload features and enhance session management
Hum2a Jul 3, 2026
245f096
Enhance UI motion and animations across components
Hum2a Jul 3, 2026
ff60332
Add game guides and UI components for playful game instructions
Hum2a Jul 3, 2026
907e785
added tooltips
Hum2a Jul 3, 2026
760279f
Implement JWT authentication and API key management features
Hum2a Jul 3, 2026
6e72b90
Implement rate limiting, session caching, and structured logging feat…
Hum2a Jul 3, 2026
09c1b39
Update SCALE.md with KV deployment instructions and Cloudflare API to…
Hum2a Jul 3, 2026
f3e95c3
replaced placeholder id
Hum2a Jul 3, 2026
892d7e2
converted to free features
Hum2a Jul 3, 2026
cbe5730
Enhance testing and deployment processes
Hum2a Jul 3, 2026
3d199d5
Refactor session caching and validation logic
Hum2a Jul 3, 2026
e7b00df
Implement PWA features, i18n support, and scorecard export functionality
Hum2a Jul 3, 2026
b4b02f2
Enhance FileField component with custom file input button and tooltip…
Hum2a Jul 3, 2026
796efd1
Merge pull request #2 from Hum2a:tier-7
Hum2a Jul 3, 2026
3a9f531
Merge pull request #3 from Hum2a:tier-6
Hum2a Jul 3, 2026
85c2198
Merge pull request #4 from Hum2a:tier-5
Hum2a Jul 3, 2026
670ca34
Merge pull request #5 from Hum2a:tier-4
Hum2a Jul 3, 2026
a7f441e
Merge pull request #6 from Hum2a:tier-3
Hum2a Jul 3, 2026
47414b0
Merge pull request #7 from Hum2a:tier-2
Hum2a Jul 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .cursor/rules/00-project.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ Golden rules:
- Validate all input with Zod (strict). Never leak secrets or stack traces to clients.
- staging and production are isolated (Neon branch, Worker env, R2 bucket per env). Never mix secrets.
- Don't add anything you can't explain in the follow-up interview.
- When rubric-relevant work ships, update `SUBMISSION.md`, spec mapping, and checklists in the same change (see `.cursor/rules/111-submission-docs.mdc`).
- New routes and async UI must follow page-load motion rules (see `.cursor/rules/31-page-load-motion.mdc`) — animated skeletons, `AnimatedOutlet`, no abrupt pop-in.
7 changes: 7 additions & 0 deletions .cursor/rules/110-docs.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ globs: "**/*.md"
---
- Keep the README's five graded sections accurate to the code (perf, media, scale, security, prod).
- Record decisions as ADRs in docs/adr. Document every public surface. Wrong docs are worse than none.
- Reviewer-facing docs (`SUBMISSION.md`, checklists, spec mapping): see **111-submission-docs** — sync with [docs/HOMEWORK-SPEC.md](../../docs/HOMEWORK-SPEC.md) when rubric-relevant work ships; professional yet personable tone for 501.
- Game mode “how to play” copy: see **32-game-guides** — single source `apps/web/src/content/game-guides.ts` + [docs/GAME-GUIDES.md](../../docs/GAME-GUIDES.md).
- Form field tooltips: see **33-field-tooltips** — `field-tooltips.ts` + [docs/FIELD-TOOLTIPS.md](../../docs/FIELD-TOOLTIPS.md).
- Auth / JWT / API keys: see **34-auth** — [docs/AUTH.md](../../docs/AUTH.md).
- Scale / cache / rate limits: see **35-scale** — [docs/SCALE.md](../../docs/SCALE.md).
- Testing / contracts / visual: see **36-quality-dx**.
- PWA / i18n / scorecard / analytics / API docs: see **37-tier7-extras** — [docs/EXTRAS.md](../../docs/EXTRAS.md).
67 changes: 67 additions & 0 deletions .cursor/rules/111-submission-docs.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
description: Keep SUBMISSION.md and reviewer-facing docs aligned with the 501 homework spec
globs: "SUBMISSION.md,README.md,docs/checklists/**,docs/HOMEWORK-SPEC.md,docs/INTERVIEW.md,docs/ROADMAP.md"
---
# Submission & reviewer-facing documentation

Write for engineers at **501 Entertainment** who build venue-facing hub products (live games, hospitality UX). Tone: **professional, clear, and personable** — confident without hype; honest about trade-offs.

## Spec is the checklist

Before editing submission docs, read [docs/HOMEWORK-SPEC.md](../../docs/HOMEWORK-SPEC.md) and [docs/checklists/01-homework-rubric.md](../../docs/checklists/01-homework-rubric.md). Every claim in SUBMISSION.md must map to a rubric row or a defensible extra.

**Brief must stay reflected:**
- Session dashboard: ID, players + scores, player photo, status, responsive UI, editable scores, real-time updates
- Match history: list → detail → video playback
- API: `GET/POST/PATCH /sessions`, `GET /sessions/:id`, database-backed
- README graded topics: perf, media, scale, security, production mitigations (with pointers to `docs/`)

## Files to update together

| File | Role |
| --- | --- |
| `SUBMISSION.md` | Primary hand-in cover sheet — links, brief coverage, extras, doc index |
| `README.md` | Graded written answers + stack; keep claims accurate to code |
| `docs/HOMEWORK-SPEC.md` | Spec mapping table when implementation status changes |
| `docs/checklists/01-homework-rubric.md` | Tick rubric items when closed |
| `docs/checklists/02-pre-submission.md` | Deploy URLs, secrets, smoke-test steps |
| `docs/checklists/03-known-gaps.md` | Resolve or honestly document remaining gaps |
| `docs/ROADMAP.md` | Status snapshot when a tier completes |
| `docs/INTERVIEW.md` | 90s tour talking points when UX/architecture shifts |

Update these **in the same change** when you ship rubric-relevant features, change deploy URLs, or close known gaps.

## SUBMISSION.md structure

Keep it scannable (reviewers skim first):

1. **Opening** — one paragraph: what Oche is, stack in plain terms, why it fits the brief (venue live scores + history + video).
2. **Live links** — staging, production, API (only URLs you have verified).
3. **Brief coverage** — bullet list mirroring the homework spec sections; use ✓ only when deployed and tested.
4. **README answers** — one line per graded topic + link to README / `docs/`.
5. **Notable extras** — RLS, WebSockets, env isolation, etc.; tie each to something 501 cares about (security, formats, scale).
6. **Doc index** — pointer to `docs/README.md`, roadmaps, checklists.

Do **not** duplicate full README essays in SUBMISSION.md — summarise and link.

## Tone guide (501-facing)

**Do**
- Use neutral project voice or sparing first person (“I chose Cloudflare Workers for…”) when explaining a decision.
- Acknowledge their domain: competitive socialising, live venue sessions, operator dashboards.
- State what is **deployed vs local-only**; note staging vs production isolation.
- When something is partial, say so briefly and point to `03-known-gaps.md` or the interview talking point.

**Don't**
- Marketing superlatives, emoji, or generic cover-letter filler.
- Oversell features that README, gaps, or code contradict.
- Raw stack dumps with no tie-back to the brief.
- Blame the toolchain; frame Windows/PowerShell fixes as DX choices you documented.

**Example opener**

> Oche is a Game Session Dashboard for your Cloud Developer take-home: a React SPA with live scores, match history with video, and a Hono API on Cloudflare with Neon Postgres. Staging and production are isolated; row-level security enforces per-owner data access throughout.

## After edits

Run `npm run rules:sync` if `.cursor/rules` changed. If README graded claims changed, verify `npm run check` still passes.
1 change: 1 addition & 0 deletions .cursor/rules/20-frontend-react.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ globs: "apps/web/**/*.{ts,tsx}"
- Route components are lazy-loaded (code splitting). No global store.
- Live updates via the `useLiveSession` WebSocket hook; polling is the documented fallback.
- API base URL comes from `import.meta.env.VITE_API_BASE` (per-env).
- Form inputs: use `TextField` / `SelectField` / `FileField` with required `tooltip` — see **33-field-tooltips**.
35 changes: 30 additions & 5 deletions .cursor/rules/30-styling.mdc
Original file line number Diff line number Diff line change
@@ -1,8 +1,33 @@
---
description: Styling — Tailwind v4 + shadcn only
description: Styling — Tailwind v4 + shadcn + Oche motion system
globs: "apps/web/**/*.{ts,tsx,css}"
---
- Tailwind utilities + shadcn/ui components only. No ad-hoc CSS files.
- Theme tokens live once in `src/index.css` (`@theme`). Identity = "live scoreboard":
deep slate canvas, chalk text, single `--color-oche` accent, condensed tabular `.score` numerals.
- Don't ship default-looking shadcn; keep the deliberate accent + spacing rhythm.
# Visual identity
- Tailwind utilities + shadcn/ui components only. Theme tokens live in `src/index.css` (`@theme`).
- Identity = "live scoreboard": deep slate canvas, chalk text, single `--color-oche` accent, condensed tabular `.score` numerals.
- Don't ship default-looking shadcn; keep deliberate accent + spacing rhythm.

# Motion philosophy (interview polish)
Every interactive surface should feel **modern, smooth, and subtly captivating** — never flashy or distracting.

**Page loads & route entry:** see **31-page-load-motion** — skeletons + slide entrances mandatory; no bare "Loading…" text.

## Principles
- **Press feedback:** every `<button>`, shadcn `Button`, and tab control uses `.oche-press` (scale 0.92 + brightness on `:active`). Accent actions add `.oche-press-accent`. Never ship a button without active feedback.
- **Live scores:** use `AnimatedScore` (`@number-flow/react`) for changing numerals — digit scroll with Oche easing; respects `prefers-reduced-motion`.
- **Easing:** `--ease-oche` (smooth deceleration) for UI; `--ease-spring` for score numerals. Durations: `--duration-fast` (180ms), `--duration-normal` (280ms), `--duration-slow` (420ms).
- **Tab highlights:** always use `TabStrip` with sliding `.oche-indicator` — never instant background swaps.
- **Tab / route content:** wrap changing panels in `AnimatedPanel` or `AnimatedOutlet` so content **slides horizontally** (forward = from right, back = from left).
- **Lists:** stagger entry with `.oche-stagger` on `<ul>` children (scoreboards, player rows).
- **Reduced motion:** all animations respect `prefers-reduced-motion` in `index.css`. Never bypass this.

## When adding UI
1. Prefer motion utilities in `index.css` over one-off inline styles.
2. Reuse `components/motion/` (`TabStrip`, `AnimatedPanel`, `AnimatedOutlet`) before inventing new patterns.
3. Route-level direction logic lives in `lib/motion.ts`.
4. Score changes: `.oche-score-pulse` on the row; `.score` transitions for numeral updates.

## Don't
- Add framer-motion or other animation libraries unless explicitly requested.
- Use bounce/elastic easing or long (>500ms) transitions.
- Animate layout-critical properties without `prefers-reduced-motion` fallback.
68 changes: 68 additions & 0 deletions .cursor/rules/31-page-load-motion.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
description: Page load and route entry animations — stylish, professional, never abrupt
globs: "apps/web/src/{routes,main.tsx,App.tsx,components/motion/**,components/ui/skeleton.tsx}"
---
# Page load motion

Every route and async boundary must **arrive** — not pop in. Load states should feel deliberate and venue-dashboard polished (501 interview signal).

## Non-negotiables

1. **No bare loading copy** — never ship plain `"Loading…"` / `"Loading sessions…"` as the only UI. Use animated skeletons from `components/ui/skeleton.tsx` inside `.oche-enter-fade`.
2. **Route transitions** — all primary content flows through `AnimatedOutlet` in `App.tsx`. Do not render `<Outlet />` directly without it.
3. **Direction-aware slides** — nav tab changes slide horizontally (`oche-enter-forward` / `oche-enter-back`). Same-tab depth changes (e.g. history list → detail) slide forward/back via pathname length in `AnimatedOutlet`.
4. **Lazy route chunks** — wrap lazy routes in `Suspense` with `RouteSkeleton` (or route-specific skeleton), not a spinner or text node.
5. **Page root entrance** — top-level route content gets `.oche-enter-up` or inherits from `AnimatedOutlet`; section headers and primary panels should not appear without motion.
6. **Data fetching** — `isLoading` from TanStack Query → matching skeleton component; `isRefetching` keeps content visible (no full-page flash).
7. **Lists after load** — player rows, history items, timeline events use `.oche-stagger` on first paint.
8. **Reduced motion** — `prefers-reduced-motion` in `index.css` zeroes animations; skeletons may remain static (no pulse override needed).

## Layered load sequence (use this order)

| Layer | When | Pattern |
| ----- | ---- | ------- |
| App shell | First paint | `App` root: `.oche-enter-fade` |
| Route chunk | Lazy import | `Suspense` → `RouteSkeleton` + `.oche-enter-fade` |
| Route body | Pathname change | `AnimatedOutlet` slide class from `lib/motion.ts` |
| Nested panel | Tab / session switch | `AnimatedPanel` with index from `slideEnterClass()` |
| API data | Query pending | Route-specific skeleton until `data` resolves |
| Content lists | Data arrived | `.oche-stagger` on `<ul>` |

## Adding a new route

```tsx
// main.tsx — lazy + Suspense
const MyPage = lazy(() => import('./routes/MyPage').then((m) => ({ default: m.MyPage })));

// router children
{ path: 'my-page', element: <Page><MyPage /></Page> }

// MyPage.tsx — root + query loading
export function MyPage() {
const { data, isLoading } = useQuery(...);
if (isLoading) return <MyPageSkeleton />; // add to skeleton.tsx if new layout
return <section className="oche-enter-up">...</section>;
}
```

Register a skeleton branch in `RouteSkeleton` if the layout differs from Overview/History/Detail.

## Tone

- **Stylish:** fade + subtle slide (18px), staggered lists, skeleton shapes that match final layout.
- **Professional:** 280–420ms durations, `--ease-oche` only; no spinners, bounce, or full-screen overlays.
- **Captivating but subtle:** motion supports the content; one primary entrance animation per navigation action.

## Don't

- Flash empty `<main>` while waiting for JS or data.
- Remount the entire app shell on route change (animate outlet only).
- Add page-load animation libraries (framer-motion, etc.) unless explicitly requested.
- Skip skeletons because "it's fast locally" — reviewers on cold cache must still see polish.

## Reference files

- Tokens & keyframes: `apps/web/src/index.css`
- Direction helpers: `apps/web/src/lib/motion.ts`
- Outlet / panel / tabs: `apps/web/src/components/motion/`
- General motion rules: **30-styling**
55 changes: 55 additions & 0 deletions .cursor/rules/32-game-guides.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
description: Playful game-mode instructions — keep copy and UI in sync
globs: "apps/web/src/{content,components}/**/*,apps/web/src/routes/*.tsx,docs/GAME-GUIDES.md,packages/db/src/seed.ts"
---
# Game guides (how to play)

Oche is a **venue scoreboard** for 501-style competitive socialising. Every active session should show **clear, playful** instructions for how that game is played.

## Single source of truth

All rules copy: **`apps/web/src/content/game-guides.ts`**

Human index: **[docs/GAME-GUIDES.md](../../docs/GAME-GUIDES.md)**

Do **not** duplicate long rules in components, README, or routes — import from `content/game-guides.ts`.

## When to update

Update guides when you:

- Add a new game mode or change rules copy
- Add seed sessions that showcase a mode (`packages/db/src/seed.ts` titles should match `resolveGameId()` keywords)
- Change how scores are tracked on the dashboard (update each guide’s `scoringHint`)
- Add create-session fields that pick game mode

## Required UI

| Surface | Requirement |
| ------- | ------------- |
| **Overview** (live) | `<GameGuideCard gameId={…} defaultOpen />` below session header |
| **Session detail** | `<GameGuideCard />` (collapsed default ok) |
| **Create session** | Game mode picker bound to `GameId` + live `GameGuideCard` preview |

Resolve mode: `resolveGameId(session.title)` unless an explicit `gameType` field exists on the session.

## Copy style (501-facing)

- **Playful, not silly** — one light venue joke per guide (`venueTip`); rules stay accurate.
- Structure every guide: `objective` → numbered `steps` → `scoringHint` (Oche board) → `venueTip`.
- Use plain English; explain busts, doubles, marks, lives where relevant.
- No walls of text — 4–6 steps max.

## Adding a new `GameId`

1. Extend `GameId` type and `GAME_GUIDES` record.
2. Add to `GAME_OPTIONS` (auto from record keys if id added to record).
3. Extend `resolveGameId()` with title keywords **or** wire explicit picker → persisted field.
4. Update `docs/GAME-GUIDES.md` index table.
5. Add/update a seed session title that triggers the mode in demos.

## Don’t

- Hard-code rules strings in `Overview.tsx`, `SessionDetail.tsx`, or `CreateSession.tsx`.
- Ship a new seed game title without checking `resolveGameId()` still maps correctly.
- Remove `scoringHint` — interviewers should see how software maps to venue workflow.
55 changes: 55 additions & 0 deletions .cursor/rules/33-field-tooltips.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
description: Every text/select/file field must have an accessible tooltip
globs: "apps/web/src/**/*.{ts,tsx}"
---
# Field tooltips

Every **text input**, **select**, **textarea**, and **file picker** in the SPA must expose helper text as a tooltip.

## Non-negotiables

1. **Never raw `<input>` / `<select>` / `<textarea>`** in routes or feature components — use `TextField`, `SelectField`, or `FileField` from `@/components/ui/form-fields`, or `MediaUpload` with a required `tooltip` prop.
2. **`tooltip` is required** (TypeScript-enforced on form field components) — short, plain-English guidance for venue staff.
3. **Triple exposure** (all three):
- Native `title` on the control (hover fallback)
- `aria-describedby` → tooltip element id
- Visible **?** control (`FieldTooltipIcon`) beside the label; bubble also shows when the field has focus (`.field-root:focus-within`)
4. **Copy lives in** `apps/web/src/content/field-tooltips.ts` — add new keys there; do not inline long tooltip strings in JSX.

## Components

| Component | Use for |
| --------- | ------- |
| `TextField` | `type="text"`, email, number, etc. |
| `SelectField` | `<select>` dropdowns |
| `FileField` | Visible file inputs — hidden `<input type="file">` + outline **Choose file** button (same affordance as `MediaUpload`) |
| `MediaUpload` | Hidden file input + upload UX |
| `FieldTooltipIcon` | Custom labels (only if wrapping a control manually) |

## Adding a new field

```tsx
import { TextField } from '@/components/ui/form-fields';
import { FIELD_TOOLTIPS } from '@/content/field-tooltips';

// 1. Add copy to field-tooltips.ts
// 2. Use the component:
<TextField label="Lane" tooltip={FIELD_TOOLTIPS.lane} ... />
```

## Tooltip copy style

- One or two sentences max.
- Explain **what** the field is for and **any limits** (max length, file types, MB).
- Venue-friendly tone; no jargon without a brief gloss.

## Don't

- Use placeholder text as the only help (placeholders ≠ tooltips).
- Ship a bare native file input in the UI — always use the hidden input + visible button pattern.
- Ship `title=""` or omit `aria-describedby`.
- Add `@radix-ui/react-tooltip` unless explicitly requested — use shared `form-fields` pattern.

## Docs

Human index: [docs/FIELD-TOOLTIPS.md](../../docs/FIELD-TOOLTIPS.md)
37 changes: 37 additions & 0 deletions .cursor/rules/34-auth.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
description: JWT auth, API keys, owner switcher, RLS upgrade path
globs: "apps/{api,web}/src/**/*.{ts,tsx}"
---
# Auth & multi-tenant (Tier 4)

## Request principal

Resolve order in `resolvePrincipal()`:

1. `Authorization: Bearer` JWT (or `?token=` for WebSocket)
2. `x-oche-owner` / `?key=` API key (demo, env JSON, or `api_keys` table)

Every DB call still uses `withPrincipal(db, ownerId, fn)` until Neon JWT RLS is enabled — see docs/AUTH.md.

## Secrets

- `OCHE_JWT_SECRET` — Wrangler secret per env; never in the SPA bundle.
- API keys stored **hashed** (SHA-256) in `api_keys`; plaintext returned once on create.
- Demo keys (`demo-key-a/b`) remain for RLS isolation demos.

## SPA

- Use `AuthProvider` + `useAuth()`; never hardcode `demo-key-a` in `api.ts`.
- Owner switcher invalidates TanStack Query cache on venue change.
- API key UI at `/settings/keys`.

## Adding auth to a new route

```typescript
const ownerId = await resolvePrincipal(c, getDb(c.env));
if (!ownerId) return c.json({ error: 'Unauthorised' }, 401);
```

## Neon upgrade

Document only in take-home — do not drop GUC policies without migration plan. Reference: `docs/examples/neon-jwt-rls.sql`.
31 changes: 31 additions & 0 deletions .cursor/rules/35-scale.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
description: Scale, caching, rate limits, observability
globs: "apps/api/**/*.ts"
---
# Scale & reliability (Tier 5)

## Rate limiting

- Use `RateLimiter` Durable Object (`RATE_LIMITER` binding) — one DO per IP.
- Do not reintroduce global in-memory Maps for production limits.
- Exempt `/health` and `/openapi.json` from rate limit.

## Session list cache

- Cache only `GET /sessions` via `lib/session-cache.ts` + `CACHE` KV.
- TTL 60s in KV (Cloudflare minimum); HTTP `max-age=15`; invalidate with `bumpSessionListCache()` on `POST` / `PATCH`.
- Set `X-Cache: HIT|MISS` on list responses.

## Logging

- Use `logInfo` / `logWarn` / `logError` from `lib/logger.ts` — JSON only, no raw `console.log` in routes.
- Every response gets `X-Trace-Id`; include `traceId` in 500 JSON body.

## Health

- Shallow: `GET /health` (no DB).
- Readiness: `GET /health?deep=1` — `select 1`; return 503 if DB unreachable.

## Docs

[docs/SCALE.md](../../docs/SCALE.md) · queue/transcode pipeline in [docs/MEDIA.md](../../docs/MEDIA.md)
Loading
Loading