Clamping currentTime in the seeking/seeked handlers is ignored on progressive
MP4 (the forward seek takes effect immediately), so the must-watch lock leaked
even though the snackbar showed.
Block at the source: a Plyr `seek` listener returns false for targets past the
watched point, which skips Plyr's default seek so the media never moves.
Rewinding and re-seeking up to the watched point stay allowed; the snackbar
fires when a forward skip is blocked. furthestSeconds is now shared between the
listener and the playback tracker. The seeking/seeked clamp is kept as a
backstop for keyboard/programmatic seeks that bypass the progress bar.
The lock clamped currentTime only during the seeking event. HLS defers the
forward seek until the fragment loads, so that clamp caught it; but for
progressive MP4 the forward seek takes effect immediately and the mid-seek
write is ignored, so playback leaked past the watched point (snackbar showed,
no snap-back).
Keep the immediate seeking-time clamp (HLS relies on it to avoid buffering
ahead) and add an authoritative re-clamp in seeked: if the playhead still
landed past the limit, snap back via a backward seek into buffered content,
which holds. Snackbar now fires once per blocked gesture and only when a
clamp actually happens.
Resetting currentTime inside the seeking handler doesn't reliably stick
across sources (HLS re-buffering, emulated YouTube events, coalesced native
seeks), so the snackbar showed but playback continued ahead. The timeupdate
handler then ignored the large forward jump via its delta < 2 guard, making
the skip permanent.
Re-clamp in seeked once the position settles, and add a timeupdate backstop
that snaps back forward jumps (delta >= 2) landing past furthestSeconds.
Adds live-class session scheduling on lessons (join link, date/time,
timezone) with automatic 24h and 1h reminder emails plus a calendar
invite (.ics), a time-change notification with an updated invite, a
teacher-authored welcome email and session invite on enrollment, and a
"Notify students" quiz nudge.
LMS gets an upcoming live-sessions card and Join buttons, plus a
reusable live-session card component (3 states) in @cio/ui with a
Storybook story. The sidebar content tree now reflects sequential
progression locking like the content list, and the course-completion
confetti regression is fixed.
Org-based emails no longer reference ClassroomIO by name. Docs add
live-class, welcome-email, and course-progression guides, expand the
certificates guide (design, signatures, corrected labels), move
plan-required callouts to the top, flatten how-to-guides to the docs
root with redirects, and add screenshots. Includes an add-docs-image
skill.
The course cover Upload tab stored the raw file and relied on CSS
object-cover to fake the 280x200 frame at display time. Route uploads
through the existing ImageCropper at a 280/200 aspect ratio so the saved
asset matches the frame. Also teach the cropper UploadTrigger to accept
drag-and-drop so the prior drop-to-upload UX is preserved.
The footer grid (brand, socials, columns, CTAs) was only rendered for
the 'classic' variant. For 7 other variants (minimal, bold, saas, tech,
studio, corporate, terminal) the grid content was defined as a snippet
but never invoked, silently dropping social links and other footer
content.
The active tab's data-[state=active]:text-foreground rule outranked the
plain text-primary hover class, so hovering the active tab left white
text on the white hover pill (invisible in dark mode). Mark the hover
text-primary as important so the hovered active tab matches the hover
color of inactive tabs.
The org landing templates used 'Program(s)' in catalog headings, the
'view all' link, and the studio/terminal hero counters. That collided
with the app's actual Programs feature and confused users into thinking
a landing-page Program meant an app Program. Default copy now says
'Courses'; renamed the programsCount var to coursesCount to match.
- Allow coursesNo to be omitted in instructor metadata (not required)
- Allow reward description to be omitted when reward object is present
- Replace raw {@html} with SafeHtmlContent in course-pricing,
course-info-blocks, and embed landing-page components
The moving-icons family sizes its SVG via a numeric `size` prop and only
forwards `class` to the wrapper div, so the cap's `size-3.5` class never
reached the SVG and icons rendered at the default 24px, overflowing the
cap. Pass size={14} to the cap icon.
The auth button derived from stores that hydrate in stages, so it
flashed through Login -> Dashboard -> Go to LMS as isLoggedIn and the
role/basePath settled at different times. Gate the label on
appInitApi.isInitializedAndReady so it only renders once the account
(and thus role) has loaded, and show a spinner in the button while a
logged-in user's experience resolves. Add separate loading and disabled
flags to authAction and wire them through all theme navs.
The hovered tab used text-primary-foreground for inactive tabs, which is
near-white — the same token as the hover pill background — so the label
disappeared. Drop the override and keep the hover label on text-primary.
Loosen the custom-domain regex to accept apex domains (1+ labels) in
addition to subdomains; the TLD already allowed any letters. Suppress
the CNAME record for apex hostnames since a CNAME is invalid at the zone
apex, and surface only the A record there. Drop the frontend apex
rejection and the misleading ".com" input suffix, and render the
domain-input prefix/suffix addons only when set.
Serve widgets from short public URLs via tenant-router with R2-backed
assets and runtime API_URL injection, so copy-paste snippets hide infra
paths. Update widget payloads, CSP, upload purge URLs, and dev-only API
inject; document format/stage workflow in AGENTS.md and fix ui-prefix-check
YAML syntax.
Gate student content by completion order, track per-video watch progress
with seek enforcement, and auto-complete lessons at the configured
threshold. Show ring progress in the lesson header, keep watch progress
on rewatch, and sync progression locks client-side so the next item
unlocks after completion.
New features:
- HLS video encoding/streaming (hls routes, hls-encoder) and a media
transcripts pipeline (transcripts route + media-transcript queries)
- AI assistant chat vs. run model: agent-course-generation worker/queue,
run-monitor pane, chat-changes card, content ask-ai bar
- Per-theme course-page previews across org landing-page themes (bold,
classic, corporate, editorial, etc.) with a shared course token/component set
- View-as-student mode and login-link / link-invite invite flows
Refactor:
- Extract packages/core consolidating config (env, storage, dashboard-url),
services and utils previously duplicated under apps/api/src/config
- Remove legacy agent service modules (chat-tools, chat-context,
context-window, document, fetch-url, lesson-content)
- Relax Cross-Origin-Resource-Policy to allow cross-origin media embedding
- Add format script to tenant-router so turbo run format covers it
- Added `AssignCoursesModal`, `AudienceMemberRow`, `AudienceTable`, and related components for improved audience management UI.
- Implemented audience invite actions including resend and revoke functionality.
- Updated audience API to support importing members and assigning courses.
- Refactored audience data handling to include status and invite management.
- Improved overall user experience with pagination and selection features in the audience table.