Compare commits
No commits in common. "768ca24eb21b1032ae2783d2c045a15bc7c33216" and "397164284805b74894caf36cc433ecc6faa79408" have entirely different histories.
768ca24eb2
...
3971642848
10 changed files with 19 additions and 190 deletions
|
|
@ -22,12 +22,9 @@ CMD ["pnpm", "--filter", "api", "dev"]
|
||||||
# 4. build
|
# 4. build
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,9 @@ CMD ["pnpm", "--filter", "web", "dev", "--host"]
|
||||||
# 4. Build
|
# 4. Build
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -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-184 -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-[46rem] -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 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>
|
</div>
|
||||||
|
|
||||||
<div className="grid items-center gap-10 md:grid-cols-2">
|
<div className="grid items-center gap-10 md:grid-cols-2">
|
||||||
|
|
@ -28,11 +28,9 @@ 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. Then you
|
A word appears. You pick the translation. You score points.
|
||||||
queue up a room and{" "}
|
Then you queue up a room and{" "}
|
||||||
<span className="text-(--color-accent) font-bold">
|
<span className="text-(--color-accent) font-bold">beat friends</span>{" "}
|
||||||
beat friends
|
|
||||||
</span>{" "}
|
|
||||||
in real time.
|
in real time.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
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 (
|
||||||
|
|
@ -16,8 +14,4 @@ const RootLayout = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({ component: RootLayout });
|
||||||
component: RootLayout,
|
|
||||||
notFoundComponent: NotFound,
|
|
||||||
errorComponent: RootError,
|
|
||||||
});
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ 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 };
|
||||||
|
|
@ -128,7 +127,6 @@ 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) {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@ 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.
|
||||||
|
|
||||||
|
|
@ -23,6 +29,9 @@ 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
|
||||||
|
|
@ -108,9 +117,6 @@ 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
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,5 @@
|
||||||
# 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!!!!!!!!!!!!!!!!!!
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue