Skip to content

feat(courses): every course free, TRI CLAW in the course footer, the whole course on one page (#1703) - #1704

Draft
gHashTag wants to merge 5 commits into
mainfrom
feat/courses-free-onepage
Draft

gHashTag wants to merge 5 commits into
mainfrom
feat/courses-free-onepage

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #1703.

Owner, 2026-10-19: «сделай курсы бесплатно всех! наша задача язык раскачать!!», «в курса футере рекламируй подписку на TRI CLAW» (free with your own API key, more keys can be added; whoever does not want the hassle pays us; and the footer links to installing the TRI CLAW GitHub App), and «сделай чтобы страница с темой курса вместилась в одну страницу … и сделай эту страницу также виджетом, который содержит другие виджеты».

One PR, five commits: the source (A–D), the regenerated static pages and cards, the blog post a course PR carries, and two commits of fixes from the review of this PR (below).

A. Every course free

  • specs/ladder/course-access.t27 is gHashTag/t27 specs/automation/course-access.t27 VERSION 3 byte for byte (t27#10799, merge 242ce553). src/lib/courseAccess.logic.ts was rebuilt the way it was made, with 999 scripts/t27-logic.mjs (t27c from t27 master, Zig 0.16): 9 fns lowered, spec-sha256: edd87ade… = the vendored file. courseAccess.gen.ts is t27c gen-ts output. step_of is FREE for every rung.
  • Re-rung. courseState no longer judges a stored course by the step stored with it: its step is stepOf(rung), the spec's own fn (VERSION 3 tells hosts to do exactly this). A course stored on a paid rung under VERSION 2 stays open when the club ends. Test: scripts/course-ladder-from-spec.test.mjs bundles the real courseLadder.ts over a fake store; the mutant that reads the stored step turns it red.
  • Removed: the lock, LadderGate, ClubTeaser, PayForm, TonWait, the checkout client (checkoutOffer, startCheckout, startTonCheckout, readOrder), freeLeft, startLink, the promo/placement plumbing nothing reads any more, 40 words and their RU, and their CSS. The only key machinery kept is the one may_report needs.
  • course-ladder.t27 words now say every course is free (SAY_LADDER, catalog, advert), RU bundle in step (every key, same digits).

B. TRI CLAW in the course footer, three paths

Words and addresses in specs/ladder/course-ladder.t27 + i18n/course-ladder.ru.json; the generator joins the rules' BOT (t27ai_bot) into the links, so nothing restates the handle.

Path Button Where it goes
Free with your own key Add a key in Telegram https://t.me/t27ai_bot?start=app_keys
We do it for you Join the club in Telegram https://t.me/t27ai_bot?start=app_club (no price in the words; the bot shows it)
GitHub Read about TRI CLAW https://t27.ai/blog/tri-claw-an-agent-you-can-audit/
  • Depends on the app's keys route, which another bee adds in gHashTag/999-multibots-telegraf in parallel. Until it ships, ?start=app_keys opens the bot.
  • The App. The slug tri-claw is confirmed by t27 docs/claw/README.md (github.com/settings/apps/tri-claw) and specs/claw/github_surface.t27. I could not confirm that it is public: from this session github.com/apps/tri-claw and api.github.com/apps/tri-claw answer 403, docs/claw has no app-manifest.yml, RELEASE.md defers the Marketplace listing, and github_surface.t27 measures the App on the gHashTag account only. So, as instructed, the third button opens the TRI CLAW post. CLAW_APP_PUBLIC : bool = false in the spec picks it; flipping it (and its spec test) to true switches the button to https://github.com/apps/tri-claw with the "Install TRI CLAW on your repository" words, already in both languages. A test holds both states.
  • The block (ClawPromo, src/styles/claw.css) shows on every course page, lesson page and the catalog in the app, and on every static course, lesson and one-page view (the same sheet, inlined).

C. The whole course on one page, and a widget of widgets

  • The page: scripts/course-onepage.mjs, called by course-pages.mjs, writes <share path>all/ for every course and language: t27.ai/learn/all/ (t27 basics), t27.ai/learn/<course>/all/, ru/ before each. 22 pages, each with its own 1200×630 card (?v= stamp), canonical, hreflang pair, JSON-LD and a sitemap entry. It carries the intro, then every module, then each lesson's title, goal, text, task and widget.
  • Lazy frames: public/widgets/frame-pool.js (one implementation for the page and the deck). A frame mounts when its slot is within MOUNT_MARGIN_PX 600 of the viewport, at most LIVE_FRAMES_MAX 3 are live (the farthest gives way), a frame further than UNMOUNT_MARGIN_PX 2000 is taken down. Each slot holds the frame's height before it loads.
  • Contents: from 1440 px a sticky column (module → lesson, a dot per lesson: done, here); below that a bottom bar ← · Contents 08/27 · → that opens a sheet with the same list. Deep links #lesson-<n>, share for the page (native share, copy, X, Telegram, LinkedIn, utm-tagged by shareTags.ts) and per lesson (its own static page), mark-as-done in the same browser key as the app, print CSS.
  • The widget: specs/widgets/course-deck.t27 + public/widgets/course-deck/, data in courses/<id>.json written by course-pages.mjs. Nine module tabs, the module's three lessons as a scroll-snap row, each card nesting its lesson widget through the same frame pool (K_LIVE_FRAMES_MAX 3 = one module). One generic widget (?course=<id>&module=<n>&lang=ru) instead of eleven: the eleven would differ only in data, and a twelfth course would need a twelfth widget, spec, card and gallery entry; this one needs nothing. Its K_ numbers are the view spec's, held equal by course-view-from-spec.mjs; no K_ holds a spec fn result, no logic.js, widget-logic --check unaffected.
  • Gallery: inserted at 298, before the wars pin (wars 298→299, the pins after it +1), on the game shelf: the gallery requires one run per category and the shelf before wars is the hive's game shelf. New test block the_course_deck_closes_the_course_shelves_before_the_arena. course-from-spec requires the courses to cover the whole gallery, so t27-basics lesson 27 ("Where next") opens the deck as its "also" widget, the one-word spec change that makes this a course PR (hence the post).
  • Entry points: the app's course overview and every lesson ("Whole course on one page →", with #lesson-<n>), every static course and lesson page.

D. UI/UX, the layout numbers in specs/view/course-view.t27

  • Reading width, measured. 68ch would have been ~95 characters: a ch is a zero, and Outfit's zero is 0.66 em. Measured in Chromium over three courses at 18 px: an average character is 0.427–0.432 em in English (Outfit) and 0.531–0.532 em in Russian (system fallback). So READ_EM_X10_EN 301 and _RU 372 (30.1 em / 37.2 em ≈ 70 characters); the spec's tests hold both inside 65–75 characters, and :lang(ru) picks the wider one.
  • Side by side from SIDE_BY_SIDE_MIN_PX 1150 (tested: two gutters + gap + the Russian column + WIDGET_MIN_PX 400 fit); below it text, task, then the widget, then the mark. Contents column from 1440. NARROW_MAX_PX = the site's PHONE_MAX 600 and TOUCH_TARGET_PX = the site's TOUCH_TARGET_MIN_PX 44 (cross-checked against the vendored specs/ui/viewport.t27). Course.css may name only the spec's breakpoints (900/520 became 1149/600; a stray width fails the check).
  • Thumb bar (prev · where · next) on lesson pages below 1150 px, THUMB_BAR_PX 56. 44 px targets for chips, buttons, crumbs links, notes links. Reserved frame height. :focus-visible ring FOCUS_RING_PX 2. prefers-reduced-motion drops transitions and smooth scroll. Greys under 4.5:1 (#6f6f6f, #5a5a5a) raised to #a6a6a6/#8a8a8a.

Fixes from the review (commits f6705ac and 301e5f1)

  • The nav's Courses note is the ladder spec's SAY_PROMO_TITLE / its Russian ("Every course is free, no sign-up"), no longer the "any three free" literal (checked in Chromium, en and ru).
  • specs/catalog/onboarding.t27 COURSE_NOTE (feeds /llms.txt and /agents.t27) now says every course is free and names the one-page address; regenerated with onboarding-from-spec.mjs, check:onboarding and test:onboarding-spec green.
  • The course deck shows SAY_NOT_FOUND for ?course=<unknown> instead of t27 basics: public/widgets/course-deck/pick.js (pure), tested in course-pages.test.mjs, checked in Chromium (?course=nope → "This course is not in the catalog." / «Такого курса нет в каталоге.», 0 frames).
  • course-view.t27: FOCUS_OFFSET_PX 2 and SETTLE_MS 120 (the pool's look after scrolling stops, now a required parameter of frame-pool.js) are spec constants with tests; the deck's K_FOCUS_RING_PX, K_FOCUS_OFFSET_PX, K_SETTLE_MS are held equal to them; the one page's progress bar takes --course-read instead of 760 px. CONTRAST_MIN_X10 is dropped: contrast is measured on the rendered pages (table below), not held by the spec. The generator header and the post now say the spec holds the layout numbers this change decides; type sizes, paddings and colours stay in the stylesheets.
  • TRI CLAW's GitHub line says "built to check" in English and «созданное, чтобы проверять» in Russian, in the footer and in the post.
  • CI: Course pages, Course ladder and Course view are separate steps with if: ${{ !cancelled() }}, so the red test:course Анализ проекта для ученых #29 no longer hides them.
  • check:queen-contrast (commit 301e5f1): checks (pure gates) on cb598ae failed at "The HUD can be read while the map is lit" because Footer.tsx imported claw.css and courseView.generated.css, so src/App.tsx (landing and board) reached two sheets the gate never measured. Root cause fixed rather than the list widened: the block moved to components/ClawPromo.tsx (which imports both sheets), Footer takes an aside node and imports neither, and Course.tsx / Courses.tsx pass <ClawPromo />. App.tsx reaches exactly what it did on main. Every step of the pure-gates job now passes locally except Course (Анализ проекта для ученых #29, as on main); Chromium shows one TRI CLAW block on #/courses and on a lesson, none on #/ or #/blog.

Playwright (Chromium 1194; 390×844 touch, 1280×800), re-run on 301e5f1

Page Width Errors Sideways Targets < 44 px Live frames (max over scroll) CLS Worst contrast
one page, neural-networks (en) 390 0 0 0 3 (90 steps) 0 8.37:1
one page, neural-networks (en) 1280 0 0 0 3 (54 steps) 0 8.37:1
one page, t27-basics (ru) 390 0 0 0 3 (83 steps) 0 8.37:1
one page, t27-basics (ru) 1280 0 0 0 3 (48 steps) 0 8.37:1
lesson relu, app 390 / 1280 / 1280 ru 0 0 0 1 frame, height reserved (760 px) 0 5.92:1
course deck embed 390 / 1280 0 0 0 3 on open, 3 after tab 5 0 8.37:1
  • Layout: one page at 390 stacked, widget after the task; at 1280 side by side, 70 characters per line (en), 72 (ru), widget column 666 / 538 px. App lesson at 1280: 70 (en) / 69 (ru) characters per line.
  • A fresh visit to …/all/#lesson-8 lands with the lesson 12 px from the top and its frame live. The contents sheet lists 27 lessons with 27 dots; tapping lesson 20 closes it, the bar reads 20/27; marking it done fills its dot and the counter reads "1 of 27 done". At 1440 the contents column is 240 px, sticky, and marks the lesson in view; the bar is gone. prefers-reduced-motion: reduce → scroll-behavior: auto. Tab focus draws a 2 px solid ring.
  • Before the font preload the one page shifted 0.014–0.017 at load (font swap on the intro); with <link rel=preload> it is 0.
  • Not fixed, pre-existing: a static lesson page (learn/relu/) scrolls 25 px sideways at 390 px on main as well as here; the cause is not found.

Screenshots (session scratchpad onepage/shots/): onepage-neural-networks-390-top.png (the intro, the "free" pill, the bottom bar), onepage-neural-networks-390-lesson8.png (lesson 8 stacked: goal, text, task, the widget right after), onepage-neural-networks-1280-lesson8.png (side by side), onepage-1440.png (the contents column with dots), onepage-390-contents.png (the contents sheet), lesson-390-footer.png and lesson-1280-ru-footer.png (the TRI CLAW block, three paths), lesson-390.png (compact crumbs, thumb bar), deck-390.png / deck-1280.png (the deck embed, module tabs, live lesson widgets), blog-390.png (the post's figure).

Checks (apps/website, local, on 301e5f1)

  • gen:course-pages ✓ · check:course ✓ · test:course ✗ 41/42, Анализ проекта для ученых #29 is red on main too · check:course-ladder ✓ · test:course-ladder ✓ 10/10 · check:course-view ✓ · test:course-view ✓ 6/6 (new) · check:onboarding ✓ · test:onboarding-spec ✓ · check:queen-spec-mirrors ✓ · check:course-pages ✓ 18/18 (incl. the deck's not-found test) · check:widget-logic ✓ · test:widget-logic ✓ · check:widget-pages ✓ · test:widget-pages ✓ · check:widgets ✓ · test:widgets-spec ✓ · check:og-preview ✓ · check:queen-fallback-parity ✓ · check:queen-contrast ✓ (and every other step of the pure-gates job, run locally) · typecheck:ratchet ✓ (179/179) · check:blog-bodies ✓ · npm run build ✓ (the 8 JSON files the prebuild rewrites restored) · a second gen:course-pages leaves no diff.
  • CI: website-checks.yml runs Course (check:course, test:course), then Course pages, Course ladder and Course view as their own steps; course-view-from-spec.mjs is also in prebuild.
  • Not run: test:agents-specs (its 2 failures are known reds on main; untouched).
{
  "version": 1,
  "head_sha": "301e5f187013001174ba0d84d0d5fe1ba5ac3e2c",
  "summary": "Every t27.ai course is free (course-access.t27 VERSION 3); each course has a whole-course-on-one-page view and a course-deck widget that loads at most 3 lesson widgets at once; TRI CLAW has three paths in the course footer.",
  "changes": [
    "specs/ladder/course-access.t27: gHashTag/t27 VERSION 3 byte for byte; src/lib/courseAccess.logic.ts rebuilt with 999 scripts/t27-logic.mjs (9 fns, spec-sha256 edd87ade), courseAccess.gen.ts with t27c gen-ts.",
    "src/lib/courseLadder.ts: a stored course's step is step_of for its rung, not the stored step, so a course opened on a paid rung under VERSION 2 does not close when the club ends; the checkout client, notePromo and startLink are gone.",
    "src/pages/Course.tsx, Courses.tsx, CourseShelf.tsx: no lock, gate, club teaser or rouble/TON pay form; their words, CSS and tests removed.",
    "specs/ladder/course-ladder.t27 + i18n/course-ladder.ru.json: words say every course is free; TRI CLAW words and addresses (app_keys, app_club, the App behind CLAW_APP_PUBLIC); the generator joins the rules' BOT into the t.me links.",
    "components/Footer.tsx ClawPromo + src/styles/claw.css: the TRI CLAW block on every course, lesson and catalog page; the same sheet is inlined into every static course page.",
    "specs/view/course-view.t27 + scripts/course-view-from-spec.mjs: reading width per language from a measured character width, breakpoints, frame budget, finger and focus; Course.css may name only these breakpoints.",
    "scripts/course-onepage.mjs + public/widgets/frame-pool.js: <share path>all/ per course and language with its card, lazy frames, contents column or sheet with progress dots, deep links, share and print CSS.",
    "specs/widgets/course-deck.t27 + public/widgets/course-deck: one widget for every course, in the gallery before the wars pin; opened as 'also' by t27-basics lesson 27.",
    "Lesson UX: side by side from 1150 px, stacked below with the widget right after the task; a thumb bar; 44 px targets; focus ring; reduced motion.",
    "src/data/blog: the course PR's post, EN and RU, with a figure drawn by the compiled step_of of VERSION 2 and 3.",
    "Review fixes: the nav note and llms.txt COURSE_NOTE say every course is free from their specs; the deck shows SAY_NOT_FOUND for an unknown ?course=; FOCUS_OFFSET_PX and SETTLE_MS join course-view.t27 and CONTRAST_MIN_X10 leaves it; CI runs the course pages, ladder and view gates as separate steps.",
    "The TRI CLAW block moved from Footer.tsx to components/ClawPromo.tsx, passed in by the course pages, so the landing and the board no longer load claw.css and courseView.generated.css (check:queen-contrast red on cb598aeb, green now)."
  ],
  "tests": [
    {
      "command": "cd apps/website && npm run check:course",
      "status": "passed",
      "result": "course-from-spec: 11 courses build clean; the gallery is covered (course-deck is an 'also' widget of t27-basics lesson 27)",
      "evidence": "local run on 301e5f18, log final-check:course.log in the session scratchpad"
    },
    {
      "command": "cd apps/website && npm run test:course",
      "status": "failed",
      "result": "41 pass, 1 fail: #29 'the scan reads lesson specs before its wipe and settles them after it', the same test that is red on main",
      "evidence": "local run, log final-test:course.log; #29 is a known red on main, not touched here"
    },
    {
      "command": "cd apps/website && npm run check:course-ladder && npm run test:course-ladder",
      "status": "passed",
      "result": "rules sha edd87ade1cc3 = the vendored VERSION 3; 26 words in 2 languages; 10/10 tests, including the re-rung test (a mutant that reads the stored step fails it)",
      "evidence": "local run; mutation run by hand: courseState with opened[at].step instead of stepOf(rungOf(at)) turns test 9 red"
    },
    {
      "command": "cd apps/website && npm run check:course-view && npm run test:course-view",
      "status": "passed",
      "result": "27 numbers, 13 words; spec tests 8 blocks / 28 asserts hold; 6/6 tests incl. negative controls on the breakpoint, the reading width, the frame budget, the deck K_ numbers, the viewport finger and Course.css @media widths",
      "evidence": "local run, logs last-check:course-view.log, last-test:course-view.log"
    },
    {
      "command": "cd apps/website && npm run gen:course-pages && npm run check:course-pages",
      "status": "passed",
      "result": "638 pages (22 of them whole courses on one page), 638 cards, 12 course-deck files, sitemap; 18/18 page tests incl. the deck's not-found pick; a second gen:course-pages leaves no diff",
      "evidence": "local run; cards drawn with Playwright Chromium 1194 behind a --no-sandbox wrapper"
    },
    {
      "command": "cd apps/website && npm run check:widget-logic && npm run test:widget-logic",
      "status": "passed",
      "result": "111 widgets run their spec's own logic; the deck has no logic.js and no K_ holding a spec fn result",
      "evidence": "local run in the worktree on 301e5f18; output kept in the session scratchpad logs"
    },
    {
      "command": "cd apps/website && npm run check:widget-pages && npm run test:widget-pages",
      "status": "passed",
      "result": "194 tool pages up to date, course-deck included (2 of its 3 test blocks are invariants of its own)",
      "evidence": "local run in the worktree on 301e5f18; output kept in the session scratchpad logs"
    },
    {
      "command": "cd apps/website && npm run check:widgets && npm run test:widgets-spec",
      "status": "passed",
      "result": "307 widgets in 3 categories; spec tests 15 blocks / 69 asserts hold, the new block pins course-deck at 298 between hive-cell and wars",
      "evidence": "local run in the worktree on 301e5f18; output kept in the session scratchpad logs"
    },
    {
      "command": "cd apps/website && npm run check:og-preview",
      "status": "passed",
      "result": "og-preview contract ok; CourseShare is unchanged",
      "evidence": "local run in the worktree on 301e5f18; output kept in the session scratchpad logs"
    },
    {
      "command": "cd apps/website && npm run check:queen-fallback-parity",
      "status": "passed",
      "result": "no var() fallback disagrees with its token (claw.css and courseView.generated.css use none)",
      "evidence": "local run in the worktree on 301e5f18; output kept in the session scratchpad logs"
    },
    {
      "command": "cd apps/website && npm run typecheck:ratchet",
      "status": "passed",
      "result": "179 errors across 26 files; baseline 179 across 26; no file gained type errors",
      "evidence": "local run in the worktree on 301e5f18; output kept in the session scratchpad logs"
    },
    {
      "command": "cd apps/website && npm run build",
      "status": "passed",
      "result": "prebuild and vite build green; the 8 tracked JSON files the prebuild rewrites were restored with git checkout --",
      "evidence": "local run, logs final-build.log and vite3.log"
    },
    {
      "command": "node measure.mjs (Playwright Chromium 1194; 390x844 touch and 1280x800)",
      "status": "passed",
      "result": "one-page view of neural-networks (en) and t27-basics (ru): 0 errors, 0 px sideways, 0 targets under 44 px, live frames at most 3 over 48-90 scroll steps, layout shift 0, worst text contrast 8.37:1; lesson page: 0 errors, CLS 0, frame height reserved, worst contrast 5.92:1; deck embed: 3 live frames, 3 after switching to module 5",
      "evidence": "session scratchpad onepage/measure.json and onepage/shots/*.png"
    },
    {
      "command": "cd apps/website && npm run check:onboarding && npm run test:onboarding-spec",
      "status": "passed",
      "result": "public/agents.t27 and public/llms.txt regenerated from the new COURSE_NOTE, 25494 bytes each; spec tests 10 blocks / 55 asserts hold; 4/4 tests",
      "evidence": "local run in the worktree on 301e5f18; output kept in the session scratchpad logs"
    },
    {
      "command": "cd apps/website && npm run check:queen-contrast && npm run check:queen-fallback-parity, then every run: step of the checks-pure job",
      "status": "passed",
      "result": "queen-contrast: 24 pairs, worst 5.39:1, 38 stylesheets walked from QueenUniverse.tsx + App.tsx; fallback parity clean; the other 58 steps of the pure-gates job pass too, except Course (test:course #29, red on main too)",
      "evidence": "local run in the worktree on 301e5f18; CI on cb598aeb had failed at this step, logs in the session scratchpad"
    },
    {
      "command": "cd apps/website && npm run test:agents-specs",
      "status": "not_run",
      "result": "not run on this branch; the two failing agents-from-specs tests are known reds on main and this PR does not touch that generator",
      "evidence": "No log: the command was not run on this branch; scripts/agents-from-specs.mjs is unchanged in this diff"
    }
  ],
  "limitations": [
    "The free TRI CLAW path opens t.me/t27ai_bot?start=app_keys; the keys route is added to the app in gHashTag/999-multibots-telegraf in parallel and is not verified here.",
    "The TRI CLAW GitHub App could not be checked as public from this session (github.com and api.github.com/apps are closed to it); docs/claw has no app-manifest.yml and defers the Marketplace listing, so the footer links the TRI CLAW post until CLAW_APP_PUBLIC is set true.",
    "Measured in Playwright Chromium only, not on a real phone or in Safari; the frame budget is measured as iframe counts, not as memory.",
    "A static lesson page (learn/<lesson>/) scrolls 25 px sideways at 390 px in Chromium on main and on this branch; the cause was not found and is not addressed here.",
    "test:course #29 stays red as on main."
  ],
  "tags": [
    "t27",
    "Courses",
    "UX",
    "TRICLAW"
  ],
  "blog": {
    "title": "Every course is free now, and each one fits on one page that is also a widget",
    "summary": "step_of in course-access.t27 VERSION 3 returns FREE for every rung, and a whole-course page keeps at most 3 of its 27 lesson widgets live while it scrolls.",
    "outline": [
      "The courses asked for the club from the fourth course on, and a course the reader cannot open is a reader the language does not get; the owner made every course free on 2026-10-19, and the rule changed first in the gHashTag/t27 spec, VERSION 3.",
      "The site runs step_of compiled from that spec, and re-asks it for every stored course, so a course opened on the old club rung stays open after the club ends; each course got one page with all 27 lessons, whose widget frames mount near the screen, 3 at most, 600 px ahead and 2000 px behind, measured at 390 and 1280 px with no sideways scroll and no layout shift.",
      "The same course is one embeddable widget of widgets; the footer offers TRI CLAW three ways; the keys screen ships in the app separately, the GitHub App link waits until it is open to other repositories, and the static lesson page's 25 px sideways scroll is still unexplained."
    ],
    "reproduce": [
      "cd apps/website && npm run check:course-view && npm run test:course-ladder",
      "cd apps/website && npm run gen:course-pages && npm run check:course-pages"
    ]
  }
}

🤖 Generated with Claude Code

https://claude.ai/code/session_01ScHVrGSr6zZUdwkdC8DR9k

…whole course on one page (#1703)

Owner, 2026-10-19: "make every course free! our task is to grow the
language"; "advertise TRI CLAW in the course footer"; "make the page with
the course's topic fit on one page ... and make it a widget of widgets".

A. Every course free
- specs/ladder/course-access.t27: gHashTag/t27 VERSION 3 byte for byte
  (t27#10799, merge 242ce553); courseAccess.logic.ts rebuilt from it with
  999 scripts/t27-logic.mjs (spec-sha256 edd87ade), courseAccess.gen.ts
  with t27c gen-ts.
- courseLadder.ts: a stored course's step is the spec's step_of for its
  rung, never the step stored with it, so a course opened on a paid rung
  under VERSION 2 does not close when the club ends (the spec's own
  instruction to hosts). Test with a mutant that reads the stored step.
- The lock, the gate, the club teaser and the rouble/TON pay forms are
  gone from Course.tsx, with their words, CSS and the checkout client.

B. TRI CLAW, three paths, in the footer of every course page
- Words and addresses in specs/ladder/course-ladder.t27 (+ RU bundle):
  own key -> t.me/<bot>?start=app_keys, the club -> ?start=app_club, the
  GitHub App -> its post while CLAW_APP_PUBLIC is false. The bot is the
  rules' BOT; no price in the words.

C/D. The whole course on one page, the course deck, the layout numbers
- specs/view/course-view.t27 (+ generator, RU bundle, tests): reading
  width per language from a measured character width, breakpoints, the
  frame budget (3 live, mount 600 px, unmount 2000 px), finger, focus,
  contrast. Course.css may name only its breakpoints.
- scripts/course-onepage.mjs: <share path>all/ per course and language,
  lazy frames through public/widgets/frame-pool.js, contents column or
  sheet with progress dots, #lesson-<n>, share, print CSS.
- specs/widgets/course-deck.t27 + public/widgets/course-deck: one widget
  for every course (?course=), nine module tabs, three lesson frames each;
  gallery entry before the wars pin; opened as "also" by t27-basics'
  last lesson so the courses still cover the gallery.
- Lesson: text and widget side by side from 1150 px, stacked below with
  the widget right after the task; prev/next under the thumb; 44 px
  targets; visible focus; reduced motion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScHVrGSr6zZUdwkdC8DR9k
…ards and the deck page (#1703)

Written by scripts/course-pages.mjs, scripts/widget-pages-from-spec.mjs,
scripts/widgets-from-spec.mjs and scripts/course-from-spec.mjs from the
specs of the previous commit; nothing here is edited by hand.

- 22 new pages: learn/<course>/all/ and ru/learn/<course>/all/ (learn/all/
  for the first course), each with its own 1200x630 card and ?v= stamp,
  and in learn/sitemap.xml with its hreflang pair.
- Every course and lesson page: the one-page entry and the TRI CLAW block.
- widgets/course-deck/: its page and courses/<id>.json (12 files).
- widgets/gallery.t27: the course deck before the wars pin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScHVrGSr6zZUdwkdC8DR9k
…hat is also a widget (#1703)

The course PR carries its post (specs/policy/course_post.t27): EN body and
RU ruBody, registered in index.ts and posts.ts. Its figure is drawn by the
compiled step_of of course-access.t27 VERSION 2 (8e535bc) and VERSION 3
(this branch), rungs 0 to 10. The numbers in it are this branch's
measurements (3 live frames at most, layout shift 0, 0.43/0.53 em per
character) and the spec's constants. It names what the change does not do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScHVrGSr6zZUdwkdC8DR9k
@github-actions

Copy link
Copy Markdown
Contributor

X Brain Health Check

Score: 100.0/100
Status: 🟢 HEALTHY
Threshold: 80/100

X Brain is above merge threshold

@github-actions github-actions Bot added the status:in-progress 🔵 Agent working label Oct 19, 2026
…e is free, the deck refuses an unknown course, the view spec holds what it claims (#1703)

- Navigation: the Courses note is the ladder spec's SAY_PROMO_TITLE and
  its Russian ("Every course is free, no sign-up"), not the old "any
  three free" literal.
- specs/catalog/onboarding.t27 COURSE_NOTE (it feeds /llms.txt and
  /agents.t27): every course is free, the one-page address; regenerated
  with onboarding-from-spec.mjs, check:onboarding green.
- course-deck: ?course=<unknown> shows SAY_NOT_FOUND instead of t27
  basics (pick.js, a pure function; test in course-pages.test.mjs;
  checked in Chromium for en and ru).
- course-view.t27: FOCUS_OFFSET_PX and SETTLE_MS (the frame pool's look
  after a scroll, now a required parameter of frame-pool.js) join the
  spec, the deck's K_FOCUS_RING_PX/K_FOCUS_OFFSET_PX/K_SETTLE_MS are held
  equal to them, the one page's progress bar takes --course-read, and
  CONTRAST_MIN_X10 is dropped: contrast is measured on the rendered pages,
  not held by the spec. The generator header and the post now say the
  spec holds the layout numbers this change decides, not every number.
- TRI CLAW's GitHub line says "built to check" in both languages.
- CI: Course pages, Course ladder and Course view are their own steps
  (if: !cancelled()), so the red test:course #29 no longer hides them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScHVrGSr6zZUdwkdC8DR9k
…ery page's footer (#1703)

checks (pure gates) on cb598ae failed at "The HUD can be read while the
map is lit" (check:queen-contrast): Footer.tsx imported claw.css and
courseView.generated.css, so src/App.tsx (the landing and the board)
reached two stylesheets the gate had never measured. The block is only
drawn on course pages, so it moves to components/ClawPromo.tsx, which
imports both sheets; Footer takes an `aside` node and imports neither.
Course.tsx and Courses.tsx pass <ClawPromo />. The gate's REACHED list is
unchanged: App.tsx again reaches exactly what it did on main.

check:queen-contrast, check:queen-fallback-parity and every other step of
the pure-gates job pass locally; only Course (test:course #29) is red, as
on main. Chromium: one TRI CLAW block on #/courses and on a lesson, none
on #/ or #/blog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ScHVrGSr6zZUdwkdC8DR9k
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Brain Stress Test Results

Tests: 261/261 passed, 0 leaked
Status: 🟢 PASS
Gate: every test in zig build test-brain-stress passes and none leaks

Copy link
Copy Markdown
Collaborator Author

checks (pure gates) is red on 301e5f1, but only at the Course step, and that red is not this PR's: test:course #29 ("the lesson specs are read before the wipe") fails with the identical assertion on main 8e535bc, where the same step is red (job 117880025341). Every other step of the job passes on this head, including the three this PR splits out so #29 no longer hides them: Course pages, Course ladder and Course view. The earlier red at "The HUD can be read while the map is lit" (cb598ae, f6705ac) is fixed in 301e5f1: the TRI CLAW block moved to ClawPromo.tsx, so App.tsx reaches only the stylesheets it reached on main, and the gate itself is unchanged. No fix for #29 exists yet; it is outside this PR's scope.


Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status:in-progress 🔵 Agent working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Courses: every course free, TRI CLAW in the course footer, the whole course on one page

2 participants