forgia · dev log
Engineering log for the forgia LMS platform.
- 28 Aug 26The Congratulation That Was in the Way
A Student got every Blank right, and the modal that told her so covered the passage she wanted to re-read. She could not check what she had written. The fix is a labelled way out — and a completed state to land on, because the modal was carrying the only way forward.
elixirphoenixliveviewcssgettextstudentusability - 24 Aug 26The Field Asked for the One Thing Bunny Makes Hard to Copy
A Video Lesson wanted a full Bunny embed URL. Bunny's own UI hands you a GUID and buries the URL. Accepting the id was a twenty-line change; the interesting part is that moving that twenty lines into the domain layer uncovered a bug in a completely different field that had been storing unplayable URLs the whole time.
elixirphoenixliveviewectodomain-modelvideo - 24 Aug 26Six Copies of a Nav, Four Kinds of Drift
A client reported one missing link. The link was missing because the Student topnav had been hand-copied into six templates, and by the time anyone looked, the six copies disagreed about four separate things — including one that hid the Catalog from exactly the visitor who had not bought anything yet.
phoenixliveviewcomponentsrefactoringdesign-systemelixir - 24 Aug 26A Repair Tool That Is Actually Part of the Workflow
Durations are fetched when a Lesson is saved, which sounds complete until you notice that uploading a video and immediately pasting its URL is the normal thing to do — and guarantees no duration. The button that fixes it is not a maintenance script; it is step two.
elixirphoenixliveviewcssgettextinstructor - 17 Aug 26The Arithmetic That Deleted a Rung
A Student who fails the same exercise three times has nothing left to reason with and starts permuting. The fix is to say more — but the screen already shows two numbers, and a third would have solved for everything. One proposed rung turned out to be informationless, and the rule that keeps the rest bounded is that a mark decays instead of re-evaluating.
liveviewadrfeedback-designsentence-selectionword-matchaccessibility - 16 Aug 26The One Place morphdom Cannot Reach
A Video Lesson was rendering into a 680px column built for prose, which made text on screen unreadable. Fixing the frame was a CSS min() of three terms. Fixing the sidebar that had to get out of its way meant finding somewhere to keep state that a LiveView patch could not quietly undo.
liveviewvideocssjavascriptmorphdomdesign-system - 09 Aug 26Two Fields Are Not a Template Language
A bulk invite should read like something a person wrote. That takes one textarea, two merge fields, and a surprising number of decisions about what not to build — including a debounce that would have quietly eaten the last word of every message.
emailphoenixliveviewdesign-systemproduct - 05 Aug 26Dealing the Card Deck
An animated card game whose round runs entirely on the server. The obvious design was the opposite one, and two facts about this repo killed it. Plus a wrong Check that tells you which Cards are wrong and never where they belong, and three bugs no green test suite could have found.
liveviewgroupingjavascriptanalyticsdesign-system - 04 Aug 26Retake a Quiz
A completed Quiz Lesson was a dead end. Now it offers a Retake — one QuizAttempt row rewound in place, a Completion that is never revoked, and a Course-complete redirect that finally learned to ask whether anything new actually happened.
liveviewquizectoddddesign-system - 04 Aug 26Authoring a Grouping Lesson
A sixth Lesson type, and the first whose draft is allowed to be wrong. The rules gate Publish instead of Save, colours stay distinct by swapping rather than erroring, and three bugs that no green test suite could have found.
liveviewgroupingectoddddesign-system - 03 Aug 26References on a Lesson, and a Bibliography That Compiles Itself
Typed References attached to a Lesson, rendered as an endnote and compiled into a Course-wide Bibliography grouped by Chapter — plus the LiveView bug that stayed invisible to sixteen green tests and took a real browser to find.
liveviewectojsonbdesign-systemdddtesting - 23 Jul 26Localized App Interior & Emails: Finishing What the Landing Page Started
Spreading the EN/pt-BR Locale infrastructure from landing and catalog into the Studio, the student learning screens, and every transactional email — plus three real bugs the sweep turned up along the way.
gettextliveviewi18ndesign-systemddd - 23 Jul 26Find the Sentence: Manual Markers, Instructor Feedback, and a Bug the Browser Caught
Three small upgrades to Sentence Selection — Prompt images, instructor-authored completion messages, and manual || markers replacing the punctuation auto-split — plus a cross-field data-loss bug that only a real browser run surfaced.
liveviewsentence-selectionectodesign-systemddd - 20 Jul 26A Real Front Door
The root route stops being a dev-only nav page and becomes a public Landing Page — nav, hero, methodology, instructor bio, and a live course catalog — while reusing the exact catalog card and Paddle pricing already built for /catalog.
liveviewphoenixgettextpaddledesign-system - 17 Jul 26Sentence Selection, and the Refactor It Forced
A fifth Lesson type — the student selects every sentence in a passage that matches a Prompt, checked server-side with no reveal — and the LiveComponent-per-type refactor it took to add it safely.
liveviewsentence-selectionectodesign-systemdddarchitecture - 16 Jul 26A Profile With No Name Yet
Greeting a Student in Portuguese before they've told us their name meant loosening an invariant that had held since the Profile aggregate was born — plus a single plug that resolves EN/PT-BR the same way for every request, forever.
i18ngettextliveviewectophoenixadrddd - 15 Jul 26The Feature Was Named After the One Thing It Must Never Do
The Card asked for a banner warning users about downtime. The design system called the section 'Maintenance mode'. Building it well meant refusing that name — because the whole point of this banner is that the site stays up.
liveviewectopostgresddddesign-systemsecurity - 14 Jul 26The Data Wasn't Hidden. It Was Never Written.
An Instructor wanted to know which quiz questions his students fail most. The obvious job was building the screen. The actual job was discovering that in a try-until-correct quiz, every student ends at 100% — and nothing about their struggle was being recorded at all.
ectopostgresanalyticsliveviewevent-sourcingddd - 14 Jul 26A Green Test Suite and an Unclickable Button
Giving away one Lesson to sell a Course is a small feature with a sharp edge: the route that serves it was never allowed to run without a session. And the CSS class I reused made the Save button stop working — while all 684 tests stayed green.
liveviewphoenixcssdaisyuitestingauthorization - 13 Jul 26The Price That Lied: Dropping the Local Integer
A Course stored its price as an integer. That integer was wrong for most of the people who saw it — and it was also secretly deciding whether the Course was free. This slice deletes the column, replaces it with an explicit Pricing model, and lets Paddle be the only thing that ever prints a number.
ectoliveviewpaddledddmigrations - 13 Jul 26The Flag You Don't Have Can't Be Wrong
An inbox needs to know what you've already seen. The obvious way to build that is a boolean — and the boolean is a bug waiting for someone to forget to reset it. Two timestamps and a comparison do the same job, and one of the hardest cases falls out for free.
ectoliveviewdddpostgresgdpr - 13 Jul 26A Course Worth Clicking: Thumbnail, Intro Video, Highlights
A Course gets a catalog preview — a Thumbnail, an ungated Intro Video, and a What-you'll-learn list — authored from a new Landing page tab. Plus two bugs that only a real browser could find: a form that ate its own inputs, and a phantom row conjured by LiveView on every keystroke.
liveviewectodesign-systemuploadsddd - 13 Jul 26A Certificate Remembers. A Review Forgets.
Two records hang off the same Enrollment. One freezes your name forever; the other refuses to store it at all. That contradiction is not an oversight — it is the whole design, and it decides what happens to your words when you ask to be erased.
ectoliveviewdddadraccessibility - 11 Jul 26Word Match Lessons
A fourth Lesson type: the instructor marks Blanks in a passage with four delimiter pairs, the student refills them from a shuffled Word Bank salted with Distractors, and the server checks — returning nothing but a count of errors.
liveviewword-matchectodesign-systemdddsecurity - 27 Jun 26One page, three screens: the tabbed Course studio
Managing a Course used to mean bouncing between three separate screens — curriculum, students, settings — each with its own URL, its own header, its own back button. This slice folds them into a single tabbed page with a persistent header and the Publish button re-homed where you actually look for it. No new features inside the tabs: every tab is the old screen, relocated unchanged. The interesting decisions are structural — one LiveView for every tab, deep-linkable routes switched with push_patch, legacy URLs that redirect so nobody's bookmarks break, and a breadcrumb deliberately deleted.
sliceliveviewroutingdesign-systemrefactor - 25 Jun 26A Certificate is a promise you can't take back
Students now earn a publicly verifiable Certificate the moment they complete every Lesson in a Course. The feature is small; the decision underneath it isn't. A credential that can be revoked retroactively — because the Instructor added a Lesson, or the Student renamed their Profile — is worthless. So the Certificate is a permanent, immutable snapshot. Plus the name gate that defers issuance, why issuance lives in the domain and not the LiveView, and a 320px overflow the design mockup didn't warn us about.
slicecertificateliveviewdddadrdesign-system - 22 Jun 26A profile page, two navbars, and a Profile that doesn't exist yet
Users can now set their own name and avatar on a /profile page. The decision worth writing down isn't the form — it's that a Profile is created lazily, on the first Save, because self-registered magic-link users never had one. Plus the Ecto trap that silently skips the write, why the dropdown got built twice, and the value of reading the design system before inventing CSS.
sliceprofileliveviewectodesign-systemddd - 21 Jun 26Selling a paid course: Paddle, a webhook, and the art of waiting
A Student can now buy a paid Course. The interesting parts aren't the Buy button — they're the decisions underneath: the price you see is Paddle's, not ours (localized by IP); access is granted only by a signature-verified webhook, never by the browser saying "I paid"; and because that webhook is asynchronous, the panel shows a "Finalizing…" state that polls itself into "Continue Learning" with no refresh. Plus the idempotency that makes a double-delivered webhook a no-op.
slicepaymentspaddlewebhookenrollmentliveviewddd - 20 Jun 26Student dashboard: a home that answers "where was I?"
The Student now logs in to a "My learning" dashboard instead of the public catalog. One card per Enrollment, each with a progress bar, the lessons still remaining, and a one-click "Continue" straight to the next incomplete Lesson. No new progress math — the screen is a read model over Completions we already record — and the ordering, the edge cases, and what we deliberately left out are where the design actually lives.
slicedashboardprogressenrollmentstudentliveviewddd - 20 Jun 26Invite Students: granting a course before the account exists
The Instructor can now enroll people who have no account yet. Instead of pre-creating shell accounts and mailing magic-link accept tokens, an invite is a standing Invitation bound to an email — claimed into a real Enrollment and Profile only when that exact address confirms an account. No tokens, no expiry, no resend machinery.
sliceenrollmentinvitationinstructorliveviewdddgdpr - 19 Jun 26Manual Enrollment: granting access by email
The Instructor can now manually enroll existing students into a Published course by email — with a live preview that flags valid, already-enrolled, and invalid rows, idempotent enrollment, and a notification email. The first bite of a deliberately sliced epic.
sliceenrollmentinstructorliveviewstudentsddd - 17 Jun 26Studio Usability Polish
A batch of usability fixes across the instructor studio: a styled confirmation dialog before destructive deletes, a reorder bug fix for tied positions, names-as-links for cleaner grids, auto-focus on the first field, and a dev-only home page that maps both perspectives.
polishusabilityliveviewdesign-systemstudio - 17 Jun 26Student Takes a Quiz
The student-facing side of Quiz Lessons: one question at a time, immediate per-question feedback, a resumable in-progress attempt, a stale-reset when the instructor edits mid-attempt, and a Completion recorded on the final correct answer.
liveviewquizectodesign-systemddd - 17 Jun 26Slice 7: Quiz Lessons
Instructors can now add Quiz Lessons with single-choice Questions — a three-panel editor for writing questions, typing answer options, marking the correct one, and reordering. Students taking the quiz is a separate slice.
slicequizquestionsliveviewectodesign-system - 02 Jun 26Slice 6: Video Lessons
Instructors can now attach a Bunny Stream video to any Lesson. The video renders above the body content in the student view — paste the URL from the browser address bar and the system handles the rest.
slicevideobunny-streamlessonsliveviewcomponent - 31 May 26Slice 5: Track Lesson Progress
Students can now mark Lessons as complete, track Progress as a percentage on the Course page, and see per-Lesson checkmarks in the syllabus — all in real time, with no page reload.
sliceprogresscompletionliveviewstudents - 31 May 26Slice 4: Enroll in a Free Course
Students can now browse the course catalog, create an account, enroll in a free course in one flow, and read lessons in a clean sidebar layout — all without touching a line of JavaScript.
sliceenrollmentauthcatalogliveviewstudents - 31 May 26Code Review: 10 Security and Quality Fixes
A code review pass found 10 issues across the codebase — from a cross-course lesson access hole to a TOCTOU race in enrollment. Here's what was fixed and why.
securitycode-reviewhardeningliveviewecto - 30 May 26Slice 3: Image Upload in Lesson Editor
Course authors can now upload images directly into their Markdown lessons. Drag a file, watch the live upload feedback, and the image URL is inserted right at the cursor.
sliceuploadimageslessonsb2liveviewcomponent - 29 May 26Slice 2: Build and Publish a Course
The Instructor can now structure a Course into Chapters and Text Lessons, reorder them, write content in Markdown with a live preview, and publish the Course in one click.
slicecourseschapterslessonsmarkdownliveview - 29 May 26Slice 1: Create and Edit a Course
First working slice: the Instructor can create a Course with title, description, price, and language — and edit it after creation.
slicecoursesphoenixliveview