Skip to content

feat(website): club teaser in lesson one of an open course - #1630

Merged
gHashTag merged 1 commit into
mainfrom
feat/club-teaser-in-lesson-one
Oct 12, 2026
Merged

gHashTag merged 1 commit into
mainfrom
feat/club-teaser-in-lesson-one

Conversation

@gHashTag

Copy link
Copy Markdown
Owner

What

The club is the one subscription, and the site's own ladder page named it only when a course was locked. A reader inside their first open course -- the majority, per the bot's thirty-day funnel (1 club card seen against 56 new people) -- met no mention of it at all. Lesson one of an open course now carries the teaser.

How

  • New SAY_CLUB_TITLE / SAY_CLUB_LINE in specs/ladder/course-ladder.t27, with a test pinning the teaser's words apart from the gate's (SAY_CLUB_* != SAY_PAID_*): the gate answers "why is this locked", the teaser answers "what is next".
  • Russian in i18n/course-ladder.ru.json (every key, {0}/digit parity held by the generator); courseLadder.generated.ts regenerated.
  • Course.tsx: ClubTeaser below the lesson pager, reusing the gate's course-hero course-ladder classes and the same startLink(STEP.paid, route) button; rendered only on lesson 1, hidden while a key is still being checked and for readers already holding a valid paid key (useLadder now returns inClub).
  • No price on the page: the bot's club card says it, same honesty as the gate (SAY_PAID_LATER).
{
  "version": 1,
  "head_sha": "7fc86ad6f5ba2ec3e550736e433e1abae9c4660c",
  "summary": "The ladder page named the club only on a locked course, so a reader inside their first open course never met it; lesson one now carries a teaser whose words live in the ladder spec and whose button is the same start link the gate uses.",
  "changes": [
    "specs/ladder/course-ladder.t27: SAY_CLUB_TITLE and SAY_CLUB_LINE consts plus test the_teaser_speaks_for_itself_not_for_the_gate",
    "i18n/course-ladder.ru.json: Russian for both new keys",
    "src/lib/courseLadder.generated.ts: regenerated (18 words x 2 languages)",
    "src/pages/Course.tsx: useLadder returns inClub (valid.paid); new ClubTeaser below the lesson-one pager, hidden while checking and for paid-key holders"
  ],
  "tests": [
    {
      "command": "npm run check:course-ladder",
      "status": "passed",
      "result": "generated file up to date with the spec",
      "evidence": "local run of this head in /tmp/tri-club-teaser/apps/website, 2026-10-13"
    },
    {
      "command": "npm run test:course-ladder",
      "status": "passed",
      "result": "5 of 5 node tests pass, including EN/RU key parity and the mutation suite",
      "evidence": "local run of this head in /tmp/tri-club-teaser/apps/website, 2026-10-13"
    },
    {
      "command": "npm run test:course",
      "status": "passed",
      "result": "42 of 42 course/lesson/cast tests pass",
      "evidence": "local run of this head in /tmp/tri-club-teaser/apps/website, 2026-10-13"
    },
    {
      "command": "npm run check:course-pages",
      "status": "passed",
      "result": "course pages check and its node tests pass",
      "evidence": "local run of this head in /tmp/tri-club-teaser/apps/website, 2026-10-13"
    },
    {
      "command": "npm run typecheck:ratchet",
      "status": "passed",
      "result": "179 errors across 26 files, baseline 179 across 26, no file gained type errors",
      "evidence": "local run of this head in /tmp/tri-club-teaser/apps/website, 2026-10-13; base comparison run on stashed pristine HEAD produced the identical 179-error set"
    }
  ],
  "limitations": [
    "The teaser shows on lesson 1 of every open course including a paid rung the reader already holds the key for, suppressed only by the inClub check on that course's own key",
    "No phone screenshot taken: the teaser reuses the gate's existing course-hero course-ladder classes, so its layout is the layout the gate already ships"
  ],
  "tags": ["website", "courseLadder", "club"],
  "blog": {
    "title": "The one subscription, named where the reader actually is",
    "summary": "The site said the club's name only on courses a reader could not open, so everyone inside their first open course met silence where the money question lives. Lesson one now carries a teaser, and its words live in the ladder spec beside the gate's.",
    "outline": [
      "The symptom in numbers: thirty days of the bot's funnel counted 56 new people, 1 of whom ever saw the club card, because on the site the club appeared only on the locked rung of the ladder.",
      "The asymmetry: the gate answers a question the locked reader asks, why is this closed, while the open-course reader asks what comes next, and no page answered that, so the teaser had to be new words, not a moved block.",
      "The mechanics: two consts in the ladder spec with a test pinning them apart from the gate's, a regenerated words file, and one small component under the lesson-one pager that reuses the gate's classes and its start link, hidden for anyone already holding a paid key."
    ]
  }
}

Companion to gHashTag/999-multibots-telegraf#3998 (the club row on the bot's main menu, merged).

🤖 Generated with Claude Code

The gate names the club only when a course is locked, and most readers
never climb that far: the bot's thirty-day funnel counted 1 club card
against 56 new people, so a reader inside their first open course met
no mention of the club at all. Lesson one now carries the teaser below
the pager -- its words are the ladder spec's new SAY_CLUB_* consts
(Russian in i18n), its button is the same start link the gate uses,
and a reader already holding a paid key sees nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the status:in-progress 🔵 Agent working label Oct 12, 2026
@tri-claw

tri-claw Bot commented Oct 12, 2026

Copy link
Copy Markdown

Summary by t27-bees

Checked by the t27 compiler

t27c parse, parse-complete and typecheck on each changed .t27 file at 7fc86ad. Nothing was generated, built or run. A pass means the file is well formed, not that it is right.

  • apps/website/specs/ladder/course-ladder.t27: parses and typechecks.

Read by the model

This PR adds a club teaser on lesson 1 of open courses so readers encounter the subscription offer inside their first course, not only on locked-course gates. New spec constants SAY_CLUB_TITLE/SAY_CLUB_LINE (distinct from the gate's SAY_PAID_*) drive the copy; the teaser reuses the gate's button and styling, and is hidden while a key is being checked or when the user already holds a paid key for that course (inClub).

Worth a look

  • apps/website/src/pages/Course.tsx:436-458 — ClubTeaser reads say.CLUB_TITLE/CLUB_LINE from the generated ladder dict. The generator strips the SAY_ prefix (spec has SAY_CLUB_TITLE, generated has CLUB_TITLE); if the generator ever changes that convention the teaser will show empty strings.
  • apps/website/src/pages/Course.tsx:364 — Teaser condition uses lesson.n === 1 && !ladder.checking && !ladder.inClub. inClub is valid.paid for the current course only (see useLadder return). A club member on an open course (courses 1–3) will still see the teaser because they don’t need a paid key for those courses — the limitation is acknowledged but the UI will show an upsell to someone who already pays.
  • apps/website/src/pages/Course.tsx:436 — useLadder now returns inClub: valid.paid. valid.paid derives from keys.paid which is the paid key for C.id. If the key format or validation changes, inClub could become inaccurate for this course.
  • apps/website/specs/ladder/course-ladder.t27:61-67 — Test the_teaser_speaks_for_itself_not_for_the_gate only checks string inequality. It won’t catch semantic drift (e.g., both strings changing to similar but not identical marketing copy).
  • apps/website/i18n/course-ladder.ru.json:14-15 — Russian copy for SAY_CLUB_LINE mentions “Четвёртый и все следующие курсы” (fourth and all following courses). If the ladder structure changes (e.g., club starts at course 5), this hardcoded “fourth” becomes wrong while the English spec would also need updating.

Read: 4 files, +47 -3, the whole diff. Head 7fc86ad. Model: openai-compatible/nvidia/nemotron-3-ultra-550b-a55b. Commands: @t27-bees review | summary | help | pause | resume. Free for every repository.

@gHashTag
gHashTag merged commit 341f1d1 into main Oct 12, 2026
20 checks passed
@github-actions github-actions Bot added status:completed Done and removed status:in-progress 🔵 Agent working labels Oct 12, 2026
@gHashTag
gHashTag deleted the feat/club-teaser-in-lesson-one branch October 12, 2026 19:11
github-actions Bot added a commit that referenced this pull request Oct 12, 2026
feat(website): club teaser in lesson one of an open course (#1630)

The gate names the club only when a course is locked, and most readers
never climb that far: the bot's thirty-day funnel counted 1 club card
against 56 new people, so a reader inside their first open course met
no mention of the club at all. Lesson one now carries the teaser below
the pager -- its words are the ladder spec's new SAY_CLUB_* consts
(Russian in i18n), its button is the same start link the gate uses,
and a reader already holding a paid key sees nothing.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant