/* Mock project: filesystem tree, file contents, before/after pairs, runtime diff. */ (function () { // ---- working-tree (current / updated) file contents ---------------- const F = {}; F["src/Http/Controller/UserController.php"] = `users->find($id); if ($user === null) { return $this->json(['error' => 'user_not_found'], 404); } $balance = $this->payments->balanceFor($user); return $this->json([ 'id' => $user->id, 'email' => $user->email, 'plan' => $user->plan->value, 'name' => $user->name, 'currency' => $user->currency, 'balance' => $balance->toArray(), ]); } public function update(ServerRequestInterface $request, string $id): ResponseInterface { $user = $this->users->find($id); if ($user === null) { return $this->json(['error' => 'user_not_found'], 404); } $data = (array) $request->getParsedBody(); $user->fill($this->onlyFillable($data)); $this->users->save($user); return $this->json($user->toArray()); } /** @return array */ private function onlyFillable(array $data): array { $allowed = ['email', 'plan', 'name']; return array_intersect_key($data, array_flip($allowed)); } } `; F["src/Service/PaymentService.php"] = `gateway->lookupBalance($user->id); return Money::fromCents($cents, $user->currency ?? 'EUR'); } public function charge(User $user, Money $amount, string $reason): bool { if ($amount->isZero()) { $this->logger->warning('Skipped zero charge', ['user' => $user->id]); return false; } $result = $this->gateway->charge($user->paymentToken, $amount->cents()); $this->logger->info('Charge attempt', [ 'user' => $user->id, 'amount' => $amount->cents(), 'ok' => $result->success, ]); return $result->success; } } `; F["public/assets/app.js"] = `import { createStore } from './store.js'; import { mountRouter } from './router.js'; const store = createStore({ user: null, notifications: [], theme: 'dark', }); async function bootstrap() { const res = await fetch('/api/session', { credentials: 'include' }); if (res.ok) { const session = await res.json(); store.set('user', session.user); store.set('theme', session.user.theme ?? 'dark'); } mountRouter(document.querySelector('#app'), store); store.subscribe('notifications', renderToasts); } function renderToasts(list) { const host = document.querySelector('#toasts'); host.replaceChildren(...list.map((n) => { const el = document.createElement('div'); el.className = \\\`toast toast--\\\${n.level}\\\`; el.textContent = n.message; return el; })); } document.addEventListener('DOMContentLoaded', bootstrap); `; F["public/assets/store.js"] = `export function createStore(initial = {}) { let state = { ...initial }; const subs = new Map(); return { get: (key) => state[key], set(key, value) { state = { ...state, [key]: value }; (subs.get(key) || []).forEach((fn) => fn(value, state)); }, subscribe(key, fn) { const list = subs.get(key) || []; list.push(fn); subs.set(key, list); return () => subs.set(key, list.filter((f) => f !== fn)); }, }; } `; F["public/assets/styles.css"] = `:root { --brand: #4d8dff; --ink: #15171a; --paper: #ffffff; --radius: 10px; } body { margin: 0; font-family: system-ui, sans-serif; background: var(--ink); color: #e6e8ea; } .toast { padding: 10px 14px; border-radius: var(--radius); border-left: 3px solid var(--brand); } .toast--error { border-left-color: #e0696a; } .toast--success { border-left-color: #5cbd6b; } `; F["src/types/api.ts"] = `export type Plan = 'free' | 'pro' | 'enterprise'; export interface User { id: string; email: string; name: string; plan: Plan; currency: string; createdAt: string; } export interface Balance { cents: number; currency: string; formatted: string; } export type ApiResult = | { ok: true; data: T } | { ok: false; error: string; status: number }; export async function getUser(id: string): Promise> { const res = await fetch(\\\`/api/users/\\\${id}\\\`); if (!res.ok) { return { ok: false, error: 'request_failed', status: res.status }; } return { ok: true, data: (await res.json()) as User }; } `; F["scripts/migrate.py"] = `#!/usr/bin/env python3 """Run pending database migrations in order.""" import sys from pathlib import Path from db import connect, applied_migrations MIGRATIONS = Path(__file__).parent / "migrations" def pending(conn): done = applied_migrations(conn) files = sorted(MIGRATIONS.glob("*.sql")) return [f for f in files if f.stem not in done] def run(conn, migration: Path) -> None: sql = migration.read_text() print(f" -> applying {migration.stem}") with conn.cursor() as cur: cur.execute(sql) cur.execute( "INSERT INTO schema_migrations (version) VALUES (%s)", (migration.stem,), ) conn.commit() def main() -> int: conn = connect() todo = pending(conn) if not todo: print("Database is up to date.") return 0 print(f"Applying {len(todo)} migration(s)...") for migration in todo: run(conn, migration) print("Done.") return 0 if __name__ == "__main__": sys.exit(main()) `; F["scripts/seed.py"] = `#!/usr/bin/env python3 """Seed the database with demo data for local development.""" import random from db import connect PLANS = ["free", "pro", "enterprise"] def seed_users(conn, count: int = 25) -> None: with conn.cursor() as cur: for i in range(count): cur.execute( "INSERT INTO users (email, plan) VALUES (%s, %s)", (f"user{i}@example.com", random.choice(PLANS)), ) conn.commit() print(f"Seeded {count} users.") if __name__ == "__main__": seed_users(connect()) `; F["templates/dashboard.html"] = ` Dashboard

Console

`; F["config/app.json"] = `{ "name": "console", "env": "production", "features": { "billing": true, "newDashboard": true, "exportCsv": false }, "payment": { "gateway": "stripe", "currency": "EUR", "retryLimit": 3 }, "logging": { "level": "info", "channel": "stdout" } } `; F["composer.json"] = `{ "name": "blijnder/console", "type": "project", "require": { "php": ">=8.2", "psr/log": "^3.0", "psr/http-message": "^2.0", "nyholm/psr7": "^1.8" }, "require-dev": { "phpunit/phpunit": "^11.0", "phpstan/phpstan": "^1.11" }, "autoload": { "psr-4": { "App\\\\": "src/" } } } `; F["package.json"] = `{ "name": "console-frontend", "private": true, "type": "module", "scripts": { "dev": "vite", "build": "vite build", "test": "vitest run", "lint": "eslint ." }, "devDependencies": { "vite": "^5.3.0", "vitest": "^2.0.0", "typescript": "^5.5.0" } } `; F["README.md"] = `# Console Internal admin console. PHP API + small vanilla JS frontend. ## Getting started composer install npm install python scripts/migrate.py npm run dev ## Layout - \`src/\` PHP application code (PSR-4, \`App\\\` namespace) - \`public/\` Document root and frontend assets - \`scripts/\` Python maintenance + migration scripts - \`templates/\` Server-rendered HTML `; F[".env"] = `APP_ENV=production APP_DEBUG=false DATABASE_URL=postgres://localhost:5432/console PAYMENT_GATEWAY=stripe PAYMENT_CURRENCY=EUR LOG_LEVEL=info `; // ---- ORIGINAL (pre-edit) versions of changed files ---------------- const O = {}; O["src/Http/Controller/UserController.php"] = `users->find($id); if ($user === null) { return $this->json(['error' => 'user_not_found'], 404); } $balance = $this->payments->balanceFor($user); return $this->json([ 'id' => $user->id, 'email' => $user->email, 'plan' => $user->plan, 'balance' => $balance->toArray(), ]); } public function update(ServerRequestInterface $request, string $id): ResponseInterface { $user = $this->users->find($id); if ($user === null) { return $this->json(['error' => 'user_not_found'], 404); } $data = (array) $request->getParsedBody(); $user->fill($this->onlyFillable($data)); $this->users->save($user); return $this->json($user->toArray()); } private function onlyFillable(array $data): array { return array_intersect_key($data, array_flip(['email', 'plan'])); } } `; O["public/assets/app.js"] = `import { createStore } from './store.js'; import { mountRouter } from './router.js'; const store = createStore({ user: null, notifications: [], theme: 'dark', }); async function bootstrap() { const res = await fetch('/api/session'); if (res.ok) { const session = await res.json(); store.set('user', session.user); } mountRouter(document.querySelector('#app'), store); store.subscribe('notifications', renderToasts); } function renderToasts(list) { const host = document.querySelector('#toasts'); host.replaceChildren(...list.map((n) => { const el = document.createElement('div'); el.className = \\\`toast toast--\\\${n.level}\\\`; el.textContent = n.message; return el; })); } document.addEventListener('DOMContentLoaded', bootstrap); `; O["config/app.json"] = `{ "name": "console", "env": "production", "features": { "billing": true, "newDashboard": false }, "payment": { "gateway": "stripe", "currency": "EUR", "retryLimit": 3 }, "logging": { "level": "info", "channel": "stdout" } } `; O["scripts/migrate.py"] = `#!/usr/bin/env python3 """Run pending database migrations in order.""" import sys from pathlib import Path from db import connect, applied_migrations MIGRATIONS = Path(__file__).parent / "migrations" def pending(conn): done = applied_migrations(conn) files = sorted(MIGRATIONS.glob("*.sql")) return [f for f in files if f.stem not in done] def run(conn, migration: Path) -> None: sql = migration.read_text() print(f" -> applying {migration.stem}") with conn.cursor() as cur: cur.execute(sql) cur.execute( "INSERT INTO schema_migrations (version) VALUES (%s)", (migration.stem,), ) conn.commit() def main() -> int: conn = connect() todo = pending(conn) if not todo: print("Database is up to date.") return 0 for migration in todo: run(conn, migration) print("Done.") return 0 if __name__ == "__main__": sys.exit(main()) `; // PaymentService is a brand-new file (added) -> original is empty O["src/Service/PaymentService.php"] = ""; // LegacyUser was deleted -> original content, no working-tree version O["src/Model/LegacyUser.php"] = ` $this->id, 'email' => $this->email, 'plan' => $this->plan, ]; } } `; // ---- file tree (nested) ------------------------------------------- const tree = { name: "console", type: "dir", path: "", open: true, children: [ { name: "config", type: "dir", path: "config", open: false, children: [ { name: "app.json", type: "file", path: "config/app.json" }, ]}, { name: "public", type: "dir", path: "public", open: true, children: [ { name: "assets", type: "dir", path: "public/assets", open: true, children: [ { name: "app.js", type: "file", path: "public/assets/app.js" }, { name: "store.js", type: "file", path: "public/assets/store.js" }, { name: "styles.css", type: "file", path: "public/assets/styles.css" }, ]}, ]}, { name: "scripts", type: "dir", path: "scripts", open: false, children: [ { name: "migrate.py", type: "file", path: "scripts/migrate.py" }, { name: "seed.py", type: "file", path: "scripts/seed.py" }, ]}, { name: "src", type: "dir", path: "src", open: true, children: [ { name: "Http", type: "dir", path: "src/Http", open: true, children: [ { name: "Controller", type: "dir", path: "src/Http/Controller", open: true, children: [ { name: "UserController.php", type: "file", path: "src/Http/Controller/UserController.php" }, ]}, ]}, { name: "Service", type: "dir", path: "src/Service", open: true, children: [ { name: "PaymentService.php", type: "file", path: "src/Service/PaymentService.php" }, ]}, { name: "types", type: "dir", path: "src/types", open: false, children: [ { name: "api.ts", type: "file", path: "src/types/api.ts" }, ]}, ]}, { name: "templates", type: "dir", path: "templates", open: false, children: [ { name: "dashboard.html", type: "file", path: "templates/dashboard.html" }, ]}, { name: ".env", type: "file", path: ".env" }, { name: "composer.json", type: "file", path: "composer.json" }, { name: "package.json", type: "file", path: "package.json" }, { name: "README.md", type: "file", path: "README.md" }, ], }; // ---- line-based LCS diff ------------------------------------------ function buildDiff(origText, updText) { const a = origText === "" ? [] : origText.replace(/\n$/, "").split("\n"); const b = updText === "" ? [] : updText.replace(/\n$/, "").split("\n"); const n = a.length, m = b.length; const dp = Array.from({ length: n + 1 }, () => new Int32Array(m + 1)); for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]); const ops = []; let i = 0, j = 0; while (i < n && j < m) { if (a[i] === b[j]) { ops.push({ t: "same", a: i, b: j }); i++; j++; } else if (dp[i + 1][j] >= dp[i][j + 1]) { ops.push({ t: "del", a: i }); i++; } else { ops.push({ t: "add", b: j }); j++; } } while (i < n) { ops.push({ t: "del", a: i++ }); } while (j < m) { ops.push({ t: "add", b: j++ }); } const rows = [], left = [], right = [], split = []; const delSet = new Set(), addSet = new Set(); let add = 0, del = 0; for (const op of ops) { if (op.t === "same") { rows.push({ sign: " ", oldNo: op.a + 1, newNo: op.b + 1, text: a[op.a] }); } else if (op.t === "del") { rows.push({ sign: "-", oldNo: op.a + 1, newNo: null, text: a[op.a] }); delSet.add(op.a); del++; } else { rows.push({ sign: "+", oldNo: null, newNo: op.b + 1, text: b[op.b] }); addSet.add(op.b); add++; } } a.forEach((text, idx) => left.push({ no: idx + 1, text, mark: delSet.has(idx) ? "del" : null })); b.forEach((text, idx) => right.push({ no: idx + 1, text, mark: addSet.has(idx) ? "add" : null })); // aligned split rows (pair del/add blocks) let dbuf = [], abuf = []; const flush = () => { const k = Math.max(dbuf.length, abuf.length); for (let x = 0; x < k; x++) split.push({ l: dbuf[x] || null, r: abuf[x] || null }); dbuf = []; abuf = []; }; for (const op of ops) { if (op.t === "same") { flush(); split.push({ l: { no: op.a + 1, text: a[op.a] }, r: { no: op.b + 1, text: b[op.b] } }); } else if (op.t === "del") dbuf.push({ no: op.a + 1, text: a[op.a], mark: "del" }); else abuf.push({ no: op.b + 1, text: b[op.b], mark: "add" }); } flush(); return { rows, left, right, split, add, del }; } // ---- changed files ------------------------------------------------- const changeDefs = [ { path: "src/Service/PaymentService.php", status: "A" }, { path: "src/Http/Controller/UserController.php", status: "M" }, { path: "public/assets/app.js", status: "M" }, { path: "config/app.json", status: "M" }, { path: "scripts/migrate.py", status: "M" }, { path: "src/Model/LegacyUser.php", status: "D" }, ]; const diffs = {}; const changes = changeDefs.map((c) => { const orig = O[c.path] != null ? O[c.path] : ""; const upd = F[c.path] != null ? F[c.path] : ""; const d = buildDiff(orig, upd); diffs[c.path] = Object.assign(d, { deleted: c.status === "D", added: c.status === "A", original: orig, updated: upd, }); return { path: c.path, status: c.status, add: d.add, del: d.del, deleted: c.status === "D" }; }); window.PROJECT = { name: "console", branch: "feat/payments-balance", files: F, originals: O, tree, diffs, changes, }; })();