Context
At commit 3531916, src/pages/index.astro:21-32 does:
const projects = await getProjectsByUserId(user.id);
const projectsWithPreviewUrls = await Promise.all(
projects.map(async (project) => {
const urls = await getProjectRuntimeUrls(project);
...
}),
);
getProjectRuntimeUrls (src/server/projects/projectUrls.ts:39-41) performs Tailscale URL resolution (network/IO) per project. With N projects that's 1 query + 2N resolutions blocking SSR of the dashboard — the page everyone lands on. This also brushes against the AGENTS.md rule: "Prefer client-side fetching/hydration for large or potentially unbounded payloads".
Related minor instance in the same theme: src/pages/monitor.astro:53-56 selects ALL non-deleted projects without an owner filter for container mapping. Harmless single-user, but it violates the same rule and will be wrong post-multi-user — add the owner filter while you're here.
What to do
Pick the cheapest approach that fits the codebase:
- Preferred: cache the Tailscale base URL/hostname resolution (it's instance-wide, not per-project) so
getProjectRuntimeUrls becomes pure string construction per project. Look inside src/server/projects/projectUrls.ts and src/server/tailscale/ to see what's actually network-bound vs derivable; there is an existing TTL-cache utility pattern in src/server/cache/ to follow.
- Alternatively, SSR only the project list and hydrate preview URLs client-side (repo rule: SSE/fetch, no polling).
Also: add eq(projects.ownerUserId, user.id) to the monitor.astro projects query.
Acceptance criteria
- Dashboard SSR does at most O(1) network calls for URL resolution regardless of project count (verify by logging or timing with several projects).
- Preview/production URLs shown on the dashboard are unchanged for running projects.
pnpm check passes (see verification-baseline issue).
Context
At commit
3531916,src/pages/index.astro:21-32does:getProjectRuntimeUrls(src/server/projects/projectUrls.ts:39-41) performs Tailscale URL resolution (network/IO) per project. With N projects that's 1 query + 2N resolutions blocking SSR of the dashboard — the page everyone lands on. This also brushes against the AGENTS.md rule: "Prefer client-side fetching/hydration for large or potentially unbounded payloads".Related minor instance in the same theme:
src/pages/monitor.astro:53-56selects ALL non-deleted projects without an owner filter for container mapping. Harmless single-user, but it violates the same rule and will be wrong post-multi-user — add the owner filter while you're here.What to do
Pick the cheapest approach that fits the codebase:
getProjectRuntimeUrlsbecomes pure string construction per project. Look insidesrc/server/projects/projectUrls.tsandsrc/server/tailscale/to see what's actually network-bound vs derivable; there is an existing TTL-cache utility pattern insrc/server/cache/to follow.Also: add
eq(projects.ownerUserId, user.id)to themonitor.astroprojects query.Acceptance criteria
pnpm checkpasses (see verification-baseline issue).