Lms Ui

Capabilities
@wabbit/tome-lms-uiv0.10.2

React render layer for the Tome LMS — lesson player (progress bar, course shell, curriculum sidebar, quiz/assignment renderers, notes panel) and the LearnerShell app shell; depends on @wabbit/tome-lms and @wabbit/tome-ui.

Installnpm install @wabbit/tome-lms-ui

Overview

@wabbit/tome-lms-ui

The React render layer for the Tome LMS: the lesson player (progress bar, course shell, curriculum sidebar, quiz/assignment renderers, prev/next nav, notes panel) and the LearnerShell app-shell (sidebar, nav groups, header, user menu, active-quest widgets). app-adjacent layer per root ARCHITECTURE.md — depends on @wabbit/tome-lms and @wabbit/tome-ui.

Install

pnpm add @wabbit/tome-lms-ui

| Peer | Range | |---|---| | next | >=15.0.0 | | payload | >=3.67.0 | | react | ^19.0.0 | | lucide-react | >=0.460.0 |

@wabbit/tome-lms, @wabbit/tome-ui, and qrcode are regular dependencies (not peers).

60-second quickstart

// A lesson page (client component tree under the course shell)
import { CourseShell, LessonContent, ProgressBar } from '@wabbit/tome-lms-ui'
import '@wabbit/tome-lms-ui/base'

<CourseShell course={course} enrollment={enrollment}>
  <ProgressBar value={progressPct} />
  <LessonContent lesson={activeLesson} />
</CourseShell>
// payload.config.ts — LearnerShell collection wiring
import { withLearnerShell } from '@wabbit/tome-lms-ui/payload'
export default buildConfig(withLearnerShell(baseConfig))
// Server Component / route handler — active-quest data for the shell
import { getActiveEnrollments } from '@wabbit/tome-lms-ui/server'

Public API

40+ components are exported from the root barrel; the load-bearing groups:

| Group | Examples | Subpath | |---|---|---| | Lesson player | CourseShell, CourseShellRoot, CourseShellLayout, CurriculumSidebar, LessonContent, ProgressBar, PrevNextNav, NotesPanel, OnThisPage, LessonVideo | root | | Gating | VisibilityGate, deriveAccessState, resolveCtaState, EnrollmentCTA, PrerequisiteGate, ExpiredEnrollmentNotice | root | | Renderers | QuizRenderer, AssignmentRenderer, defaultBlockRenderers (VideoBlock/QuizBlock/AssignmentBlock/CodeBlock/NoteBlock) | root | | Catalog + landing | CourseCatalogList, CourseCard, AcademyCard, AcademyDirectory, CourseLandingHero, CourseLandingOutline, CourseLandingInstructorSection, CourseLandingReviewsSection, LeaveReviewForm, CourseLandingRelatedSection | root | | Dashboard | DashboardEnrollmentList, EnrollmentSummaryCard, DashboardGradeTable, CourseGradeDetail, DashboardCertificateList, CertificateCard, CertificateVerificationView, CertificateTemplate, DashboardNotesIndex, DashboardLeaderboardTable, StudentProfileEditor, StudentProfileForm | root | | LearnerShell | LearnerShell, LearnerSidebar, LearnerNavGroup, LearnerNavItem, LearnerNavSubItem, LearnerHeader, LearnerUserMenu, LearnerInsetCard, ActiveQuestSection, ActiveQuestPicker | root | | Nav resolution | resolveLearnerNav, DOMAIN_ORDER, DOMAIN_LABEL, DOMAIN_DEFAULT_OPEN, LEARNER_UI_PREFS_SLUG | root (re-exported from ./nav) | | Hooks | useCurrentLesson, useCoursePrerequisites, usePhaseState, useCompletionTracking, useLearnerPrefs | root / ./hooks/usePrefs | | Server data | getActiveEnrollments, getActiveQuestData | ./server | | Payload config | buildLearnerUiPrefsCollection, learnerUiPrefsCollection, withLearnerShell | ./payload | | CSS | ./tokens, ./base, ./components/* (per-component modules) | CSS |

Server / client posture — the exemplary split, canonized

This package is the reference pattern for a Tome UI package that mixes render components with data access — new packages with a similar shape should copy this structure rather than reinvent one:

  • Root barrel (src/index.ts) — the render-component surface. Roughly 40 of ~66 .tsx files declare 'use client'; the rest are server-renderable presentational components with no interactivity. The barrel's own header comment states the rule explicitly: "Server-only modules ship from `@wabbit/tome-lms-ui/server` and `@wabbit/tome-lms-ui/payload` subpaths and intentionally do NOT surface here."
  • `./server` subpath — guarded with import 'server-only' at the top of the barrel, so accidental client-bundling fails at build time rather than leaking data-layer code. Exports getActiveEnrollments/getActiveQuestData — pure data fetchers, zero React.
  • `./payload` subpath — server-only Payload config helpers (withLearnerShell, buildLearnerUiPrefsCollection). Its header comment is explicit about the split rationale: "Sites import `withLearnerShell()` from here at config time. The hook lives separately at `@wabbit/tome-lms-ui/hooks/usePrefs` (client-only)."
  • `./nav` subpath — deliberately carries no client/server directive at all. Its header comment states why: "No client/server directive — all values here are server-safe pure data and pure functions, importable from either side of the boundary." resolveLearnerNav and the domain constants run identically in a Server Component computing initial nav state and a Client Component re-deriving it after a client-side mutation.
  • `./hooks/usePrefs` — the one client-only hook broken out to its own subpath specifically so it doesn't have to live inside the server-guarded ./payload module.

The pattern in one sentence: render components on the root barrel, data-access behind a `server-only` guard on a named subpath, pure isomorphic logic on its own subpath with no directive at all, and the one client-only hook that doesn't fit elsewhere gets a dedicated subpath.

Extending

New lesson-player or dashboard components follow the root-barrel pattern (co-located .tsx + CSS Module, exported from src/index.ts, 'use client' unless genuinely presentational). New server data helpers go in src/server/ behind the server-only import. See docs/superpowers/specs/2026-05-04-tome-lms-ui-learner-shell-design.md for the LearnerShell's nav-manifest resolution contract before adding a new nav domain.

Design history

  • docs/superpowers/specs/2026-04-14-tome-lms-v2-2-lesson-player-design.md, 2026-04-14-tome-lms-v2-2-lesson-player-plan.md — lesson player origin
  • docs/superpowers/specs/2026-05-04-tome-lms-ui-learner-shell-design.md, 2026-05-04-tome-lms-ui-learner-shell-build-plan.md, 2026-05-04-tome-lms-ui-learner-shell-orchestrate-prompt.md — LearnerShell
  • docs/superpowers/specs/2026-05-17-tome-lms-ui-editorial-course-shell-design.md — editorial course shell variant
  • docs/claude-gotchas.md → Payload / Typing / Layer Design section — "LMS server-write mutations: the promotion trigger already fired — and the factoring is inner-layer-only" is directly relevant to anyone adding a new server data helper here

Decisions that shaped this package

See docs/specs-map.md in this repo for the full package→spec routing table, and the Business wiki's docs/superpowers/specs/ for the source specs cited below.

  • The LearnerShell's five nav domains (Adventure/Library/Progress/Community/Account) are fixed; only items slot in — architecturally mirrors @wabbit/tome-admin's sidebar manifest pattern but tuned for the consumer-side learner role; adding a sixth domain requires a spec amendment, not a config change — 2026-05-04-tome-lms-ui-learner-shell-design.md.
  • The sidebar's Active Quest shows only the current module/chain, not the full curriculum map — the Compass page stays the canonical full-visibility surface (including locked content); the sidebar is a resume affordance, not a duplicate map — 2026-05-04-tome-lms-ui-learner-shell-design.md.
  • `StudentProfileEditor` is deprecated in favor of folding its hardcoded fields (goals/experience/interests/archetype/custom) into `StudentProfileForm`'s schema vocabulary — the two components owned overlapping "edit student profile" contracts with no doc distinguishing them; a new useTagList extraction made the schema-driven merge cheap, so one surface wins instead of two — R4 brief ruling #5, 2026-07-12-tome-r4-convergence-decisions-brief.md (code: src/components/StudentProfileEditor/StudentProfileEditor.tsx carries the @deprecated tag pointing at StudentProfileForm, confirmed present).
  • Server-only data access is split onto named subpaths (`./server`, `./payload`) rather than mixed into the root render-component barrel — ./server is guarded by import 'server-only' so accidental client-bundling of data-layer code fails at build time instead of leaking; ./nav carries no directive at all because its contents are genuinely isomorphic pure data/functions — 2026-05-04-tome-lms-ui-learner-shell-design.md.

Exports

  • @wabbit/tome-lms-ui
  • @wabbit/tome-lms-ui/tokens
  • @wabbit/tome-lms-ui/base
  • @wabbit/tome-lms-ui/components/progress-bar
  • @wabbit/tome-lms-ui/components/course-shell
  • @wabbit/tome-lms-ui/components/curriculum-sidebar
  • @wabbit/tome-lms-ui/components/lesson-content
  • @wabbit/tome-lms-ui/components/visibility-gate
  • @wabbit/tome-lms-ui/components/enrollment-cta
  • @wabbit/tome-lms-ui/components/mark-complete-button
  • @wabbit/tome-lms-ui/components/prev-next-nav
  • @wabbit/tome-lms-ui/components/notes-panel
  • @wabbit/tome-lms-ui/components/on-this-page
  • @wabbit/tome-lms-ui/components/quiz-renderer
  • @wabbit/tome-lms-ui/components/assignment-renderer
  • @wabbit/tome-lms-ui/components/prerequisite-gate
  • @wabbit/tome-lms-ui/components/expired-enrollment-notice
  • @wabbit/tome-lms-ui/components/phase-indicator
  • @wabbit/tome-lms-ui/components/phase-transition-screen
  • @wabbit/tome-lms-ui/components/student-profile-form
  • @wabbit/tome-lms-ui/components/suggested-next-panel
  • @wabbit/tome-lms-ui/components/quest-map
  • @wabbit/tome-lms-ui/components/lesson-video
  • @wabbit/tome-lms-ui/components/course-catalog-list
  • @wabbit/tome-lms-ui/components/course-card
  • @wabbit/tome-lms-ui/components/academy-card
  • @wabbit/tome-lms-ui/components/academy-directory
  • @wabbit/tome-lms-ui/components/course-landing-hero
  • @wabbit/tome-lms-ui/components/course-landing-outline
  • @wabbit/tome-lms-ui/components/course-landing-instructor-section
  • @wabbit/tome-lms-ui/components/course-landing-reviews-section
  • @wabbit/tome-lms-ui/components/leave-review-form
  • @wabbit/tome-lms-ui/components/course-landing-related-section
  • @wabbit/tome-lms-ui/components/dashboard-enrollment-list
  • @wabbit/tome-lms-ui/components/enrollment-summary-card
  • @wabbit/tome-lms-ui/components/dashboard-grade-table
  • @wabbit/tome-lms-ui/components/course-grade-detail
  • @wabbit/tome-lms-ui/components/dashboard-certificate-list
  • @wabbit/tome-lms-ui/components/certificate-card
  • @wabbit/tome-lms-ui/components/certificate-verification-view
  • @wabbit/tome-lms-ui/components/certificate-template
  • @wabbit/tome-lms-ui/components/dashboard-notes-index
  • @wabbit/tome-lms-ui/components/dashboard-leaderboard-table
  • @wabbit/tome-lms-ui/components/student-profile-editor
  • @wabbit/tome-lms-ui/components/learner-shell
  • @wabbit/tome-lms-ui/components/learner-sidebar
  • @wabbit/tome-lms-ui/components/learner-nav-group
  • @wabbit/tome-lms-ui/components/learner-nav-group/state
  • @wabbit/tome-lms-ui/components/learner-nav-item
  • @wabbit/tome-lms-ui/components/learner-nav-sub-item
  • @wabbit/tome-lms-ui/components/learner-header
  • @wabbit/tome-lms-ui/components/learner-user-menu
  • @wabbit/tome-lms-ui/components/learner-inset-card
  • @wabbit/tome-lms-ui/components/active-quest-section
  • @wabbit/tome-lms-ui/components/active-quest-picker
  • @wabbit/tome-lms-ui/server
  • @wabbit/tome-lms-ui/nav
  • @wabbit/tome-lms-ui/payload
  • @wabbit/tome-lms-ui/hooks/usePrefs

Changelog

v0.10.2patch

019150b: Fixed a defect where `QuizRenderer` and `AssignmentRenderer` never read the authored fields off blocks stored in the nested `{ blockType, blockData: {...} }` shape — the shape EDU's real lesson rows use, with no `id` on the outer block object. `asQuizBlock`/`asAssignmentBlock` returned the block AS-IS and every field read (`quiz.questions`, `quiz.maxAttempts`, `quiz.showCorrectAnswers`, `quiz.passingScore`, `assignment.submissionType`, `assignment.points`, `assignment.choices`, …) came off the OUTER object, which only carries `blockType`/`blockData` — so `questions` was always `undefined`, no quiz question ever rendered (students saw only a bare Submit button), and every submit graded 0/n; assignment blocks silently collapsed to the default `text` submission type regardless of what was authored. Both narrowing functions now unwrap `block.blockData` when present, falling back to the block itself when it's absent — the pre-existing flat shape (`{ id, title, questions, ... }`, still used by every other consumer and by this package's own pre-existing tests) keeps working byte-for-byte. `readBlockId` on both renderers now also falls back through `block.blockData?.id` and `block.blockID` before giving up — EDU's real rows currently carry no id anywhere, so it still returns `undefined` for them, and the two-step quiz-attempt-start / richer-assignment-submit dispatch paths fall back exactly as they did before (`onQuizAttemptStart` receives `''` via its existing `?? ''` guard; `onAssignmentSubmitWithBlock` is skipped in favor of `onAssignmentSubmit` since it only fires when an id is truthy) — no `d.ts` contract change was needed for either. `LessonContent`'s block dispatcher already routed this shape to both renderers correctly (its `blockType` lookup handles the `'quizBlock'`/`'quiz'` and `'assignmentBlock'`/`'assignment'` key spellings) — the renderers' own field reads were the only break. New tests cover both shapes side by side (`QuizRenderer.blockdata-unwrap.test.tsx`, `AssignmentRenderer.blockdata-unwrap.test.tsx`), including a nested fixture copied verbatim from the verified live EDU row shape, and were demonstrated red against the unfixed source before the fix was applied.

  • 019150b: Fixed a defect where `QuizRenderer` and `AssignmentRenderer` never read the authored fields off blocks stored in the nested `{ blockType, blockData: {...} }` shape — the shape EDU's real lesson rows use, with no `id` on the outer block object. `asQuizBlock`/`asAssignmentBlock` returned the block AS-IS and every field read (`quiz.questions`, `quiz.maxAttempts`, `quiz.showCorrectAnswers`, `quiz.passingScore`, `assignment.submissionType`, `assignment.points`, `assignment.choices`, …) came off the OUTER object, which only carries `blockType`/`blockData` — so `questions` was always `undefined`, no quiz question ever rendered (students saw only a bare Submit button), and every submit graded 0/n; assignment blocks silently collapsed to the default `text` submission type regardless of what was authored. Both narrowing functions now unwrap `block.blockData` when present, falling back to the block itself when it's absent — the pre-existing flat shape (`{ id, title, questions, ... }`, still used by every other consumer and by this package's own pre-existing tests) keeps working byte-for-byte. `readBlockId` on both renderers now also falls back through `block.blockData?.id` and `block.blockID` before giving up — EDU's real rows currently carry no id anywhere, so it still returns `undefined` for them, and the two-step quiz-attempt-start / richer-assignment-submit dispatch paths fall back exactly as they did before (`onQuizAttemptStart` receives `''` via its existing `?? ''` guard; `onAssignmentSubmitWithBlock` is skipped in favor of `onAssignmentSubmit` since it only fires when an id is truthy) — no `d.ts` contract change was needed for either. `LessonContent`'s block dispatcher already routed this shape to both renderers correctly (its `blockType` lookup handles the `'quizBlock'`/`'quiz'` and `'assignmentBlock'`/`'assignment'` key spellings) — the renderers' own field reads were the only break. New tests cover both shapes side by side (`QuizRenderer.blockdata-unwrap.test.tsx`, `AssignmentRenderer.blockdata-unwrap.test.tsx`), including a nested fixture copied verbatim from the verified live EDU row shape, and were demonstrated red against the unfixed source before the fix was applied.
v0.10.1patch

f9b479c: Re-exported `QuizAttemptStartResult` from the package root. It was already part of `CourseShellRoot`'s public `onQuizAttemptStart` signature but had no supported import path — consumers had to mirror its shape structurally instead of importing it.

  • f9b479c: Re-exported `QuizAttemptStartResult` from the package root. It was already part of `CourseShellRoot`'s public `onQuizAttemptStart` signature but had no supported import path — consumers had to mirror its shape structurally instead of importing it.
v0.10.0minor

433bec6: CourseShell gains an additive, backward-compatible two-step submit contract for quizzes and assignments. - `onQuizAttemptStart?: (lessonId, quizBlockId) => Promise<{ attemptId, cooldownExpiry?, attemptsRemaining? }>` and `onQuizAttemptSubmit?: (attemptId, answers) => Promise<QuizResult>` let a site wire the platform's two-step `startQuizAttempt` → `submitQuizAttempt` mutation flow (`@wabbit/tome-lms/server`) into `QuizRenderer`. When both are provided, `QuizRenderer` starts an attempt on mount (passing the quiz block's own id), surfaces a disabled/cooldown-expiry submit state when the start response is locked out, and dispatches submit through the attempt id instead of the lesson id. - `onAssignmentSubmitWithBlock?: (lessonId, assignmentBlockId, submission) => Promise<void>` lets `AssignmentRenderer` carry the authored assignment block's own id through to sites whose mutation layer needs it, when the block has an id. - Both are purely additive: `onQuizSubmit`/`onAssignmentSubmit` keep working byte-identically when the new props are absent, and the two-step quiz path only activates when BOTH `onQuizAttemptStart` and `onQuizAttemptSubmit` are wired (partial wiring falls back to the single-call contract).

  • 433bec6: CourseShell gains an additive, backward-compatible two-step submit contract for quizzes and assignments. - `onQuizAttemptStart?: (lessonId, quizBlockId) => Promise<{ attemptId, cooldownExpiry?, attemptsRemaining? }>` and `onQuizAttemptSubmit?: (attemptId, answers) => Promise<QuizResult>` let a site wire the platform's two-step `startQuizAttempt` → `submitQuizAttempt` mutation flow (`@wabbit/tome-lms/server`) into `QuizRenderer`. When both are provided, `QuizRenderer` starts an attempt on mount (passing the quiz block's own id), surfaces a disabled/cooldown-expiry submit state when the start response is locked out, and dispatches submit through the attempt id instead of the lesson id. - `onAssignmentSubmitWithBlock?: (lessonId, assignmentBlockId, submission) => Promise<void>` lets `AssignmentRenderer` carry the authored assignment block's own id through to sites whose mutation layer needs it, when the block has an id. - Both are purely additive: `onQuizSubmit`/`onAssignmentSubmit` keep working byte-identically when the new props are absent, and the two-step quiz path only activates when BOTH `onQuizAttemptStart` and `onQuizAttemptSubmit` are wired (partial wiring falls back to the single-call contract).
v0.9.8patch

e7277a7: Packaging hygiene — `@wabbit/tome-lms` and `@wabbit/tome-ui` move from hard `dependencies` to `peerDependencies` (+ `workspace:*` devDependency twins). No source change, no behavior change. `@wabbit/tome-lms-ui` was the ONLY `layer: app` package in the monorepo declaring `@wabbit/*` packages as runtime `dependencies`. Every sibling — `tome-admin`, `tome-admin-pro`, `tome-chrome`, `tome-dispatch`, `tome-longform`, `tome-readout` — declares its engines as required peers with a `workspace:*` devDependency twin, and carries no `peerDependenciesMeta` entry for them. This package now matches that convention exactly. Both were declared `workspace:^`, which pnpm rewrites at pack time — so the published tarball carried a hard `^0.x` runtime dependency on the LMS engine. A consumer mounting `tome-lms-ui` always mounts `tome-lms` itself (they are one licensable family, `learning`), so npm was installing and version-resolving a second copy of an engine the consumer already supplies. The peer declaration lets the consumer's copy satisfy it. Ranges follow the sibling convention (`>=MIN <NEXTMAJOR`): `@wabbit/tome-ui` at `>=0.9.0 <1.0.0`, matching `tome-chrome`/`tome-dispatch`/`tome-longform`/`tome-readout`/`tome-admin` verbatim. `@wabbit/tome-lms` at `>=0.12.0 <1.0.0` — deliberately holding the floor the published tarball already carried rather than raising it to the current 0.14.0, so this change alters the KIND of dependency without narrowing the version contract consumers already rely on. (`tome-sc` declares the same engine at `>=0.9.0 <1.0.0`; the higher floor here is the conservative choice, not a conflict.) Both peers are REQUIRED (no `peerDependenciesMeta` entry), which is the honest declaration and matches the sibling packages: - `@wabbit/tome-lms` is statically value-imported in exactly one place — `src/server/active-quest.ts` imports `getActiveEnrollments`/`getLessonChain` from `@wabbit/tome-lms/server`, reachable via the `./server` subpath. Everything else referencing the engine is a comment or a deliberately re-declared type (see `src/types.ts`, which re-declares `CertificationAwardData` specifically so consumers "don't have to depend directly on @wabbit/tome-lms types just for the shell"). An optional peer would be dishonest for `./server`. - `@wabbit/tome-ui` has ZERO imports anywhere in `src/` — its only references are prose comments and three CSS files that consume `--tome-color-*` Layer-2 tokens shipped by `@wabbit/tome-ui/tokens`. The dependency is real but ambient: the consumer must load those tokens for this package to render correctly. A peer declares that requirement without forcing a runtime install of a module nothing imports, which is precisely what the sibling packages do. The `workspace:*` devDependency twins are required, not optional bookkeeping: `assert:declared-imports` fails a statically-imported internal package that is peer-only ("add a workspace:\* devDependency so pnpm topology orders the build"), because peers are invisible to pnpm's build ordering on cold checkouts. `pnpm assert:declared-imports` is green after the change. Guarded going forward by a new `assert:app-layer-peers` check (repo tooling — no changeset of its own, since it ships no package), wired into `platform-discipline` CI pre-build alongside the other manifest asserts. It fails any `layer: app` package that declares a `@wabbit/*` engine in `dependencies`, and carries an empty, stale-entry-failing allowlist so a future exception has to be argued rather than assumed. NOT published here; publishing is David-gated behind the irreversible-publish preflight checklist.

  • e7277a7: Packaging hygiene — `@wabbit/tome-lms` and `@wabbit/tome-ui` move from hard `dependencies` to `peerDependencies` (+ `workspace:*` devDependency twins). No source change, no behavior change. `@wabbit/tome-lms-ui` was the ONLY `layer: app` package in the monorepo declaring `@wabbit/*` packages as runtime `dependencies`. Every sibling — `tome-admin`, `tome-admin-pro`, `tome-chrome`, `tome-dispatch`, `tome-longform`, `tome-readout` — declares its engines as required peers with a `workspace:*` devDependency twin, and carries no `peerDependenciesMeta` entry for them. This package now matches that convention exactly. Both were declared `workspace:^`, which pnpm rewrites at pack time — so the published tarball carried a hard `^0.x` runtime dependency on the LMS engine. A consumer mounting `tome-lms-ui` always mounts `tome-lms` itself (they are one licensable family, `learning`), so npm was installing and version-resolving a second copy of an engine the consumer already supplies. The peer declaration lets the consumer's copy satisfy it. Ranges follow the sibling convention (`>=MIN <NEXTMAJOR`): `@wabbit/tome-ui` at `>=0.9.0 <1.0.0`, matching `tome-chrome`/`tome-dispatch`/`tome-longform`/`tome-readout`/`tome-admin` verbatim. `@wabbit/tome-lms` at `>=0.12.0 <1.0.0` — deliberately holding the floor the published tarball already carried rather than raising it to the current 0.14.0, so this change alters the KIND of dependency without narrowing the version contract consumers already rely on. (`tome-sc` declares the same engine at `>=0.9.0 <1.0.0`; the higher floor here is the conservative choice, not a conflict.) Both peers are REQUIRED (no `peerDependenciesMeta` entry), which is the honest declaration and matches the sibling packages: - `@wabbit/tome-lms` is statically value-imported in exactly one place — `src/server/active-quest.ts` imports `getActiveEnrollments`/`getLessonChain` from `@wabbit/tome-lms/server`, reachable via the `./server` subpath. Everything else referencing the engine is a comment or a deliberately re-declared type (see `src/types.ts`, which re-declares `CertificationAwardData` specifically so consumers "don't have to depend directly on @wabbit/tome-lms types just for the shell"). An optional peer would be dishonest for `./server`. - `@wabbit/tome-ui` has ZERO imports anywhere in `src/` — its only references are prose comments and three CSS files that consume `--tome-color-*` Layer-2 tokens shipped by `@wabbit/tome-ui/tokens`. The dependency is real but ambient: the consumer must load those tokens for this package to render correctly. A peer declares that requirement without forcing a runtime install of a module nothing imports, which is precisely what the sibling packages do. The `workspace:*` devDependency twins are required, not optional bookkeeping: `assert:declared-imports` fails a statically-imported internal package that is peer-only ("add a workspace:\* devDependency so pnpm topology orders the build"), because peers are invisible to pnpm's build ordering on cold checkouts. `pnpm assert:declared-imports` is green after the change. Guarded going forward by a new `assert:app-layer-peers` check (repo tooling — no changeset of its own, since it ships no package), wired into `platform-discipline` CI pre-build alongside the other manifest asserts. It fails any `layer: app` package that declares a `@wabbit/*` engine in `dependencies`, and carries an empty, stale-entry-failing allowlist so a future exception has to be argued rather than assumed. NOT published here; publishing is David-gated behind the irreversible-publish preflight checklist.
v0.9.7patch

Updated dependencies [57b7a43]

  • Updated dependencies [57b7a43]
  • Updated dependencies [fa9c30b]
  • Updated dependencies [fc30bf6] - @wabbit/tome-lms@0.14.0
v0.9.6patch

Updated dependencies - @wabbit/tome-lms@0.13.0

  • Updated dependencies - @wabbit/tome-lms@0.13.0
v0.9.5patch

Updated dependencies [47a8d78] - @wabbit/tome-lms@0.12.0

  • Updated dependencies [47a8d78] - @wabbit/tome-lms@0.12.0
v0.9.4patch

Updated dependencies [0a070e0] - @wabbit/tome-ui@0.11.0

  • Updated dependencies [0a070e0] - @wabbit/tome-ui@0.11.0
v0.9.3patch

Updated dependencies [68465b3] - @wabbit/tome-lms@0.11.0

  • Updated dependencies [68465b3] - @wabbit/tome-lms@0.11.0
v0.9.1patch

Updated dependencies - @wabbit/tome-ui@0.10.0

  • Updated dependencies - @wabbit/tome-ui@0.10.0
v0.9.0minor

CourseCatalogList: the course-card CTA no longer prefers --pc-_ brand tokens or falls back to the safety-yellow literal; it now rides --lms-color-primary-cta → --tome-color-primary (neutral). Industrial-lineage consumers get the previous look via @wabbit/tome-blocks-industrial-theme, which defines the --lms-color-_ hooks inside its theme scope.

  • CourseCatalogList: the course-card CTA no longer prefers --pc-_ brand tokens or falls back to the safety-yellow literal; it now rides --lms-color-primary-cta → --tome-color-primary (neutral). Industrial-lineage consumers get the previous look via @wabbit/tome-blocks-industrial-theme, which defines the --lms-color-_ hooks inside its theme scope.
v0.8.1patch

1173d00: Two fixes from the wabbit EDU Phase 5 prod dogfood (2026-07-18): - **lms-ui:** CurriculumSidebar now derives per-row effective access via `deriveAccessState` (enrollment/tier-aware) instead of disabling every `locked`-visibility row — enrolled members can navigate locked lessons from the rail, matching what the content pane already grants. `CurriculumTree` gains an optional `resolveLessonAccess` prop; without it the visibility-tier fallback (anon/landing behavior) is unchanged. - **lms:** the `course-completion` badge check moved out of `onLessonCompletion` (it fired per LESSON, awarding course-completion badges on a student's first completed lesson) into a new `awardCourseCompletionBadges` CourseEnrollment afterChange hook guarded on the status transition into `completed` — the same guard `autoAwardCertification` uses. Per-lesson points (and the points-threshold badge cascade inside `awardPoints`) are unchanged.

  • 1173d00: Two fixes from the wabbit EDU Phase 5 prod dogfood (2026-07-18): - **lms-ui:** CurriculumSidebar now derives per-row effective access via `deriveAccessState` (enrollment/tier-aware) instead of disabling every `locked`-visibility row — enrolled members can navigate locked lessons from the rail, matching what the content pane already grants. `CurriculumTree` gains an optional `resolveLessonAccess` prop; without it the visibility-tier fallback (anon/landing behavior) is unchanged. - **lms:** the `course-completion` badge check moved out of `onLessonCompletion` (it fired per LESSON, awarding course-completion badges on a student's first completed lesson) into a new `awardCourseCompletionBadges` CourseEnrollment afterChange hook guarded on the status transition into `completed` — the same guard `autoAwardCertification` uses. Per-lesson points (and the points-threshold badge cascade inside `awardPoints`) are unchanged.
  • Updated dependencies [1173d00] - @wabbit/tome-lms@0.10.1
v0.8.0minor

6bc419c: R4 ruling #5: `StudentProfileForm` gains a `tag-list` field type (built on `useTagList`) and an exported `studentProfileEditorSchema(availableArchetypes?)` reproducing `StudentProfileEditor`'s field set through the schema vocabulary; Editor is `@deprecated` with the exact migration recipe in its tag. One honest gap, not smoothed over: Editor's free-form `customFields` key-value rows have no schema equivalent — a `key-value-list` field type gets built when a second consumer needs one (trigger named in both docblocks); consumers relying on customFields keep using Editor until then.

  • 6bc419c: R4 ruling #5: `StudentProfileForm` gains a `tag-list` field type (built on `useTagList`) and an exported `studentProfileEditorSchema(availableArchetypes?)` reproducing `StudentProfileEditor`'s field set through the schema vocabulary; Editor is `@deprecated` with the exact migration recipe in its tag. One honest gap, not smoothed over: Editor's free-form `customFields` key-value rows have no schema equivalent — a `key-value-list` field type gets built when a second consumer needs one (trigger named in both docblocks); consumers relying on customFields keep using Editor until then.
  • 36e537a: Every package now declares an explicit `sideEffects` field (38 added; motion/engine/forms already correct). Registration-bearing modules (render files' `registerRenderer`, `blocks/*/index.ts` `defineBlock` self-registration, widget `register.ts` files, productHooks, permission self-registrations, print templates, chrome built-in variants) are listed so bundlers can tree-shake everything else WITHOUT dropping import-time registrations — previously the field was unset, which blocked cross-module tree-shaking through the barrels entirely. Never blanket `false` on a package with registration or CSS.
  • 36e537a: Small verified fixes: agency-essentials `Contact` gains its missing `'use client'` (it calls the rich-text adapter hook; direct RSC import crashed). chrome `NavGuard` now dev-warns when its capability gate fails to load while a `requiredCapability` is set (the fail-open contract itself is unchanged and now documented). blocks-core `BLOCK_CATALOG.ts` corrupted entries corrected from real block meta (content-two-column, content-with-corner-notch, signal-ship-card names/descriptions; gallery variants filled) + drift-risk header. Stale docstrings fixed (chrome `HeaderLogo`, blocks-gallery registry header, lms-ui payload JSDoc import path). blocks meta-package backcompat suite now asserts the RENDER registry resolves renderers (previously only descriptor registration was tested — a dropped render import shipped silently).
  • a93f478: Re-render and cleanup fixes: chrome's HeaderClient dead theme state + unreachable effect deleted; Navbar6/7 body-scroll-lock now saves and restores the pre-existing overflow value (LearnerSidebar pattern) instead of clobbering to ''; Navbar7's scroll listener is rAF-throttled. marketing-starter's Testimonial derives the clamped slide index during render instead of an effect. forms' `FieldRenderer` is wrapped in `React.memo` (call-site props verified stable), cutting whole-step re-render work per keystroke in multi-field forms. lms-ui's `useLearnerPrefs` gains optional `initialPrefs` server-seeding (non-breaking) + in-flight dedup with TTL for the unseeded path.
  • aef2725: DRY adoption sweep (the audit's "adoption, not extraction" rule): crm/deals capability presets delegate to core's `sessionHasCapabilityOrLegacyAdmin`; new core `buildOwnershipWhere`/`ownershipOrBypass` (via `./access`) adopted by core's vendorScoped, catalog's vendor-scoping, and org's ownOrScoped (public APIs unchanged); `slugField()` adopted at 7 sites where semantics matched exactly (core lms collections + createMemberCollection — replacing a third independent slugify), with ~25 sites honestly skipped for named semantic divergences (auto-regenerate-on-clear vs allow-empty, collection-level hook pattern) now listed as core-enhancement candidates; new `formatDisplayDate` in blocks-core utilities (UTC-pinned, hydration-safe) adopted at 5 verified-identical sites; lms-ui consolidates its two certificate date formatters locally; `useMediaQuery`/`useIsMobile` published from tome-ui and adopted by AppShell + admin's SidebarProvider; gamification's `awardPoints` now uses the authoritative `getPointsBalance` (fixes a divergent 1000-row scan cap vs the correct 10000).
  • Updated dependencies [6bc419c]
  • Updated dependencies [36e537a]
  • Updated dependencies [36e537a]
  • Updated dependencies [a93f478]
  • Updated dependencies [aef2725]
  • Updated dependencies [aef2725] - @wabbit/tome-lms@0.10.0 - @wabbit/tome-ui@0.9.9
v0.7.11patch

@wabbit/tome-lms@0.9.3

  • @wabbit/tome-lms@0.9.3
v0.7.10patch

Updated dependencies [ec4b7bc] - @wabbit/tome-ui@0.9.8

  • Updated dependencies [ec4b7bc] - @wabbit/tome-ui@0.9.8
v0.7.9patch

Admin label polish + formatted commerce money columns (PR #208): explicit labels for CRM collections ("CRM Accounts…"), Admin/Learner UI Preferences, and better-auth generated collections ("Auth Accounts", "Two-Factor Credentials", OAuth/JWKS casing) via the plugin's customizeCollection hook; nav SYSTEM_LABEL_OVERRIDES map (payload-kv → "Payload KV") applied at resolver + pinned-section label sites; Orders.total / Payments.amount / Prices.amount virtual afterRead fields format integer cents against the row currency ("4900" → "$49.00") in list views with no client components (zero generate:importmap coupling). - @wabbit/tome-lms@0.9.2

  • Admin label polish + formatted commerce money columns (PR #208): explicit labels for CRM collections ("CRM Accounts…"), Admin/Learner UI Preferences, and better-auth generated collections ("Auth Accounts", "Two-Factor Credentials", OAuth/JWKS casing) via the plugin's customizeCollection hook; nav SYSTEM_LABEL_OVERRIDES map (payload-kv → "Payload KV") applied at resolver + pinned-section label sites; Orders.total / Payments.amount / Prices.amount virtual afterRead fields format integer cents against the row currency ("4900" → "$49.00") in list views with no client components (zero generate:importmap coupling). - @wabbit/tome-lms@0.9.2
v0.7.8patch

bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.

  • bed3f90: Docs-manifest emitter pipeline (W3 ship-readiness). `@wabbit/tome-blocks-core` now ships a standalone Node ESM CLI at `scripts/emit-docs-manifests.mjs` that emits per-package documentation manifests (index.json, packages/<slug>.json, changelog.json) by reading what packages already carry — READMEs, the payload-free `<pkg>/meta` block-usage barrels, package.json exports maps, and CHANGELOG.md. It is the docs-pipeline sibling of the gallery source extractor and is consumed by host sites at prebuild: `node node_modules/@wabbit/tome-blocks-core/scripts/emit-docs-manifests.mjs --output-dir <dir> --scope <scope.json>`. To let the emitter import block metadata uniformly without dragging Payload config into a build script, the `./meta` payload-free subpath (BlockMetaEntry[]) is extended to the remaining offered blocks packs — agency-essentials, catalog-pack, lms-pack, org-pack, and signal-theme — mirroring the existing editorial-pack / marketing-starter / content-writer / extras barrels. Each block's `BlockMeta` was relocated verbatim into a payload-free sibling meta module and re-imported by its block config; no meta values changed. Every supported-core package additionally adds `CHANGELOG.md` to its published `files` array so the next publish cascade ships changelogs the emitter can read from installed tarballs at prebuild.
  • Updated dependencies [bed3f90]
  • Updated dependencies [850d51c] - @wabbit/tome-ui@0.9.7 - @wabbit/tome-lms@0.9.1
v0.7.7patch

Updated dependencies - @wabbit/tome-ui@0.9.6

  • Updated dependencies - @wabbit/tome-ui@0.9.6
v0.7.6patch

Updated dependencies - @wabbit/tome-ui@0.9.5

  • Updated dependencies - @wabbit/tome-ui@0.9.5
v0.7.5patch

Updated dependencies [03865f0] - @wabbit/tome-lms@0.9.0

  • Updated dependencies [03865f0] - @wabbit/tome-lms@0.9.0
v0.7.4patch

Updated dependencies [d2d0b0d] - @wabbit/tome-lms@0.8.0

  • Updated dependencies [d2d0b0d] - @wabbit/tome-lms@0.8.0
v0.7.3patch

Updated dependencies [d5d81ce] - @wabbit/tome-lms@0.7.0

  • Updated dependencies [d5d81ce] - @wabbit/tome-lms@0.7.0
v0.7.2patch

@wabbit/tome-lms@0.6.2

  • @wabbit/tome-lms@0.6.2
v0.7.1patch

Updated dependencies [84a047a] - @wabbit/tome-ui@0.9.3

  • Updated dependencies [84a047a] - @wabbit/tome-ui@0.9.3
v0.7.0minor

90d66fa: Tokenize longform + LMS typography to the platform `--tome-text-*` scale **tome-longform** — every hard-coded `font-size` rem/px literal across the 15 block CSS modules (Accordion, AnchorSection, Aside, AuthorAside, Callout, ChapterDivider, CrossLink, DataTable, ImageGrid, KeyFacts, SeriesNav, Spoiler, TabbedContent + DropCap's custom prop left as-is) now reads the platform type token with the original literal preserved as the fallback — e.g. `font-size: var(--tome-text-sm, 0.875rem)`. `em`-based sizes are left untouched (intentional relative sizing). A Tome-themed consumer (one that loads `@wabbit/tome-ui/tokens`) now gets longform type that tracks the platform scale; a consumer with no Tome tokens renders identically to before (fallback = the prior literal). **Non-breaking, but rendered sizes shift in Tome-themed consumers — re-verify longform visually after upgrading.** **SeriesNav** — `.partLink` (which sits on the TOP_BANNER `--block-accent-bg` surface) now leads its color with `--block-accent-text` before falling back to `--tome-color-muted-foreground`. When `--block-accent-text` is unset this is identical to the prior rule (non-regressive); when a consumer sets an accent background they now have a paired on-accent text hook, closing the cream-on-pastel contrast gap by contract rather than by a hard-coded value (mirrors the existing `.seriesLabel` / `.sidebarLabel` pairing). **tome-lms-ui** — `tokens.css` now bridges the `--lms-font-size-*` scale to `--tome-text-*` (mirroring the existing color-token bridge), so LMS _content_ typography tracks the platform scale instead of being a parallel fixed scale. The previously-undefined `xs` / `base` / `xl` / `2xl` names (used across components with inline fallbacks) are now defined. Fallbacks equal the dominant observed intended size, so a non-Tome consumer is non-breaking. `CertificateTemplate` literals are tokenized onto this scale. Dense sidebar/nav chrome keeps its sub-14px px literals — that is intentional UI density, not reading prose, and was deliberately left untokenized.

  • 90d66fa: Tokenize longform + LMS typography to the platform `--tome-text-*` scale **tome-longform** — every hard-coded `font-size` rem/px literal across the 15 block CSS modules (Accordion, AnchorSection, Aside, AuthorAside, Callout, ChapterDivider, CrossLink, DataTable, ImageGrid, KeyFacts, SeriesNav, Spoiler, TabbedContent + DropCap's custom prop left as-is) now reads the platform type token with the original literal preserved as the fallback — e.g. `font-size: var(--tome-text-sm, 0.875rem)`. `em`-based sizes are left untouched (intentional relative sizing). A Tome-themed consumer (one that loads `@wabbit/tome-ui/tokens`) now gets longform type that tracks the platform scale; a consumer with no Tome tokens renders identically to before (fallback = the prior literal). **Non-breaking, but rendered sizes shift in Tome-themed consumers — re-verify longform visually after upgrading.** **SeriesNav** — `.partLink` (which sits on the TOP_BANNER `--block-accent-bg` surface) now leads its color with `--block-accent-text` before falling back to `--tome-color-muted-foreground`. When `--block-accent-text` is unset this is identical to the prior rule (non-regressive); when a consumer sets an accent background they now have a paired on-accent text hook, closing the cream-on-pastel contrast gap by contract rather than by a hard-coded value (mirrors the existing `.seriesLabel` / `.sidebarLabel` pairing). **tome-lms-ui** — `tokens.css` now bridges the `--lms-font-size-*` scale to `--tome-text-*` (mirroring the existing color-token bridge), so LMS _content_ typography tracks the platform scale instead of being a parallel fixed scale. The previously-undefined `xs` / `base` / `xl` / `2xl` names (used across components with inline fallbacks) are now defined. Fallbacks equal the dominant observed intended size, so a non-Tome consumer is non-breaking. `CertificateTemplate` literals are tokenized onto this scale. Dense sidebar/nav chrome keeps its sub-14px px literals — that is intentional UI density, not reading prose, and was deliberately left untokenized.
v0.6.1patch

Updated dependencies [8947ff1] - @wabbit/tome-ui@0.9.2 - @wabbit/tome-lms@0.6.1

  • Updated dependencies [8947ff1] - @wabbit/tome-ui@0.9.2 - @wabbit/tome-lms@0.6.1
v0.6.0minor

56fc8d5: CourseShell now derives **real** lesson completion + access state. Two changes, both surfacing through `<CourseShell>`: **1. Completion state (`completedLessonIds`).** `MarkCompleteButton`'s "Completed" pill and `CurriculumSidebar`'s per-lesson completion markers never flipped — both derived completion from `enrollment.completedLessons`, a field that **does not exist** on the `CourseEnrollment` schema (the canonical source is the global `lesson-completions` collection). Fix: - New optional `completedLessonIds?: string[]` prop on `<CourseShell>` (`CourseShellRootProps`) and a required `completedLessonIds: ReadonlySet<string>` on `CourseShellContextValue`. Resolve it server-side via `@wabbit/tome-lms#getCompletedLessonIds` and pass it in. - `CourseShellRoot` merges the server-authoritative set with an in-session **optimistic** overlay, so the pill/marker flips immediately on mark-complete and reconciles (prunes) once the server revalidation re-hydrates the prop. - `MarkCompleteButton` + `CurriculumSidebar` now read the context set; the dead `extractCompletedLessonIds` helper (read the non-existent field) is removed. - Back-compat: omit the prop and completion markers simply never flip — the prior behavior, no errors. **2. Access state (`canAccessCurrentLesson`) — BEHAVIOR CHANGE.** `CourseShellRoot` shipped a Wave-2a stub hardcoding `canAccessCurrentLesson = true`, so `VisibilityGate`/`PrerequisiteGate` never actually gated. It now calls the existing pure `deriveAccessState()` (visibility + enrollment + tier + expiry precedence, RSC-safe, mirrors `@wabbit/tome-lms/guards#canAccessLesson`). Prerequisite gating stays disabled (empty list) until a resolver feeds the shell — additive, tracked separately. Consumers that relied on the stub's "everything accessible" behavior will now see locked/preview lessons actually gate. Verify any server-side paywall (e.g. body-stripping) still composes correctly with the now-live client gate.

  • 56fc8d5: CourseShell now derives **real** lesson completion + access state. Two changes, both surfacing through `<CourseShell>`: **1. Completion state (`completedLessonIds`).** `MarkCompleteButton`'s "Completed" pill and `CurriculumSidebar`'s per-lesson completion markers never flipped — both derived completion from `enrollment.completedLessons`, a field that **does not exist** on the `CourseEnrollment` schema (the canonical source is the global `lesson-completions` collection). Fix: - New optional `completedLessonIds?: string[]` prop on `<CourseShell>` (`CourseShellRootProps`) and a required `completedLessonIds: ReadonlySet<string>` on `CourseShellContextValue`. Resolve it server-side via `@wabbit/tome-lms#getCompletedLessonIds` and pass it in. - `CourseShellRoot` merges the server-authoritative set with an in-session **optimistic** overlay, so the pill/marker flips immediately on mark-complete and reconciles (prunes) once the server revalidation re-hydrates the prop. - `MarkCompleteButton` + `CurriculumSidebar` now read the context set; the dead `extractCompletedLessonIds` helper (read the non-existent field) is removed. - Back-compat: omit the prop and completion markers simply never flip — the prior behavior, no errors. **2. Access state (`canAccessCurrentLesson`) — BEHAVIOR CHANGE.** `CourseShellRoot` shipped a Wave-2a stub hardcoding `canAccessCurrentLesson = true`, so `VisibilityGate`/`PrerequisiteGate` never actually gated. It now calls the existing pure `deriveAccessState()` (visibility + enrollment + tier + expiry precedence, RSC-safe, mirrors `@wabbit/tome-lms/guards#canAccessLesson`). Prerequisite gating stays disabled (empty list) until a resolver feeds the shell — additive, tracked separately. Consumers that relied on the stub's "everything accessible" behavior will now see locked/preview lessons actually gate. Verify any server-side paywall (e.g. body-stripping) still composes correctly with the now-live client gate.
  • Updated dependencies [56fc8d5] - @wabbit/tome-lms@0.6.0
v0.4.8patch

Updated dependencies [0b2a1d6] - @wabbit/tome-ui@0.8.3

  • Updated dependencies [0b2a1d6] - @wabbit/tome-ui@0.8.3
v0.4.7patch

Updated dependencies [4225e9f] - @wabbit/tome-ui@0.8.2

  • Updated dependencies [4225e9f] - @wabbit/tome-ui@0.8.2
v0.4.6patch

Updated dependencies [1d90b24] - @wabbit/tome-ui@0.6.1

  • Updated dependencies [1d90b24] - @wabbit/tome-ui@0.6.1
v0.3.0minor

**lms-ui — CSS Modules → plain CSS rename (minor, consumer-visible).** `packages/lms-ui/src/components/*/index.module.css` renamed to `index.css` across 30+ components; exports map in `package.json` updated to match (`./components/*` now point to `index.css` under `dist/`). Consumers switch from `import styles from './index.module.css'` to side-effect `import './index.css'`. The previous layout was rejected by Next.js because the CSS used attribute-based global selectors (`[data-layout="three-column"]`), which CSS Modules flag as non-pure. This rename unblocks cross-package CSS `@import` from consumer barrels (e.g. ProCut's `src/styles/tome-lms-ui.css`). Also bumps the build script to add `NODE_OPTIONS=--max-old-space-size=8192` (DTS was OOM'ing against the peer type graph) and adds `cross-env` as a devDep. **admin — Payload 3.x entrypoint alignment + Turbopack cmdk fix (patch).** `DefaultCommandRegistrar` was split: the Payload-aware variant lives in a new `PayloadDefaultCommandRegistrar.tsx` with a static ESM import of `@payloadcms/ui`. Root cause: Next 15 Turbopack's CJS-of-ESM interop returned `useConfig` as not-a-function under the prior `require('@payloadcms/ui')` lazy-load path. Edit/List/Nav entrypoints now render `<DefaultEditView>` and siblings with `DocumentViewClientProps`, matching Payload 3.x's full-replacement slot contract (the prior HOC shape assumed `children` that Payload never delivered). No public API surface changes.

  • **lms-ui — CSS Modules → plain CSS rename (minor, consumer-visible).** `packages/lms-ui/src/components/*/index.module.css` renamed to `index.css` across 30+ components; exports map in `package.json` updated to match (`./components/*` now point to `index.css` under `dist/`). Consumers switch from `import styles from './index.module.css'` to side-effect `import './index.css'`. The previous layout was rejected by Next.js because the CSS used attribute-based global selectors (`[data-layout="three-column"]`), which CSS Modules flag as non-pure. This rename unblocks cross-package CSS `@import` from consumer barrels (e.g. ProCut's `src/styles/tome-lms-ui.css`). Also bumps the build script to add `NODE_OPTIONS=--max-old-space-size=8192` (DTS was OOM'ing against the peer type graph) and adds `cross-env` as a devDep. **admin — Payload 3.x entrypoint alignment + Turbopack cmdk fix (patch).** `DefaultCommandRegistrar` was split: the Payload-aware variant lives in a new `PayloadDefaultCommandRegistrar.tsx` with a static ESM import of `@payloadcms/ui`. Root cause: Next 15 Turbopack's CJS-of-ESM interop returned `useConfig` as not-a-function under the prior `require('@payloadcms/ui')` lazy-load path. Edit/List/Nav entrypoints now render `<DefaultEditView>` and siblings with `DocumentViewClientProps`, matching Payload 3.x's full-replacement slot contract (the prior HOC shape assumed `children` that Payload never delivered). No public API surface changes.
v0.2.0minor

Initial publish to npm.wabbit.com — first registry release for the 5 non-block-pack packages. Companion to the existing sprint-3-blocks-split changeset (which handles the 11 linked block packages). Together these two changesets bring all 8 publish-pipeline-Phase-2-remediated packages to a coherent first-release cohort: - `@wabbit/tome-core` 0.1.0 → 0.2.0 - `@wabbit/tome-ui` 0.2.0 → 0.3.0 - `@wabbit/tome-motion` 0.1.0 → 0.2.0 - `@wabbit/tome-lms` 0.1.0 → 0.2.0 - `@wabbit/tome-lms-ui` 0.1.0 → 0.2.0 - `@wabbit/tome-blocks-core` 0.1.0 → 0.2.0 (via sprint-3) - `@wabbit/tome-blocks-extras` 0.1.0 → 0.2.0 (via sprint-3) - `@wabbit/tome-blocks-marketing-starter` 0.1.0 → 0.2.0 (via sprint-3) All 8 packages ship with metadata, dist/ output, exports map verified by P6 scratch-consumer smoke (35/35 resolutions), 'use client' + 'server-only' directives preserved through tsup bundle:false. Verdaccio v0 live since 2026-04-18 at npm.wabbit.com.

  • Initial publish to npm.wabbit.com — first registry release for the 5 non-block-pack packages. Companion to the existing sprint-3-blocks-split changeset (which handles the 11 linked block packages). Together these two changesets bring all 8 publish-pipeline-Phase-2-remediated packages to a coherent first-release cohort: - `@wabbit/tome-core` 0.1.0 → 0.2.0 - `@wabbit/tome-ui` 0.2.0 → 0.3.0 - `@wabbit/tome-motion` 0.1.0 → 0.2.0 - `@wabbit/tome-lms` 0.1.0 → 0.2.0 - `@wabbit/tome-lms-ui` 0.1.0 → 0.2.0 - `@wabbit/tome-blocks-core` 0.1.0 → 0.2.0 (via sprint-3) - `@wabbit/tome-blocks-extras` 0.1.0 → 0.2.0 (via sprint-3) - `@wabbit/tome-blocks-marketing-starter` 0.1.0 → 0.2.0 (via sprint-3) All 8 packages ship with metadata, dist/ output, exports map verified by P6 scratch-consumer smoke (35/35 resolutions), 'use client' + 'server-only' directives preserved through tsup bundle:false. Verdaccio v0 live since 2026-04-18 at npm.wabbit.com.
  • Updated dependencies - @wabbit/tome-ui@0.3.0 - @wabbit/tome-lms@0.2.0