Repository navigation
Conversation
The lessons now describe code that exists in ephbaum/elxrBB rather than code that was planned. Every command and snippet was run. Lesson 1: Phoenix 1.8.13 on Elixir 1.20 / OTP 28. Uses the official elixir-lang install script, drops the Node.js step (1.8 builds assets with standalone esbuild and Tailwind binaries), and explains why the project is generated as `elxrbb --module ElxrBB` — an OTP application name cannot be mixed case, which is what the 2023 attempt tripped over. `--live` is noted as redundant rather than removed, since the flag is still accepted. Lesson 2: rewritten around `mix phx.gen.auth` instead of Pow, and renamed to "User Accounts". Pow has had no release since January 2025 and predates the 1.8 layout and component conventions. The Pow draft moves to docs/archive/original-lessons/02-user-authentication-pow.md. The new lesson covers scopes, magic links and sudo mode, then adds elxrBB's own material: the username generator built from this repo's word lists, case-insensitive uniqueness via a functional index, and the profile form. Lesson 3: forums, topics and replies, replacing the planned flat `/topics` + `/replies` CRUD with the nested routes a forum actually needs. Covers the aggregate subquery that produces reply counts without an N+1 or a counter column, why route declaration order matters, why ownership is checked at mount and again on save, and how a stream-backed reply list has to track its own count. Each lesson calls out a bug that was hit while building: the nil-intolerant `String.trim/1` in `update_change/3`, the aggregate that loses its Ecto type crossing a subquery, and a username generator that could produce names its own schema rejected. STATUS.md now reports lessons and implementation separately, records the three deviations from the original plan, and lists what is next. The README gains a table of contents; the original account of the ChatGPT collaboration is kept as "Where this came from". Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QKEb38aDmzRFVxFnM6Fe3X
Lesson 2 opened by arguing against a library rather than explaining the one it uses. It now states the choice — `mix phx.gen.auth` — and moves on. The outline entry, the status note and the archive pointer follow suit. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QKEb38aDmzRFVxFnM6Fe3X
Audits the original sixteen-lesson outline now that lessons 1-3 exist. The spine holds; several specifics do not. - Lessons 4 and 5 describe one feature — a reply with a parent_id — and are merged. - Real-time moves from lesson 11 to lesson 5 and switches from Phoenix Channels to PubSub. A LiveView receives broadcasts in handle_info/2; Channels are for clients that are not LiveViews. Leaving it at lesson 11 also means six lessons where a posted reply is invisible to everyone but its author. - Pagination and search were absent from the plan entirely. Every list query in the application is unbounded today, which is the largest gap between finishing the tutorial and having a working forum. New lesson 6. - Testing moves earlier and narrows: the application has had tests since lesson 2, so lesson 13 covers what we have not used rather than introducing the idea. - The animal-names lesson is dropped. Lesson 2 loads those word lists at compile time; putting them in a table and exposing them to JavaScript adds nothing. The JS-hook material moves to lesson 8 as a Markdown preview. - Payments, SMS and browser push become appendices C and D. A step needing a vendor account, a card and a public webhook endpoint is not one most readers can take, and the roles work it was bundled with is real. - Old lesson 6 splits: profiles already shipped in lesson 2, avatars and private messaging become lesson 7, and file storage sits behind a behaviour rather than naming one vendor in the title. - Rich text narrows to Markdown alone, with sanitization as the point of the lesson rather than a footnote. STATUS.md now lists the application's known gaps — no pagination, no real-time, no search, plain-text bodies, unrestricted forum management, no rate limiting — so they read as scheduled rather than overlooked. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QKEb38aDmzRFVxFnM6Fe3X
Written against the implementation in ephbaum/elxrBB. Merges what the outline had as lessons 4 and 5, since "threaded replies" and "sub-topics" are the same feature. Covers the self-referencing foreign key and why depth is stored rather than derived, the two check constraints that keep the denormalization honest, building a tree in two queries instead of one per level, and why sibling ordering needs an id tiebreak or the page reorders itself between renders. For voting: one table with two nullable targets and an exclusive-or check constraint, partial unique indexes and why they must be partial, summing scores rather than keeping a counter column, and the pinned-value clause that makes casting the same vote twice take it back. The web section covers recursive function components, the duplicate DOM id the test suite caught, the deliberate trade of a LiveView stream for a plain assign now that replies are a tree, and three habits for event payloads: parse rather than trust, check the id addresses something in scope, and prefer the non-raising getter. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QKEb38aDmzRFVxFnM6Fe3X
The lessons quote the application's source and nothing verified the quotes. An audit of lessons 1-4 against the code found the quotes accurate but the comparison itself unreliable: checking a lesson against the application's HEAD reports drift wherever a later lesson changed the code on purpose. Lesson 3 shows replies as a flat LiveView stream and lesson 4 replaces that with a tree, so lesson 3 looks broken against HEAD and is not. So pin each lesson to the commit that concludes it, in lessons.exs, and check its blocks against that tree. Blocks name their source file in the fence. Comparison ignores whitespace and whole-line comments, so a lesson may re-indent an excerpt, reflow it to the page width, or leave out a comment it explains in prose; it may not change a name, a literal or the structure. A bare `...`, or a comment mentioning `...`, elides code the lesson is not showing. 27 of 39 source blocks are annotated and pass. The remaining 12 condense their source without marking the elision, which needs a judgement call per block; --strict fails on unannotated blocks so coverage can be ratcheted. Also correct lesson 1: it said Phoenix 1.8 needs Elixir 1.15 or newer, but this application's mix.exs requires 1.17. Add CI to run the check on push, on pull request, and weekly, since the application can drift without this repository changing. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QKEb38aDmzRFVxFnM6Fe3X
Pushing to a PR branch fired both the push and pull_request triggers, so every commit ran the same check twice. Branches are covered by pull_request; keep push for merges to main. Bump actions/checkout to v5 while here -- v4 targets Node 20, which the runners now force onto Node 24 with a deprecation warning. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QKEb38aDmzRFVxFnM6Fe3X
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.
Lessons 1-4 are now written against code that exists and passes its tests, and the sixteen-lesson outline has been audited against that code.
The implementation is in ephbaum/elxrBB#1. Every command and snippet in these lessons was run, not assumed.
The lessons
Lesson 1 — Setting Up the Environment. Phoenix 1.8.13 on Elixir 1.20 / OTP 28. Uses the official elixir-lang install script, drops the Node.js step (1.8 builds assets with standalone esbuild and Tailwind binaries), and explains why the project is generated as
elxrbb --module ElxrBB— an OTP application name cannot be mixed case, which is what the 2023 attempt tripped over.Lesson 2 — User Accounts (renamed from "User Authentication with Pow"). Built on
mix phx.gen.auth: scopes, magic links, sudo mode. Then elxrBB's own material — the username generator built from this repository's word lists, case-insensitive uniqueness via a functional index, and the profile form. The Pow draft is preserved indocs/archive/original-lessons/for reference.Lesson 3 — Forums, Topics and Replies. Replaces the planned flat
/topics+/repliesCRUD with the nested routes a forum actually needs. Covers the aggregate subquery that produces reply counts without an N+1 or a counter column, why route declaration order matters, why ownership is checked at mount and on save, and how a stream-backed list has to track its own count.Lesson 4 — Threaded Replies, Sub-Topics and Voting (new; merges old lessons 4 and 5). The self-referencing foreign key and why depth is stored rather than derived, the check constraints that keep that denormalization honest, building a tree in two queries instead of one per level, and why sibling ordering needs an id tiebreak or the page reorders itself between renders. For voting: one table with two nullable targets and an exclusive-or check constraint, partial unique indexes and why they must be partial, and summing scores rather than keeping a counter column.
Each lesson calls out a bug hit while building it — the nil-intolerant
String.trim/1inupdate_change/3, the aggregate that loses its Ecto type crossing a subquery, a username generator that produced names its own schema rejected, and a duplicate DOM id the test suite caught. These are the parts a reader learns most from.The plan audit
The original outline was drafted before any code existed. The spine holds — contexts, then the forum domain, then threading, moderation, deployment — but several specifics do not survive contact with the application.
docs/00a-outline.mdnow carries the revised plan and a Revisions to this plan section explaining each change:parent_id. Merged.Phoenix.PubSuband receives inhandle_info/2. Channels are for clients that are not LiveViews. Leaving it at 11 also means six lessons where a posted reply is invisible to everyone but its author.Also: old lesson 6 splits (profiles shipped in lesson 2; avatars and private messaging become lesson 7, with file storage behind a behaviour rather than one vendor named in the title), and rich text narrows to Markdown alone with sanitization as the point rather than a footnote.
Sixteen lessons became fifteen plus four optional appendices.
STATUS.md
Now reports lesson status and implementation status separately, and lists the application's known gaps — no pagination, no real-time, no search, plain-text bodies, unrestricted forum management, no rate limiting — so they read as scheduled rather than overlooked.
README
Gains a table of contents and a statement of what the stack actually is. The original account of the ChatGPT collaboration is kept intact, under "Where this came from".
🤖 Generated with Claude Code
https://claude.ai/code/session_01QKEb38aDmzRFVxFnM6Fe3X
Generated by Claude Code