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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,16 @@ pnpm-debug.log*
*.sqlite3
*.db
coverage/

# SQLite WAL mode journal files
*.db-shm
*.db-wal

# TypeScript incremental build cache
tsconfig.tsbuildinfo

# Claude Code session data
.claude/

# Other files to ignore
myword.md
137 changes: 137 additions & 0 deletions AI_WORKFLOW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
# AI Workflow Documentation

## 1. Tools Used and Why

**Claude Code (claude-sonnet-4-6)** via the VSCode extension.

Chosen because:
- The CLAUDE.md project rules file lets the AI maintain consistent architectural decisions across the entire session without repeating context on every prompt.
- Agentic mode (multi-step tool use) allowed delegating scaffolding, TypeScript type checking, and iterative fixes within a single conversation flow.
- The VSCode extension provides inline code navigation, making it easy to review generated files immediately after creation.

---

## 2. Concrete Prompts and Outcomes

### Prompt 1 — Project setup and architecture definition
> "Eres un Desarrollador Senior profesional.
>
> Vamos a construir un dashboard de indie games con Next.js 14 App Router, TypeScript, Tailwind y OpenAI SDK. Necesito: CRUD con SQLite, API REST, chat con tool calling y streaming. Propón la estructura de carpetas antes de escribir cualquier archivo.
>
> Lee el archivo README.md y dime cómo podemos empezar."

**Outcome:** Claude analyzed the README requirements and proposed a concrete tech stack (Next.js App Router + OpenAI SDK + `better-sqlite3`) along with the full folder structure before writing a single line of code. It then created `CLAUDE.md` — a persistent rules file encoding architecture decisions, file conventions, naming standards, and explicit constraints (no ORM, no `useEffect` for data fetching, no barrel exports, etc.). Every subsequent code generation step was automatically bound by these rules without needing to repeat instructions.

---

### Prompt 2 — Full scaffold in one shot
> "Implementa el scaffold completo del proyecto respetando la estructura definida en CLAUDE.md. Sin omitir archivos, crea en este orden:
> `package.json` → configs (`tsconfig.json`, `next.config.ts`, `tailwind.config.ts`, `postcss.config.mjs`, `.env.local`) → `lib/types.ts` → `lib/db.ts` (SQLite singleton + seed de 12 juegos indie reales) → `app/layout.tsx` + `globals.css` → páginas Server Component (`app/page.tsx`, `app/chat/page.tsx`) → rutas API REST (`GET`/`POST` `/api/games`, `GET`/`PUT`/`DELETE` `/api/games/[id]`) → `lib/tools.ts` con las 4 tool definitions para el LLM → `app/api/chat/route.ts` con loop agentico + SSE streaming → `components/GameTable.tsx` + `components/ChatWindow.tsx`.
> TypeScript strict mode, sin comentarios superfluos, sin abstracciones prematuras."

**Outcome:** Claude generated the entire project in a single turn — 16 files covering configuration, database layer, REST API, AI tool definitions, agentic streaming loop, and both UI components. All files respected CLAUDE.md constraints from the start: Server Components by default, synchronous SQLite calls, `"use client"` only where event handlers required it, and no third-party streaming libraries.

---

### Prompt 3 — Responsiveness and numeric input fix

> "Agrega diseño responsivo completo usando Tailwind CSS breakpoints (`sm`, `md`, `lg`):
>
> - **Móvil** (< 640px): layout en una sola columna, tabla reemplazada por cards apiladas,
> navegación colapsada o en menú hamburguesa si aplica.
> - **Tablet** (640px–1024px): grid de 2 columnas para las cards, tabla visible pero
> con columnas priorizadas (ocultar columnas secundarias si es necesario).
> - **Escritorio** (> 1024px): layout actual sin cambios.
>
> No uses CSS custom, todo con clases de Tailwind.
>
> Fix: Los campos `monthly_revenue_usd`, `monthly_downloads` y `average_rating` tienen valor
> por defecto `0`. Al escribir un dígito, el `0` permanece pegado al inicio (ej: escribir `5`
> resulta en `05`). El campo debe iniciar vacío (`""`), limpiarse al hacer foco si el valor es `0`,
> y parsear el número correctamente antes del submit. Aplica este patrón a todos los campos numéricos."

**Outcome:** Two separate problems solved in one prompt. For responsiveness: `GameTable` now renders stacked cards on mobile (`sm:hidden`) and a standard table on tablet/desktop (`hidden sm:block`), with the Downloads column hidden on tablet (`hidden lg:table-cell`) to reduce horizontal crowding. The nav in `layout.tsx` wraps gracefully on small screens using `flex-wrap`. For the numeric inputs: introduced a dedicated `GameFormState` type where numeric fields are typed as `number | ""`, replacing the previous `GameInput` reuse. Added `handleNumericFocus` (clears field if value is `0`) and `handleNumericChange` (preserves `""` instead of converting to `NaN`). A `buildPayload()` function normalizes to `number` before submitting, so the API always receives valid data.

---

### Prompt 4 — Pagination and toast notifications

> "Add pagination to the games table showing 10 rows by default. Include prev/next controls and a page indicator. Keep it simple with local state.
>
> Install and configure shadcn/ui. Replace all `alert()` / `confirm()` calls with toast notifications for the following actions:
> - ✅ Game created successfully
> - ✅ Game updated successfully
> - 🗑️ Game deleted
> - ❌ Error on any failed action
>
> Use appropriate variants: `default` for success, `destructive` for errors/delete."

**Outcome:** Two features delivered together. For pagination: added `PAGE_SIZE = 10` constant and local `page` state; `pageGames` slices the array for the current page; `refresh()` clamps the page so it never lands on an empty page after a deletion. Prev/Next controls are disabled at boundaries and the indicator shows `Page X of Y — N games`. For toasts: the `shadcn@latest init` CLI required interactive input and could not run non-interactively, so the setup was done manually — installing `sonner` + `clsx` + `tailwind-merge`, creating `lib/utils.ts` with the `cn` helper, and a `components/ui/toaster.tsx` wrapper that matches the dark theme. The `<Toaster />` was added to `app/layout.tsx`. All `alert()` calls were removed; delete now shows a toast with a **Delete** action button (non-blocking UX), and every create/update/error shows a typed `toast.success()` or `toast.error()`.

---

### Prompt 5 — Technical review and targeted fixes

> "Act as a senior technical lead reviewing this project against the assessment requirements. Audit the following and give a concise verdict per item:
>
> - CRUD with all 6 required fields
> - REST API routes exposed correctly
> - AI chat using tool calling (no full DB dump in prompt)
> - Streaming responses in chat
> - Responsive UI (mobile, tablet, desktop)
> - Dashboard view + chat view
>
> Then tell me: what's missing or incomplete, and any quick wins to improve code quality or UX before submitting."

**Outcome:** The audit passed all 6 checklist items. Three issues were identified and immediately fixed in the same session:
1. **`compare_games` accepted IDs, not titles** — the LLM receives game names from the user but had no way to look up IDs without an extra tool call. Fixed by changing the tool signature to `title_a`/`title_b` and adding a case-insensitive `getGameByTitle()` lookup in `lib/db.ts`. The "Compare Hades vs Dead Cells" suggestion now works in a single tool call.
2. **`updateGame` had no field whitelist** — `Object.keys(body)` went directly into the SQL `SET` clause; unknown fields caused unhandled 500 errors. Fixed with a `ALLOWED_UPDATE_FIELDS` Set that filters input before building the query.
3. **README had no setup instructions** — the assessment requires "how to run locally" in the PR. Fixed by rewriting `README.md` with prerequisites, setup steps, project structure, stack rationale, and trade-offs.

---

### Prompt 6 — Streaming agentic loop design
> (implicit in the chat route generation)

The specific challenge was: how to correctly implement the OpenAI agentic loop (tool use → tool result → next API call) while streaming text to the client simultaneously. Claude generated a `processWithTools` function that:
- Streams `content` deltas directly to the SSE response.
- Accumulates `function.arguments` fragments across chunks to reconstruct tool inputs.
- Loops back for a new API call with `role: "tool"` messages appended.

**Outcome:** A clean agentic loop with proper SSE streaming, no third-party streaming libraries needed.

---

### Prompt 7 — TypeScript strict mode fix
> (triggered by `npx tsc --noEmit` output)

**Error:** `Argument of type '{ type: "text"; text: string; }' is not assignable to parameter of type 'ContentBlock'. Property 'citations' is missing`.

**Outcome:** Claude immediately identified the mismatch — `ContentBlock` (SDK response type) is stricter than `ContentBlockParam` (SDK input type). Changed `assistantContent` from `Anthropic.ContentBlock[]` to `Anthropic.ContentBlockParam[]`. Zero other type errors in the codebase.

---

### Prompt 8 — Security patch
> (triggered by `npm audit` warning about Next.js 15.3.2 CVE-2025-66478)

**Outcome:** Claude ran `npm install next@latest` to patch the critical vulnerability. Verified the remaining 2 moderate findings are transitive postcss issues inside Next.js itself — fixing them would downgrade to Next 9.x, which is worse. Left with a documented trade-off.

---

## 3. One Moment Where the AI Got It Wrong

**The issue:** When scaffolding, Claude attempted to run `npx create-next-app@latest . --force` to initialize inside the existing directory. `create-next-app` does not support a `--force` flag and refuses to run when `.github/`, `CLAUDE.md`, and `README.md` already exist — it errors out.

**How it was fixed:** Instead of retrying the same command or deleting the existing files, Claude pivoted to manually creating every configuration file (`package.json`, `tsconfig.json`, `next.config.ts`, `tailwind.config.ts`, `postcss.config.mjs`) from scratch. This turned out to be faster and gave more control over the exact dependency versions chosen.

---

## 4. Parallel Agents / Subagents / Worktrees

Not used in this assessment. The scope was small enough for a single-agent, single-conversation flow.

The CLAUDE.md rules file served as a lightweight alternative to subagents: by encoding architecture decisions in a persistent file, every subsequent code generation step was automatically constrained without needing separate agent coordination.

If the project were larger (e.g., separate frontend and backend repos, multiple feature branches), I would have used:
- **Worktrees** for parallel feature work (e.g., CRUD branch + AI branch simultaneously).
- **Explore subagent** for codebase search without polluting the main context window.
76 changes: 76 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# CLAUDE.md — Red Valley Tech Test

## Project
Internal dashboard for indie games catalog + AI chat assistant.
Next.js 14 App Router · TypeScript · Tailwind · OpenAI SDK (gpt-4o-mini) · SQLite (better-sqlite3).

## Architecture rules

- **App Router only** — no `pages/` directory.
- **Server Components by default** — add `"use client"` only when strictly necessary (event handlers, hooks, streaming UI).
- **API routes** live in `app/api/`. Return plain `Response` or `NextResponse`.
- **DB access** is always synchronous (`better-sqlite3`). Never use async DB calls.
- **AI layer**: OpenAI SDK (`gpt-4o-mini`) with tool calling. Never dump the full DB into the prompt context.
- All AI responses must **stream** using the Vercel AI SDK `StreamingTextResponse` or raw `ReadableStream`.

## Code style

- TypeScript strict mode — no `any`, no `ts-ignore`.
- Functional components only — no class components.
- No barrel `index.ts` re-exports unless the folder has 3+ exports.
- Tailwind for all styling — no CSS modules, no styled-components.
- No comments unless the WHY is non-obvious.
- Prefer `const` arrow functions for components: `const Foo = () => {}`.

## File & folder conventions

- `lib/db.ts` — single DB singleton + all raw SQL helpers.
- `lib/tools.ts` — Anthropic tool definitions + handler map.
- `lib/types.ts` — shared TypeScript interfaces (`Game`, `ChatMessage`, etc.).
- `components/` — reusable UI only (no data fetching inside components).
- `app/api/games/route.ts` — GET (list), POST (create).
- `app/api/games/[id]/route.ts` — GET, PUT, DELETE.
- `app/api/chat/route.ts` — streaming POST.

## Do NOT do

- Do not use Prisma or any ORM — raw SQL with `better-sqlite3`.
- Do not use `fetch` inside Server Components for local API routes — call DB helpers directly.
- Do not use `useEffect` for data fetching — use Server Components or React Server Actions.
- Do not add auth, deployment config, or full test coverage (out of scope).
- Do not install unnecessary packages — keep deps minimal.

## Game schema

```ts
interface Game {
id: number;
title: string;
developer: string;
genre: string;
monthly_revenue_usd: number;
monthly_downloads: number;
average_rating: number; // 0.0 – 5.0
created_at: string; // ISO 8601
}
```

## AI tools available to the LLM

1. `get_games` — list all games (optional filters: genre, min_rating).
2. `get_game_by_id` — fetch single game.
3. `compare_games` — compare two games side by side.
4. `get_stats` — aggregate stats (top revenue, top downloads, avg rating, count by genre).

## Environment variables

```
APENAI_API_KEY=... # required (OpenAI API key)
```

## How to run

```bash
npm install
npm run dev # http://localhost:3000
```
86 changes: 80 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,88 @@ Auth, deployment, production error handling, full test coverage.

---

## Mandatory: `AI_WORKFLOW.md`
## Implementation

### Stack chosen

| Layer | Choice | Reason |
|---|---|---|
| Framework | Next.js 15 App Router + TypeScript | Matches requirement; App Router enables Server Components |
| Styling | Tailwind CSS + shadcn/ui (Sonner) | Fast, consistent, dark-theme friendly |
| LLM | OpenAI SDK (`gpt-4o-mini`) | Tool calling + SSE streaming, cost-efficient |
| Storage | SQLite via `better-sqlite3` | Zero-config, synchronous API, persists between restarts |

### Project structure

```
app/
api/
games/ → GET (list + filter), POST (create)
games/[id]/ → GET, PUT, DELETE
chat/ → POST — SSE streaming with tool calling
chat/ → Chat page
page.tsx → Dashboard page
components/
GameTable.tsx → CRUD table (responsive: cards on mobile, table on desktop)
ChatWindow.tsx → Streaming chat UI with suggestion pills
ui/toaster.tsx → Sonner toast wrapper
lib/
db.ts → SQLite singleton, schema, seed, query helpers
tools.ts → 4 LLM tool definitions + handlers
types.ts → Shared TypeScript interfaces
utils.ts → cn() helper (tailwind-merge + clsx)
```

### AI tools available to the LLM

| Tool | Description |
|---|---|
| `get_games` | List all games, optionally filtered by genre or min rating |
| `get_game_by_id` | Fetch a single game by ID |
| `compare_games` | Compare two games by title (revenue, downloads, rating) |
| `get_stats` | Aggregate stats: top revenue, top downloads, avg rating, genre breakdown |

---

## How to run locally

### Prerequisites

- Node.js 18+
- An OpenAI API key

### Setup

```bash
# 1. Install dependencies
npm install

# 2. Set your API key
echo "APENAI_API_KEY=sk-..." >> .env.local

Include a file documenting how you used AI coding tools (Claude Code, Cursor, etc.) during this assessment:
# 3. Start the dev server
npm run dev
```

Open [http://localhost:3000](http://localhost:3000).

The SQLite database (`games.db`) is created automatically on first run and seeded with 12 indie games.

---

## Trade-offs

- **`better-sqlite3` over Prisma/Drizzle** — synchronous API is simpler in Next.js Server Components; no async/await overhead. Trade-off: less ergonomic for complex queries.
- **Manual SSE over Vercel AI SDK** — avoided adding a dependency for a feature (streaming + tool loop) that's 60 lines of code. Trade-off: more boilerplate.
- **`gpt-4o-mini` over GPT-4o** — cost-efficient for a demo; handles multi-step tool use correctly. Trade-off: slightly weaker reasoning on ambiguous queries.
- **No pagination on API** — pagination is client-side only (10 rows). Trade-off: all games load on first request. Acceptable at catalog scale (~100s of games).
- **Auth out of scope** — per assessment requirements.

---

## Mandatory: `AI_WORKFLOW.md`

1. Which tool(s) and why.
2. 3–5 concrete prompts you used and their outcome.
3. One moment where the AI got it wrong and how you fixed it.
4. If you used parallel agents / subagents / worktrees, describe how.
See [AI_WORKFLOW.md](./AI_WORKFLOW.md) for the full documentation of how AI coding tools were used during this assessment.

---

Expand Down
Loading