Compare commits

...

7 commits

Author SHA1 Message Date
lila
768ca24eb2 fix: remove unfrozen pnpm install from builder stages
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m3s
2026-04-26 09:03:23 +02:00
lila
091495c1db updating tasks 2026-04-26 08:46:54 +02:00
lila
0da8397940 feat: add root and route-level error boundaries 2026-04-26 08:45:18 +02:00
lila
e3d28e4127 updating issues 2026-04-24 18:34:43 +02:00
lila
4de2c40482 feat: add 404 catch-all route and NotFound page 2026-04-24 18:28:22 +02:00
lila
4fabde57bd adding prompt 2026-04-24 10:27:54 +02:00
lila
e9ba8d292d updating tasks 2026-04-24 10:21:06 +02:00
10 changed files with 190 additions and 19 deletions

View file

@ -22,9 +22,12 @@ CMD ["pnpm", "--filter", "api", "dev"]
# 4. build # 4. build
FROM base AS builder FROM base AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/api/package.json ./apps/api/
COPY packages/shared/package.json ./packages/shared/
COPY packages/db/package.json ./packages/db/
RUN pnpm install --frozen-lockfile
COPY . . COPY . .
RUN pnpm install
RUN pnpm --filter shared build RUN pnpm --filter shared build
RUN pnpm --filter db build RUN pnpm --filter db build
RUN pnpm --filter api build RUN pnpm --filter api build

View file

@ -21,9 +21,11 @@ CMD ["pnpm", "--filter", "web", "dev", "--host"]
# 4. Build # 4. Build
FROM base AS builder FROM base AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY apps/web/package.json ./apps/web/
COPY packages/shared/package.json ./packages/shared/
RUN pnpm install --frozen-lockfile
COPY . . COPY . .
RUN pnpm install
ARG VITE_API_URL ARG VITE_API_URL
ENV VITE_API_URL=$VITE_API_URL ENV VITE_API_URL=$VITE_API_URL
RUN pnpm --filter shared build RUN pnpm --filter shared build

View file

@ -0,0 +1,43 @@
import { Link } from "@tanstack/react-router";
export default function NotFound() {
return (
<section className="relative flex flex-col items-center justify-center py-24 text-center overflow-hidden">
<div className="absolute inset-0 -z-10">
<div className="absolute top-0 left-1/2 h-72 w-184 -translate-x-1/2 rounded-full bg-(--color-primary) opacity-[0.10] blur-3xl" />
<div className="absolute top-10 left-1/2 h-72 w-184 -translate-x-1/2 rounded-full bg-(--color-accent) opacity-[0.10] blur-3xl" />
</div>
<div className="-rotate-1 mb-4">
<span className="inline-flex items-center gap-2 rounded-full bg-(--color-surface) px-4 py-1 text-xs font-semibold tracking-widest uppercase text-(--color-accent) border border-(--color-primary-light)">
lost in translation
</span>
</div>
<h1 className="text-8xl font-black tracking-tight text-(--color-text) leading-none">
4
<span className="inline-block rotate-1 px-3 py-1 bg-(--color-primary) text-white rounded-xl">
0
</span>
4
</h1>
<p className="mt-6 text-lg font-medium text-(--color-text-muted) max-w-sm">
This page doesn't exist. Maybe it never did - or maybe you{" "}
<span className="text-(--color-accent) font-bold">
just guessed wrong
</span>
.
</p>
<div className="mt-8">
<Link
to="/"
className="px-7 py-3 rounded-full text-white font-bold text-sm bg-(--color-primary) hover:bg-(--color-primary-dark)"
>
Back to home
</Link>
</div>
</section>
);
}

View file

@ -0,0 +1,56 @@
import { Link } from "@tanstack/react-router";
interface RootErrorProps {
error: Error;
reset: () => void;
}
export default function RootError({ error, reset }: RootErrorProps) {
return (
<section className="relative flex flex-col items-center justify-center min-h-screen text-center overflow-hidden px-6">
<div className="absolute inset-0 -z-10">
<div className="absolute top-0 left-1/2 h-72 w-184 -translate-x-1/2 rounded-full bg-(--color-primary) opacity-[0.10] blur-3xl" />
<div className="absolute top-10 left-1/2 h-72 w-184 -translate-x-1/2 rounded-full bg-(--color-accent) opacity-[0.10] blur-3xl" />
</div>
<div className="-rotate-1 mb-4">
<span className="inline-flex items-center gap-2 rounded-full bg-(--color-surface) px-4 py-1 text-xs font-semibold tracking-widest uppercase text-(--color-accent) border border-(--color-primary-light)">
something went wrong
</span>
</div>
<h1 className="text-5xl md:text-6xl font-black tracking-tight text-(--color-text) leading-[1.05]">
Unexpected{" "}
<span className="inline-block rotate-1 px-3 py-1 bg-(--color-primary) text-white rounded-xl">
error
</span>
</h1>
<p className="mt-6 text-lg font-medium text-(--color-text-muted) max-w-sm">
Something crashed. This has been noted {" "}
<span className="text-(--color-accent) font-bold">it's not you</span>.
</p>
{import.meta.env.DEV && (
<pre className="mt-4 max-w-xl w-full text-left text-xs bg-(--color-surface) border border-(--color-primary-light) rounded-2xl p-4 overflow-auto text-(--color-text-muted)">
{error.message}
</pre>
)}
<div className="mt-8 flex gap-3 flex-wrap justify-center">
<button
onClick={reset}
className="px-7 py-3 rounded-full text-white font-bold text-sm bg-(--color-primary) hover:bg-(--color-primary-dark)"
>
Try again
</button>
<Link
to="/"
className="px-7 py-3 rounded-full font-bold text-sm text-(--color-primary) border-2 border-(--color-primary) hover:bg-(--color-surface)"
>
Back to home
</Link>
</div>
</section>
);
}

View file

@ -0,0 +1,49 @@
interface RouteErrorProps {
error: Error;
reset: () => void;
}
export default function RouteError({ error, reset }: RouteErrorProps) {
return (
<section className="relative flex flex-col items-center justify-center py-24 text-center overflow-hidden">
<div className="absolute inset-0 -z-10">
<div className="absolute top-0 left-1/2 h-72 w-184 -translate-x-1/2 rounded-full bg-(--color-primary) opacity-[0.10] blur-3xl" />
<div className="absolute top-10 left-1/2 h-72 w-184 -translate-x-1/2 rounded-full bg-(--color-accent) opacity-[0.10] blur-3xl" />
</div>
<div className="-rotate-1 mb-4">
<span className="inline-flex items-center gap-2 rounded-full bg-(--color-surface) px-4 py-1 text-xs font-semibold tracking-widest uppercase text-(--color-accent) border border-(--color-primary-light)">
something went wrong
</span>
</div>
<h1 className="text-5xl font-black tracking-tight text-(--color-text) leading-[1.05]">
This page{" "}
<span className="inline-block rotate-1 px-3 py-1 bg-(--color-primary) text-white rounded-xl">
crashed
</span>
</h1>
<p className="mt-6 text-lg font-medium text-(--color-text-muted) max-w-sm">
Something went wrong loading this page.{" "}
<span className="text-(--color-accent) font-bold">Try again</span> or
head back home.
</p>
{import.meta.env.DEV && (
<pre className="mt-4 max-w-xl w-full text-left text-xs bg-(--color-surface) border border-(--color-primary-light) rounded-2xl p-4 overflow-auto text-(--color-text-muted)">
{error.message}
</pre>
)}
<div className="mt-8 flex gap-3 flex-wrap justify-center">
<button
onClick={reset}
className="px-7 py-3 rounded-full text-white font-bold text-sm bg-(--color-primary) hover:bg-(--color-primary-dark)"
>
Try again
</button>
</div>
</section>
);
}

View file

@ -7,8 +7,8 @@ const Hero = () => {
return ( return (
<section className="relative pt-10 md:pt-16 pb-10 md:pb-14"> <section className="relative pt-10 md:pt-16 pb-10 md:pb-14">
<div className="absolute inset-0 -z-10"> <div className="absolute inset-0 -z-10">
<div className="absolute -top-24 left-1/2 h-72 w-[46rem] -translate-x-1/2 rounded-full bg-(--color-primary) opacity-[0.10] blur-3xl" /> <div className="absolute -top-24 left-1/2 h-72 w-184 -translate-x-1/2 rounded-full bg-(--color-primary) opacity-[0.10] blur-3xl" />
<div className="absolute -top-10 left-1/2 h-72 w-[46rem] -translate-x-1/2 rounded-full bg-(--color-accent) opacity-[0.10] blur-3xl" /> <div className="absolute -top-10 left-1/2 h-72 w-184 -translate-x-1/2 rounded-full bg-(--color-accent) opacity-[0.10] blur-3xl" />
</div> </div>
<div className="grid items-center gap-10 md:grid-cols-2"> <div className="grid items-center gap-10 md:grid-cols-2">
@ -28,9 +28,11 @@ const Hero = () => {
</h1> </h1>
<p className="mt-5 text-lg md:text-xl font-medium text-(--color-text-muted) max-w-xl mx-auto md:mx-0"> <p className="mt-5 text-lg md:text-xl font-medium text-(--color-text-muted) max-w-xl mx-auto md:mx-0">
A word appears. You pick the translation. You score points. A word appears. You pick the translation. You score points. Then you
Then you queue up a room and{" "} queue up a room and{" "}
<span className="text-(--color-accent) font-bold">beat friends</span>{" "} <span className="text-(--color-accent) font-bold">
beat friends
</span>{" "}
in real time. in real time.
</p> </p>

View file

@ -1,6 +1,8 @@
import { createRootRoute, Outlet } from "@tanstack/react-router"; import { createRootRoute, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
import Navbar from "../components/navbar/NavBar"; import Navbar from "../components/navbar/NavBar";
import NotFound from "../components/NotFound";
import RootError from "../components/RootError";
const RootLayout = () => { const RootLayout = () => {
return ( return (
@ -14,4 +16,8 @@ const RootLayout = () => {
); );
}; };
export const Route = createRootRoute({ component: RootLayout }); export const Route = createRootRoute({
component: RootLayout,
notFoundComponent: NotFound,
errorComponent: RootError,
});

View file

@ -4,6 +4,7 @@ import type { GameSession, GameRequest, AnswerResult } from "@lila/shared";
import { QuestionCard } from "../components/game/QuestionCard"; import { QuestionCard } from "../components/game/QuestionCard";
import { ScoreScreen } from "../components/game/ScoreScreen"; import { ScoreScreen } from "../components/game/ScoreScreen";
import { GameSetup } from "../components/game/GameSetup"; import { GameSetup } from "../components/game/GameSetup";
import RouteError from "../components/RouteError";
import { authClient } from "../lib/auth-client"; import { authClient } from "../lib/auth-client";
type GameStartResponse = { success: true; data: GameSession }; type GameStartResponse = { success: true; data: GameSession };
@ -127,6 +128,7 @@ function Play() {
export const Route = createFileRoute("/play")({ export const Route = createFileRoute("/play")({
component: Play, component: Play,
errorComponent: RouteError,
beforeLoad: async () => { beforeLoad: async () => {
const { data: session } = await authClient.getSession(); const { data: session } = await authClient.getSession();
if (!session) { if (!session) {

View file

@ -8,12 +8,6 @@ Labels: `[feature]` `[infra]` `[security]` `[ux]` `[debt]`
Things that are actively in progress or should be picked up immediately. Mostly operational risk and the remaining phase 7 hardening work. Things that are actively in progress or should be picked up immediately. Mostly operational risk and the remaining phase 7 hardening work.
- **404 and redirect handling** `[ux]`
Unknown routes return raw errors. Add a catch-all route on the frontend for client-side 404s. Consider a Caddy fallback for unrecognized subdomains.
- **React error boundaries** `[ux]`
Catch and display runtime errors gracefully instead of crashing the entire app.
- **Pin dependencies in package.json** `[debt]` `[infra]` - **Pin dependencies in package.json** `[debt]` `[infra]`
Unpinned deps in a CI/CD pipeline are a real risk. Pin all versions to exact values to prevent unexpected breakage on build. Unpinned deps in a CI/CD pipeline are a real risk. Pin all versions to exact values to prevent unexpected breakage on build.
@ -29,9 +23,6 @@ Things that are actively in progress or should be picked up immediately. Mostly
- **Conditionally register OAuth providers** `[debt]` - **Conditionally register OAuth providers** `[debt]`
Better Auth logs warnings when social providers are registered without credentials (`Social provider google is missing clientId or clientSecret`). Instead of registering all providers unconditionally, only add a provider to the config when its credentials are present in the environment. Keeps local dev clean for contributors who don't have OAuth apps set up. Better Auth logs warnings when social providers are registered without credentials (`Social provider google is missing clientId or clientSecret`). Instead of registering all providers unconditionally, only add a provider to the config when its credentials are present in the environment. Keeps local dev clean for contributors who don't have OAuth apps set up.
- **Multiplayer GameService unit tests** `[debt]`
round evaluation, scoring, tie-breaking, timeout handling
--- ---
## next ## next
@ -117,6 +108,9 @@ Directionally right, timing is unclear. Revisit when the next/now work is done.
Shipped milestones, newest first. Shipped milestones, newest first.
- **04 - 2026 - React error boundaries** - Catch and display runtime errors gracefully instead of crashing the entire app.
- **04 - 2026 - 404 and redirect handling** - Unknown routes return raw errors. Add a catch-all route on the frontend for client-side 404s.
- **04 - 2026 - Multiplayer GameService unit tests** - round evaluation, scoring, tie-breaking, timeout handling
- **04 - 2026 - Security headers with helmet** - Add helmet middleware to set secure HTTP response headers. - **04 - 2026 - Security headers with helmet** - Add helmet middleware to set secure HTTP response headers.
- **04 - 2026 - Rate limiting on API endpoints** - At minimum: auth endpoints (brute force prevention) and game endpoints (spam prevention) - **04 - 2026 - Rate limiting on API endpoints** - At minimum: auth endpoints (brute force prevention) and game endpoints (spam prevention)
- **04 - 2026 — Migrations in deploy pipeline** — Drizzle migrate runs as a CI/CD step before the API container restarts - **04 - 2026 — Migrations in deploy pipeline** — Drizzle migrate runs as a CI/CD step before the API container restarts

View file

@ -1,5 +1,19 @@
# notes # notes
## prompt
ive attached the readme of my project. this is my current task:
task description.
1. tell me which files you need to see to get the full context of the problem
2. walk me text-only through the problem and the solution
3. if we need to update multiple files: lets go through them one by one, no matter how many files
4. if we go through a file, we'll do it slowly section by section, no matter how many sections
5. how to name the current feature branch? also tell me when its time to git commit and provide a commit message
6. if we have multiple options to do something, also always provide options that reflect current industry standards and best practices
## tasks ## tasks
- **IMPORTANT** db migrations have to be part of the deployment pipeline!!!!!!!!!!!!!!!!!! - **IMPORTANT** db migrations have to be part of the deployment pipeline!!!!!!!!!!!!!!!!!!