Repository navigation
feat(courses): any three courses free, a course catalog, and paying on the site - #1632
Conversation
…n the site Owner, 2026-10-13: readers could not subscribe -- zero course keys were minted since the ladder went live, because the second course sent them to the Telegram bot and they stopped there. - Rules VERSION 2 vendored from gHashTag/t27 (specs/ladder/course-access.t27, logic and gen-ts regenerated): any three courses free, the club from the fourth on, no rung sends a reader to the bot. - #/courses: every course on one page, each card saying what a press will do (yours / free / club), the count of free courses left, and the one lesson to continue. "Courses" stands in the header instead of nine lines inside "Pages". - A locked course keeps its name, description and breadcrumbs (Courses > course > module > lesson); the gate used to replace them. - The paid gate takes the club in roubles on the site through the render's /api/course/checkout (Robokassa, one payment, no auto-renewal), comes back with ?order= and opens the course once the render has the payment. Russian readers only; without a price on the render the gate keeps the bot button. - Every new word is in specs/ladder/course-ladder.t27 and its Russian bundle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary by t27-beesChecked by the t27 compilert27c parse, parse-complete and typecheck on each changed .t27 file at
Read by the modelWorth a look
Read: 13 files, +610 -38, the whole diff. Head |
Why
The owner (2026-10-13) reported that readers could not subscribe. Production showed why:
The owner also asked for:
The owner approved hand-written TS for this work (
owner-approved-foreign).What
courseAccess.logic.tswas rebuilt from the new spec. Compared over 8448 inputs, it differs from the old build only atstepOf(1), which changed from 1 to 0.courseAccess.gen.tschanges onlyVERSION = 2.#/coursescatalog:step_of, not a hard-coded 3.GET /api/course/checkout. When it returnsonwith a rouble price, Russian-language readers get an email field and a "Pay N roubles" button.POST /api/course/checkoutand redirects to Robokassa. Robokassa returns the reader to#/<route>?order=<id>./api/course/checkout/orderuntil the render has the payment, then stores the paid key and opens the course.specs/ladder/course-ladder.t27, with Russian ini18n/course-ladder.ru.json.Checks
check:course-ladder: up to date, 43 words in 2 languages.test:course-ladder,test:course,check:courseandcheck:course-pagespass.qa/og-preview-contract,qa/docs-spec-contractandcheck:ariapass.vite buildsucceeds.tsc -breports no errors in the touched files.BASEin Navigation andsetKeyin an effect in Lesson.Browser check on a local dev server:
?order=, then "Waiting for the payment...", then "Payment received", with the course open and the key stored.{ "version": 1, "head_sha": "15367b74a5a68a916b1d2b756aa2f1904d34735b", "summary": "Readers could not subscribe: zero course keys were minted after the ladder sent the second course to the Telegram bot. Now any three courses are free, a catalog at #/courses lists every course with what a press will do, a locked course keeps its title, and Russian readers can pay for the club in roubles on the site once the render enables it.", "changes": [ "specs/ladder/course-access.t27, courseAccess.logic.ts, courseAccess.gen.ts: rules VERSION 2 vendored from gHashTag/t27#9357, any three courses free, then the club", "specs/ladder/course-ladder.t27 and i18n/course-ladder.ru.json: 25 new words for the catalog, the free counter and paying on the site; courseLadder.generated.ts regenerated", "src/pages/Courses.tsx and main.tsx: the #/courses catalog with badges, free courses left and one continue button", "src/components/Navigation.tsx and index.css: Courses gets its own place in the header and the mobile menu", "src/pages/Course.tsx and Course.css: breadcrumbs, a locked course keeps its title, free slot warnings, and the on-site rouble payment form shown only to Russian readers when the render offers it", "src/lib/courseLadder.ts: freeLeft, checkoutOffer, startCheckout and readOrder against the render's checkout API" ], "tests": [ { "command": "npm run check:course-ladder && npm run test:course-ladder", "status": "passed", "result": "43 words in 2 languages up to date; ladder tests pass", "evidence": "local run in apps/website at 15367b74a" }, { "command": "npm run check:course, test:course and check:course-pages", "status": "passed", "result": "every course has its menu entry and pages; course tests pass", "evidence": "local run in apps/website at 15367b74a" }, { "command": "node qa/og-preview-contract.mjs, qa/docs-spec-contract.mjs and npm run check:aria", "status": "passed", "result": "all three contracts pass with the new route", "evidence": "local run in apps/website at 15367b74a" }, { "command": "npx vite build", "status": "passed", "result": "build succeeds in about 15 seconds", "evidence": "local run in apps/website at 15367b74a" }, { "command": "browser walk on the local dev server with a mocked render checkout", "status": "passed", "result": "three courses open free, the fourth shows the club gate with title and breadcrumbs, a mocked order goes from waiting to paid and opens the course; English readers see only the bot button", "evidence": "local run on port 5199 at 15367b74a" } ], "limitations": [ "Paying on the site stays off until the render serves /api/course/checkout with a price; until then the gate keeps the Telegram bot button.", "The rouble price is derived on the render from the club's 10,000 Stars at the CBR rate; that render change is a separate gHashTag/999-multibots-telegraf PR.", "Paying on the site with crypto is not built yet; it needs a web buyer identity on the render first.", "Two eslint errors in touched files predate this change: an unused BASE in Navigation and a setState inside an effect." ], "tags": [ "Courses", "Payments", "UX" ], "blog": { "title": "Three courses free, and the club paid on the site", "summary": "The ladder lost every reader at the Telegram bot rung; this PR makes three courses free, adds a catalog, and prepares paying on the site.", "outline": [ "Production showed zero course keys minted since the ladder went live: the second course sent readers to a Telegram bot, and nobody came back.", "The rules moved to VERSION 2 in gHashTag/t27: any three courses are free, the club opens the rest, and a bot key a reader already holds keeps working.", "A catalog at #/courses now says on every card what a press will do, counts the free courses left, and offers one lesson to continue.", "Russian readers will pay for the club in roubles on the site, priced from the club's 10,000 Stars at the CBR rate, once the render turns checkout on." ] } }🤖 Generated with Claude Code