feat(academy): add the chapter-wise worksheet site - #350
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
academy.byteveda.org joins the canonical list, so the new app derives its site config the same way main and docs do.
A fifth app at academy.byteveda.org: a stocked inventory of CBSE and ICSE worksheets for Class 9-10, plus a made-to-order quote for chapters we do not hold. Buying is not open yet, so the request is for a free sample rather than a purchase, and no price appears in it. The browser posts what was asked for and never a rupee; the server re-prices from the same functions the quote panel uses, and mails the work order through Resend.
Installs browsers when the academy test task is affected, collects its Playwright report, and adds it to the Lighthouse matrix.
kartikeya-27
force-pushed
the
feat/academy
branch
from
September 19, 2026 10:38
d3980a3 to
b59653a
Compare
Nothing in academy references the blog or console tables and nothing references it, so it gets a namespace rather than a corner of public. The unique constraint on the email is the one-free-sample rule. The lookup beside it is only there to turn the common case into a sentence; two requests racing each other both pass it.
A bitmap rather than an inline SVG: the mark is a multi-tone gradient, so it cannot take currentColor. No tile behind it either — the bracket was a white glyph that needed one, this one brings its own colour. The supplied file had 432,046 pixels at alpha 250-254 and 1,103 at 255, so the whole mark was faintly translucent; packages/ui/brand holds the original untouched and records how the asset was derived from it.
The closing section repeated the hero's heading and both its buttons, and the 01/02/03 three-up said what the delivery steps say lower down.
With no payment the address is the whole of the price, so the request is capped at one chapter and the server re-checks it: the page is a single-select, validateOrder refuses a second item, and the claim is taken before the mail goes out and released again if it fails. The table says so too — Pick becomes Swap once something is chosen, and the price column shows the list figure struck through beside Free. Carries the layout fixes that came with it: the full-bleed bands keep their contents in the page's column, and the quote panel is narrower and tighter than the form beside it.
next-env.d.ts carries the next/image-types reference, but it is generated by next build and gitignored — so a typecheck that runs before any build, which is what CI does, had no idea what a PNG was. The reference belongs in the file that imports one.
kartikeya-27
approved these changes
Sep 19, 2026
This branch was successfully deployed
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.
Adds
apps/academy— a fifth app foracademy.byteveda.org, built from the Design-Claude prototype in../Assignment delivery platform design/. It sells chapter-wise CBSE and ICSE worksheets for Class 9 and 10: a stocked inventory of 36 chapters with answer keys, and a made-to-order quote for anything not on the shelf.The prototype's structure is the spec; its mint-on-green palette is not. The app uses the shared Midnight tokens from
@byteveda/uiso it reads as part of the family.Buying is not open yet
Payment is deliberately unbuilt. Rather than ship a dead Pay button, the request is for a free sample sheet — the visitor picks a chapter, leaves an email, and gets the sheet back by hand within 24 hours. Every place the prototype printed a total now prints a "coming soon" notice instead: under the inventory bar, and on
/sample.No rupee figure appears anywhere in the request flow — not on the line, not on the submit button, not in the receipt mail. Prices stay where they inform rather than charge: the inventory table, where the list figure is struck through beside Free, and the live quote panel. The one exception is the team's work order, which keeps the figure under a "List value" heading, because the person filling the request is the only reader who wants it.
One free sample per email address
A sample costs nothing, so the email address is the whole of the price.
academy.sample_requestsin@byteveda/dbis both the record of what was sent and the lock that stops the catalogue being collected one free sheet at a time.public. Nothing in academy references the blog or console tables and nothing references it.pgSchema("academy")says so in the one place that cannot drift.hasClaimedSampleruns first only to turn the common case into a sentence; two requests racing each other both pass it, and the insert decides which is second.claimSamplereads Postgres23505as "already had one".DATABASE_URL→ 503. The cap cannot be enforced without it, and being briefly offline beats giving the catalogue away.ResolvedOrder.line, a single-select store, andvalidateOrderrefusing a second item. The table says so too —PickbecomesSwaponce something is chosen.Migration
0007_academy_samplesis additive: one new schema, one enum, one table. Nothing existing is touched.Prices never cross the wire
The browser posts
{kind: "chapter", chapterId}or{kind: "custom", request}and never a price.validateOrderre-derives every rupee server-side from the same pure functions the quote panel uses, so there is one pricing rule and a tampered payload cannot buy a chapter pack for one rupee. That stays true even though nothing is charged today — it is the rule that has to be right on the day payment opens, not something to wire up then. An e2e test asserts the exact request body.Layout
/— hero, inventory table with filters and paging, made-to-order form with a live quote, delivery steps./sample— the request: the picked chapter, email, send. Its own route rather than a modal, so the pick is persisted tosessionStorageand survives a reload or a pasted link. NotlocalStorage: a pick resurrected next week would name a catalogue that has moved on. It is re-validated throughparseCartItemon restore, and areadyflag stops the page flashing "nothing picked yet" at someone who has already chosen.POST /api/orders— validates, records the claim, then mails a work order toACADEMY_ORDER_INBOXand a price-free receipt to the visitor through Resend.The vocabulary is split on purpose:
src/features/sample/*is the UI,src/lib/orders/*is the server-side work order that comes back into its own when payment lands.src/components/ui/*(field, input, listbox select, segmented radio group) are app-local because@byteveda/uihas no form primitives. Promoting them needs a rename first —.inputand.fieldwould collide withapps/admin. The select is a custom combobox over a listbox rather than a native<select>, whose option popup ignoresdata-themeand renders unreadable on dark.Full-bleed bands keep their contents in the page's column. The made-to-order band and the three-up are the only sections whose rules reach the screen edges, and they were bleeding their contents too — on a wide screen the form began 331px left of the nav, the inventory and the footer. A
.band-innercapped at--maxwfixes it, and an e2e test measures both ends against.inventory-head, which is a plain.wrapand therefore is the column.A new brand mark
Wordmarkin@byteveda/uiswaps the bracket glyph for the leaf logo, so all five sites pick it up. No tile behind it — the bracket was a white glyph that needed the accent square to sit on; this one brings its own colour.The supplied artwork had 432,046 pixels at alpha 250–254 and only 1,103 at 255, so the whole mark was faintly translucent and picked up whatever sat behind it.
packages/ui/brand/holds the original untouched and records how the shipped asset was derived from it.Worth a second opinion: the mark averages 3.10:1 against the dark background and 5.87:1 against the light one. It clears the 3:1 WCAG asks of a graphical object, but the dark stem does sink into the dark theme and only the pale leaves carry it.
apps/admin's rail, login and email pages draw their own.marktile and never went throughWordmark, so they still show the bracket.Notes for review
RESEND_API_KEYandDATABASE_URLare both required at runtime; without either the route returns 503 rather than silently dropping the request or handing out a second sample.ACADEMY_ORDER_FROMandACADEMY_ORDER_INBOXare optional overrides, declared inturbo.json'spassThroughEnv.:6543).:5432is session mode and is fordb:migrateonly.apps/academy, theacademyCNAME, and the sender domain verified in Resend.apps/admin/src/lib/email/webhook.tsis the pattern).Verification
22 unit tests in
apps/academy, 31 in@byteveda/db, 26 Playwright tests (desktop plus Pixel 7 and iPhone 14), Biome clean, all 22 turbo tasks green./and/sampleboth prerender static. Screenshotted in both themes.The claim layer was also exercised against the real database — first claim, repeat claim under a different case, release — which is how the
23505handling turned out to be wrong: drizzle wraps thepgerror in aDrizzleQueryError, so a check against the top-levelcodenever matched and "you already have one" surfaced as a 500.isUniqueViolationnow walks the cause chain, and a unit test pins both shapes.