forgia · dev log
Engineering log for the forgia LMS platform.
- 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