forgia · dev log
Engineering log for the forgia LMS platform.
- 15 Oct 26The Page After the Purchase
An enrolled Student opening their Course used to land on what was essentially the sales page. The same URL now shows their Progress Chapter by Chapter, a Continue that actually continues, and a way into any Lesson.
elixirphoenixliveviewdomain-modelstudent - 15 Oct 26One Chapter at a Time
A hundred-Lesson Course turned the Lesson sidebar into a scroll. Now only the current Chapter is open, every other one is a row you can expand, and finishing a Chapter sends you to the next one, even from a Lesson in the middle.
elixirphoenixliveviewhooksstudent - 15 Oct 26Fences That Do Something
Text Lessons gain three Rich Blocks the Instructor writes as plain markdown fences: a Checklist, an Annotated Example and a Flashcard. Rendered on the server, all Instructor text escaped, nothing stored.
elixirphoenixliveviewmarkdownsecurityinstructor - 09 Oct 26The Same Frame Every Time
Four Active Lesson types, four slightly different screens. Now they share one topbar and one Lesson list, and the two that still looked like forms got the comic print the other two already wore.
elixirphoenixliveviewcssdesign-systemstudent - 09 Oct 26In My Own Words
The home page finally has the introduction video and copy that sounds like the person who teaches the courses. The code was small. The interesting parts were a video in the wrong library and a test that refuses em dashes.
elixirphoenixliveviewgettextbunnycopyvisitor - 09 Oct 26Forward, Always
Finish a Course and the platform stopped letting you move through it. Skip an exercise and it would not let you leave. One pure function now decides where every Lesson leads, and the overview is no longer one of the answers.
elixirphoenixliveviewdomain-modelstudent - 23 Sep 26The Page Before the Course
A Course can now have a public page before it has any Lessons: the pitch, and a form that drops an address straight into Brevo. The interesting part is a one-line rule that looked like it fell out of the status field and didn't — because status is exactly the field that moves back.
elixirphoenixliveviewectobrevotestinginstructor - 23 Sep 26One Box, One Write
A visitor joining one Course's Waitlist can now also ask to hear about every future launch. The checkbox was the easy part. Deciding what a single signup means when it touches two lists took the rest.
elixirphoenixliveviewectobrevowaitlistinstructor - 23 Sep 26Coming Soon, in the Grid
Announced Courses now show up in the public catalog, carrying a band that says when they open, with the month in the visitor's own language. It is one line of query and a lot of deciding what a card must not say.
elixirphoenixliveviewectogettextcataloginstructor - 17 Sep 26The Patch Is Not the Form
A bug report blamed the video modal. The modal was innocent — the title was already gone before anyone opened it. What LiveView actually puts on the wire for an input-level phx-change, and why a green test suite could never have told us.
elixirphoenixliveviewectotestinginstructor - 17 Sep 26Green Tests, Twitching Flag
The Quiz got the arcade layout — half-tone paper, ink outlines, a progress track with a hopping flag, confetti. The deliverable was motion, and the test suite is structurally blind to motion. What you assert when the framework cannot see the feature.
elixirphoenixliveviewcssjavascripttestingdesign-systemstudent - 16 Sep 26The Prototype Overreached
A Character now delivers a Sentence Selection as speech instead of prose. The layout that specified it also quietly rewrote the feedback rules — including one that would have handed the Student the answer key — so this item shipped the frame and refused the rules.
elixirphoenixliveviewectocssgettextdesign-systemstudent - 14 Sep 26The Second Uploader
An Instructor can now build a cast of Characters once and reuse it everywhere. The screen is two text fields and two image slots — and putting a second uploader on one page broke a component that had worked fine on four others, twice, in ways no test in the suite could have seen.
elixirphoenixliveviewectouploadsgettextinstructor - 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 Number Is Always There, and It Is Wrong at First
Bunny reports a video's length at every stage of transcoding, including the stages where it doesn't know yet. That one fact — a required field that is present and false — decided the shape of the whole integration, and is why the client answers with three outcomes when the caller only needs two.
elixirphoenixintegrationmoxtestingdomain-model - 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 - 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 - 09 Aug 26The Invitation That Knows Who You Are
An Invitation is bound to one exact email, so a typo at sign-up throws it away. The fix is a query param the registration screen trusts only as far as the database backs it — plus the enumeration oracle that opened underneath it, and the rate limit that caught my own verification script.
securityonboardingphoenixemaildesign-system - 23 Jul 26A Content-Security-Policy, and Three Things It Caught Before Shipping
A CSP header allowlisting our own scripts plus Paddle and Bunny, shipped report-only first — which is exactly what caught a frame-src fallback misunderstanding, a Paddle CDN subdomain, and Course thumbnails actually living on Backblaze B2, not local disk.
securityphoenixplugpaddlebunny - 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 - 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 - 14 Jul 26The Rate Limiter That Throttles Everyone Except the Attacker
A magic-link login has no password to brute-force, so the threat isn't guessing — it's spending. Someone else's inbox, and your finite mail quota. The obvious per-IP limit protects neither, and behind a reverse proxy it does something much worse than nothing.
securityphoenixplughammerrate-limiting - 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 - 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 - 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