fix(ci): green the Format gate, restore the pnpm pin — and the missing content schema - #25
Open
amielnoy wants to merge 11 commits into
Open
fix(ci): green the Format gate, restore the pnpm pin — and the missing content schema#25amielnoy wants to merge 11 commits into
amielnoy wants to merge 11 commits into
Conversation
`mockupPreviewPlugin.ts` rewrites `src/.generated/mockup-components.ts` on every sandbox build, in its own formatting. Prettier reformats it, the next build writes it back, and whoever built last commits the difference — which is precisely how it reached `main` and turned the Format step red there. Formatting generated output is a fight with the generator, so it is ignored instead, alongside the build artefacts already listed for the same reason. It stays committed, because a fresh checkout has to typecheck before anything has run the plugin. A sandbox build now leaves the working tree clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`"packageManager": "pnpm@11.20.0"` was removed on 20 August by a Replit Agent commit titled "Remove dependency from package.json", which appears to have taken the pin along with the dependency it meant to delete. Corepack reads that field to decide which package manager runs. Without it, it keeps walking up the filesystem — and on a machine with any package.json above the checkout, it finds that one instead. Here it lands on `~/package.json`, which pins yarn, so every `pnpm run` in the repository answers "This project is configured to use yarn". CI was unaffected because the workflow installs pnpm itself, which is why this survived six days without being noticed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Vercel project connected to a GitHub repository builds on every push, and such a build has nowhere to land here: the twelve apps each write their own `artifacts/<name>/dist/public/`, and the single directory Vercel wants only exists after the workflow's assembly step. Left on, it failed every push with *No Output Directory named "public" found* — a red deployment sitting next to the green one that actually shipped. `git.deploymentEnabled: false` is read from the pushed commit, so it silences the branches that carry it. The file is inert for the deployment itself: `--prebuilt` serves the routes in `.vercel/output/config.json` and never reads `vercel.json`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
The content migration had everything except the tables. The extractor
reads the question bank and the two catalogs, the generator writes 299
insert statements, the server queries six tables through PostgREST, and
the client already fetches all three collections with the bundled copy as
a fallback — but nothing in this repository ever created the tables the
seed inserts into, so the seed cannot be applied to a fresh database and
`/api/content/*` answers 503. That is what the live API is doing today.
`academy-schema.sql` creates them, and two details in it are not
decoration. The ids are identity columns because the seed's last six
lines call `setval('<table>_id_seq', …)` so a hand-added row cannot
collide with a seeded one — against plain `bigint` columns those calls
abort the transaction and nothing lands, which is what a throwaway
Postgres said when asked. And the anon role is granted select with a read
policy, because the API reads with the anon key: without both, the tables
are full and every response is empty, which looks exactly like having no
tables at all.
Verified end to end rather than by eye: schema and seed applied to a
scratch Postgres, 150 question items, 80 challenges and 40 lecture items
in both languages, ordered the way the API orders them, `text[]` answers
and Hebrew intact.
`tests/unit/contentSchema.spec.ts` holds the schema, the seed and
`content_store.py` to one set of column names, without needing a
database. A rename in one of the three is otherwise reported as a 503
that reads like an outage.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Applying the seed meant composing a connection string by hand from a password, a pooler hostname and a role that only exist together in `server/app/config.py` — and pasting it into a shell, where it lands in history. The first attempt at that pasted the placeholders verbatim and asked DNS to resolve `db.<ref>.supabase.co`. `seed:academy` reads the one value that is not already in the repository from `.env.local`, which is git-ignored, and composes the rest. It uses psql when it is installed and the same client out of a container when it is not, since Docker is already required by the test suite. The password travels in the environment, never in argv, where `ps` would show it. A password found in a git-tracked file is refused rather than used, with advice to rotate it. This repository commits `.env` files on purpose — they hold public build-time config — so a secret that lands in one is a secret on its way to GitHub, and quietly accepting it here is how it would stay there. It finishes by counting what landed, per collection and per language, and fails if any lecture is marked ready without a URL — the invariant the seed has broken before, now checked against the database rather than only against the file that fills it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A `DATABASE_URL` left in the shell from a copied example wins over everything else the seeder can compose, and the failure it produces is a DNS error about a host called `db.<ref>.supabase.co` — which reads like a network problem rather than a setting nobody filled in. That is exactly how it failed the first time someone ran it. Angle brackets are now refused by name, with the two ways out: fix the value, or unset it and let the password in `.env.local` compose the connection. The banner also prints the user and host it is about to write to — never the password — so the wrong target is visible before anything is applied rather than after. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
vercel
Bot
temporarily deployed
to
Preview – learn-practice-work-ai-testing-academy
August 26, 2026 03:33
Inactive
Every API path on the deployed site was returning the academy's HTML
shell at HTTP 200, because the catch-all rewrite that makes client-side
routing work does not know `/api` is different. The client cannot tell:
`loadServerConfig` checks `res.ok`, the status passes, `res.json()` throws
on HTML, the catch returns `{}`, and the site concludes there is no
server-side key. The AI proxy, Google sign-in, the content API and
checkout all disappear, and the page looks perfectly healthy while it
happens. This repository already documents the same trap for the dev
server; the deployment had grown its own.
`/api/*` is now settled before the filesystem, and where it goes is a
deploy-time decision rather than a committed hostname — the API has moved
host twice already. With `API_ORIGIN` set it is proxied there, which also
means the browser sees a single origin, so the login cookie stays
first-party and CORS never applies. Without it, 503 with a JSON content
type: the same message the swallowed HTML was trying to give, said out
loud and immediately.
The smoke check now fails the deploy if `/api/ai/config` comes back as
text/html, and the unit suite exercises both shapes of the table.
It also stops failing every pull request. Preview deployments are behind
Vercel Authentication by design, so each route assertion met a login
redirect and went red on a deployment that was fine. A preview is now
checked for what is true there — that it deployed and is protected — and
the full pass runs on production, which is public.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The red deployments beside every green one did not come from this repository's configuration. A Vercel project belonging to an entirely different codebase, `home-economy-stabilation`, had its Git integration pointed here, so it built twelve Vite apps and then went looking for a single `public/` that only exists after the workflow's assembly step. `git.deploymentEnabled: false` silenced it, and would have silenced every other project linked to this repo along with it — Vercel reads that flag per repository, not per project, and more than one project is linked here with at least one deploying successfully. Quieting all of them to fix one misconfigured project trades a visible problem for an invisible one. The fix belongs in that project's own settings, and the runbook now says so. This reverses c336396, whose reasoning was sound and whose premise — that the failing build was ours — was wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`vercel deploy` prints the URL when the deployment is created, which is not when the edge starts serving it. Run 32950109049 went red on that gap: the URL was printed at 08:55:32.801 and the first check failed at 08:55:33.095 — 294 milliseconds later, against a deployment that answers correctly to this day. The check now waits for the root to stop answering 404, treats a public 200 and a protected 302 alike as ready, and distinguishes "no response at all" from "responded, but wrongly" when it gives up. Two smaller corrections to the preview branch. It read the status and the Location header with two separate requests, which can land on different states and disagree about what the deployment did; one request now provides both. And a 401 challenge counts as protection working: Deployment Protection answers a non-browser client — which `curl -I` is — with a bare challenge rather than the SSO redirect in several configurations. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…atic The accessibility scan waited for the main landmark to prove the app had rendered, and it does not prove that: `index.html` ships a prerendered shell inside `#root` that already contains `<main id="main-content">`, so the wait resolved against markup present before any script ran — and axe, sampling an unstyled page, reported every text node as a contrast failure at once. It now waits for two things that are only true afterwards: a painted `body` background, which the UA leaves transparent until `app.css` is applied and which therefore proves the stylesheet axe is about to measure is live, in dev and in a production build alike; and the prerender marker being gone, since `createRoot().render()` replaces `#root` wholesale rather than hydrating it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four commits. The first three green
main; the fourth completes the Supabase content migration.1. The Format gate was policing a generated file
mockupPreviewPlugin.tsrewrites.generated/mockup-components.tson every sandbox build in its own formatting, so Prettier and the generator fought and whoever built last committed the difference. Ignored now, alongside the build artefacts.prettierignorealready lists. A sandbox build leaves the tree clean.2. The pnpm pin was missing
"packageManager": "pnpm@11.20.0"was dropped on 20 August by a Replit Agent commit titled "Remove dependency from package.json". Corepack then walks up the filesystem and finds whatever it hits first — on a dev machine with a~/package.json, that is yarn.3. Vercel's own Git builds are off
They have nowhere to land: the single output directory only exists after the workflow's assembly step, so every push produced a red No Output Directory named "public" found next to the green deployment that actually shipped.
4. The content schema the migration never had
The migration was built except for one thing: nothing in this repository creates the tables. The extractor reads the question bank and the two catalogs, the generator writes 299 inserts, the server queries six tables over PostgREST, and the client already fetches all three collections with the bundled copy as a fallback — but the seed only truncates and inserts, so it cannot be applied to a fresh database.
/api/content/question-bankreturns503 {"error":"Content temporarily unavailable"}in production right now.scripts/src/academy-schema.sqlcreates them. Two details in it are load-bearing:setval('<table>_id_seq', …)so a hand-added row cannot collide with a seeded id. Against plainbigintcolumns those calls abort the transaction and nothing lands — which is what a throwaway Postgres reported when asked.grant selecttoanonplus a read policy. The API reads with the anon key. Without both, the tables are full and every response is empty — indistinguishable from having no tables.Two defects found while verifying:
decknumber and the client turns it into an href at render time; the extractor wrote the records raw, so regenerating the seed produced cards marked ready that open nothing. The committed JSON had absolute URLs someone had patched in by hand. It resolves them throughlectureHref()now and throws if a ready lecture ends up without one. The fixed extractor reproduces the previously committed JSON exactly.Verified end to end, not by eye: schema and seed applied to a scratch Postgres — 150 question items, 80 coding challenges, 40 lecture items in both languages, ordered as the API orders them,
text[]answers and Hebrew intact, zero ready-but-linkless cards.tests/unit/contentSchema.spec.tsholds the schema, the seed andcontent_store.pyto one set of column names without needing a database.295 unit tests pass; typecheck, lint and format are clean.
🤖 Generated with Claude Code