Function
The lesson this control marks and un-marks
Its state is read from the durable browser store, not from local state
seeded to false — a lesson completed last week shows as completed on
arrival, and a mark survives a reload and a server restart.
Before the browser has read that store, neither state is true. The control used to render the invitation across that window, which told a learner who had already finished the lesson something false about their own progress — and did it in the page's closing call to action, the last thing they look at. It now reserves the control's space instead, and commits to a state only once there is one. Every other progress-bearing component in this codebase already refuses to assert what it cannot justify; this is that rule applied to the loudest of them.
While the lesson is incomplete it invites the learner to finish it and
offers the primary "Mark as complete" button. Once complete it says so
once — the statement is the aria-live region that announces the change —
and offers a quiet "Unmark" action instead. Completion is never expressed
by disabling a control: a learner who marked a lesson by accident, or who
wants to take it again, must be able to undo it.
The click writes to two places, on purpose. It records the lesson in
the browser ProgressTracker, which is what the outline and video-list
indicators read, and it still calls the markLessonComplete Server
Action against the server's in-memory tracker. The Server Action is not
dead code: it is the path per-user progress will take when auth lands, so
that migration becomes a change of caller rather than a rewrite. Until
then the server write is ephemeral and nothing reads it back.
A confirmed mark is celebrated — the same burst the finish rule fires when playback reaches the end of the lesson, since both are the same moment to the learner. A rejected write records nothing and so celebrates nothing, and un-marking never celebrates.
The control is a Client Component because it owns a transition and reads the learner store.
Marking and un-marking go through the completion composition root
(use-lesson-completion): it shows the change at once, saves it through
the learner's Server Actions, and withdraws it if the save is refused. The
control celebrates only a mark that was saved.
The lesson's completion control, in both of its states — and in the third one, which says nothing.