forgia · dev log
Engineering log for the forgia LMS platform.
- 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 - 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 - 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 - 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 26Readable course URLs: a slug that knows when to stop changing
Courses now live at /catalog/scrum-master-fundamentos instead of /catalog/42. The slug is auto-derived from the title — no field for the Instructor to fill — and the one decision worth writing down is about time: it regenerates on every title edit while the Course is a Draft, then freezes the moment it's Published, so a shared or indexed URL never breaks. Plus the small Phoenix trick that made ~25 links change by themselves, and why the migration is the only place a -2 suffix is allowed.
sliceseoroutingslugectophoenixddd - 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 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