Repository navigation
Sync with upstream ClassBuild (Codex redesign) + re-home Canvas features - #1
Merged
Merged
Conversation
Standalone 1920×1080 marketing/pitch page combining the Codex parchment palette, Newsreader + JetBrains Mono fonts, the colophon SVG mark, the five-stage indicator strip, the seven-artifact aside, and the six worked-example course thumbnails — designed for a single hero screenshot. Asset paths are absolute so the page works both via the Vite dev server and after Vercel deploy. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pitch-scoped copy of the six course thumbnails so the pitch page can ship the newer, smaller renders without disturbing /courses/ that the rest of the app still uses. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Refresh docs for the redesign: new Codex hero image; six chapter themes (Press/Notebook/Almanac/Storybook/Studio/Terminal) replacing the old four; OpenAI gpt-image-2 + ElevenLabs providers (Gemini removed); the seven per-chapter deliverables; five-stage pipeline; and the weeklyChallenge prompt builder in the library table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the app onto the Codex parchment design system and six per-course chapter themes (Press/Notebook/Almanac/Storybook/Studio/Terminal). Replace Gemini with OpenAI gpt-image-2 (slide + chapter images) and ElevenLabs (narration). Rebuild the Setup → Syllabus → Research → Build → Export flow and the published course viewer, switch slides to image-first decks, and add the themed quiz/challenge renderer in tools/. Refresh the favicon/icon set (SVG + web manifest + apple-touch + OG card) and prune stale public assets (Gemini-era voice samples, old hero and preview images). Remove the legacy setup/syllabus components and the Gemini services. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The generated favicon PNGs and favicon.svg used the source mark's portrait aspect (e.g. 16x19, 32x38) and a 100x120 SVG viewBox, so browsers squished the tab icon. Regenerate the square favicons (16/32/48/96) from the 512px square-colophon render, point favicon.svg at the square colophon variant, and add a multi-resolution favicon.ico (+ <link ... sizes="any">) for default and legacy requests. Verified all icon paths resolve in-browser with no 404s. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bump MODELS.opus to claude-opus-4-8. Crossing from 4.6 triggers the 4.7
migration, which rejects manual extended thinking (budget_tokens) on Opus —
a raw model-ID swap would 400 every Opus call (syllabus, chapters).
Two non-obvious 4.7+ defaults shape the fix: adaptive thinking is OFF unless
explicitly enabled (effort alone does NOT make Opus think), and the thinking
summary text is omitted by default. So the Opus branch now sets
thinking:{type:'adaptive', display:'summarized'} alongside
output_config.effort (mapping the existing max/high/medium/low budgets onto
effort levels) — this preserves deep reasoning on syllabus/chapters and keeps
the streamed thinking our UI shows via onThinking. Sonnet 4.6 / Haiku 4.5
keep manual budget_tokens, which they still accept. No sampling params are
set, so that 4.7 breaking change doesn't apply. README + CLAUDE.md version
lines updated.
(display isn't typed in SDK 0.74.0 yet; the field still serializes, so cast.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Point the footer "The maker" link at tangenlab.com and "Code with Claude · Tokyo" at claude.com/code-with-claude/tokyo (were placeholder URLs), and repoint the README hackathon credit from the old Google signup form to the Code with Claude · Tokyo event page. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On Opus 4.8 the effort levels were recalibrated and 'max' overthinks — the syllabus stalled ~7 min before the first token. Drop every Opus 'max' call to 'high' (Anthropic's recommended floor for intelligence-sensitive work, and what the already-fast chapter build uses): fresh syllabus (SPA + CLI), practice quiz, and in-class quiz. Also surface Opus's reasoning on the syllabus page. display:'summarized' (added with the 4.8 upgrade) makes Opus stream its planning, but SyllabusPage discarded it — it had onText/onError but no onThinking. Now the pre-output wait shows a live "Reasoning through the course architecture · 0:14" trace with an elapsed clock instead of a frozen "Designing the course architecture…" label. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two bugs surfaced while generating materials on Opus 4.8:
1. Activities crash ("undefined is not an object — 'variations.length'"): the
activity-detail model occasionally returns valid JSON that omits an array
field (e.g. variations), and the `as ActivityDetail` cast doesn't validate
it — so the detail render/export/copy crashed the whole Build phase, and the
malformed object was persisted, so reselecting Activities re-crashed. Add
normalizeActivityDetail() and apply it at parse (useChapterMaterials), render
+ copy (ActivitiesTab), and export (BuildPage). Pre-existing fragility, not a
4.8 regression — but it now degrades gracefully and recovers stuck courses.
2. Weekly Challenge silently reverting: under adaptive thinking, thinking tokens
count toward max_tokens. The old formula (budgetTokens + maxTokens) left too
little output room at 'high', so a deep think truncated the JSON →
parseJson threw → setTabError → button reverted with no output. Give Opus
generous headroom above the intended output (maxTokens + budgetTokens*2,
capped at 128k); keep the Sonnet/Haiku reservation where budget_tokens must
fit inside max_tokens.
Not a code bug: slow slide generation is gpt-image-2 rendering 4K images, not
Opus thinking.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Audio "synthesis failed" box and the batch-confirm modal used bg-cb-status-warning (solid #b07a1f gilt) as the container AND text-cb-status-warning for the text — identical colour, so the error message and modal heading rendered invisibly: an "ugly, meaningless" mustard box with only a button. Switch the container to the tinted bg-cb-status-warning-bg, matching the app convention (saturated text on a light tint), so the actual message is readable. The audio failure itself happens at ElevenLabs (invalid key / voice / quota) and is captured via friendlyError into audioError — it was always there, just painted invisible. Now legible so it can be acted on. Not migration-related (transcript is Sonnet, narration is ElevenLabs — neither changed). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Investigating the "SCORM export failed." banner (which appeared on the Build page): SCORM packages the weekly challenge, and the challenge had been failing to generate (the truncation bug fixed in f1c2791). Three rough edges hardened: - ExportPage SCORM handler threw a cryptic "SCORM export failed." when the challenge data was present-but-incomplete (no questions), and silently did nothing when absent. It now validates questions up front and gives an actionable message ("generate the mastery challenge for this chapter first"). - buildWeeklyChallengeHtml threw on partial data (challengeData.metadata and sanitised.questions were assumed present). Guard both so a sparse challenge yields a (possibly empty) package instead of a cryptic throw. - The uiStore error banner persisted across navigation, so a SCORM failure on the Export page haunted the Build page (the screenshot). Clear the transient error on route change in AppShell; errors raised while you stay on a page are unaffected. Root cause is upstream (challenge generation, already fixed) — these make the failure legible and self-contained. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
buildWeeklyChallengeHtml obfuscated answers with
Buffer.from(out, 'binary').toString('base64'). Buffer is a Node global with no
browser polyfill, so in the SPA Build page the template threw "Buffer is not
defined" the instant it encoded the first answer — and the throw was swallowed
(catch { /* template failed */ }), so the challenge parsed and saved its data
but rendered no HTML and showed no error: it just reverted to the "Generate
mastery challenge" button. The Node CLI has Buffer, which is why the example
courses have working challenges while the in-browser challenge never did. The
same template backs the SCORM export, so in-browser SCORM failed for the same
reason.
Switch to btoa (browser global; Node >= 16), masking each XOR byte with & 0xFF
so the output is byte-identical to Buffer's latin1 truncation (CLI output
unchanged) and btoa never sees a code point > 0xFF. The runtime decoder already
uses atob.
Also stop swallowing the template error in generateWeeklyChallenge — surface it
as a tab error so a render failure can never be silent again.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ction bug) Empirically verified (curl, 2026-05) that ElevenLabs' TTS endpoint returns permissive CORS (access-control-allow-origin: *) and a real HTTP 401 for a bad key — so direct browser BYOK calls are allowed and CORS is NOT the blocker. A *rejected* fetch (the catch path, vs an HTTP status response) therefore means the request never reached ElevenLabs: almost always a local block of api.elevenlabs.io (a Safari content/privacy blocker, "Prevent Cross-Site Tracking", a browser extension, a VPN, or iCloud Private Relay). Anthropic and OpenAI keep working because they are different hosts. - Replace the misleading "Network error. Check your connection and retry." with guidance that names the real, user-fixable causes. - Fail fast on a rejected fetch instead of retrying 3x (a host block isn't transient). The 429/5xx retry on real HTTP responses is unchanged. - Surface the actionable message verbatim for a status-less ElevenLabsTtsError; keep friendlyError's mapped copy (key rejected / rate limit / overloaded) for errors that carry an HTTP status, so a genuine 401/422 is no longer masked as a generic "network" error. No backend/proxy added — CORS is open, so a proxy would only break BYOK. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- generateAllOutputs never queued the weekly challenge, so "Generate all materials for this chapter" built everything EXCEPT the Challenge (you had to open the tab and generate it by hand). Queue it alongside the others. - The challenge preview's "Begin Challenge" did nothing in-app yet worked in the downloaded file. The challenge's SCORM API discovery walks up to window.parent; in the sandboxed preview iframe (no allow-same-origin) reading a property on the cross-origin parent throws a SecurityError, which aborted init before the start button was wired. Wrap discovery in try/catch so a missing/cross-origin LMS just means "run standalone" (also hardens real LMS embeds). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Publish · standalone HTML" built ONE self-contained .html with 4 student tabs using a different, older viewer — so it lacked tabs and didn't match the polished example courses at courses.classbuild.ai. Replace it with a multi-file course SITE (.zip) that uses the SAME Codex viewer the examples use: index.html index/sidebar/tabs/welcome (Codex chrome) chapters/NN.html themed reading quizzes/NN.html gamified practice quiz challenges/NN.html weekly mastery challenge slides/chNN/slide-MM.jpg rendered slide images audio/NN.mp3 audiobook narration All 7 tabs (Reading · Practice · Challenge · Discussion · Activities · Audio · Slides) appear when their data exists; discussion + activities render natively in the viewer. Keeping a multi-file layout means slide images and audio stay as files, so the site stays light instead of ballooning a single HTML to 100MB+. - Port the example-course viewer to src/templates/codexCourseSiteTemplate.ts (verbatim, minus the gallery "all courses" back-link, which a standalone published course has no use for). - Rewrite publishExporter to assemble the zip: decode slide data URIs to .jpg, fetch audio blobs to .mp3, write themed chapter/quiz/challenge HTML, build the index via the Codex viewer. - Export button: "Publish · course site (.zip)"; downloads <course>-site.zip. - Remove the now-orphaned courseViewerTemplate.ts. Verified the viewer output (tabs, file paths, theme tag, single-course auto-open) with a smoke test; build is green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oads ROOT CAUSE (confirmed via investigation): the persisted course snapshot included the rendered 4K slide images (~13/chapter, ~1.3MB each as base64). Zustand persist re-serializes + rewrites the ENTIRE course to IndexedDB on every state change with no debounce, and slides are written one-at-a-time (a ~13x write storm). Once a deck rendered, the multi-MB writes stalled without resolving OR rejecting — leaving a deceptively green "Saved · 12m ago" while nothing actually saved. Content generated after that point (audio, weekly challenge) never reached disk, so a reload restored the stale pre-stall snapshot and they vanished; the transcript survived only because it was written before the stall. Audio was doubly doomed: it was kept solely as a blob: URL, which is stripped on save and dies on reload regardless. FIX: - Keep the persisted snapshot lean: strip slide imageDataUri at the persist boundary (partialize). imagePrompt/title stay, so the deck re-renders next session. In-memory data is unchanged, so live preview/export/publish are unaffected in-session. Chapter reading figures stay inlined (smaller, written once, not cleanly re-renderable) so readings survive a reload intact. - Make audio durable across reload: persist a size-capped (~6MB) base64 data URI (audioDataUri) alongside the in-session blob URL; rehydrate and all export / publish / download read sites fall back to it. Long chapters re-narrate from the surviving transcript instead of bloating the snapshot. - Surface a hung write: idbStorage.setItem now races a 15s timeout, so an oversized/never-settling put becomes a visible persistError instead of a silently stale green "Saved". - Audio download was mislabeled .wav — it is MP3; fixed the filename + button. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The audio-transcript prompt instructed inserting "[short pause]" pacing cues —
but ElevenLabs reads bracketed stage directions aloud verbatim ("short pause")
instead of pausing. Rewrite the pacing instruction to rely on punctuation and
sentence/paragraph structure and explicitly forbid bracketed/parenthetical
directions. Also sanitize at the TTS layer (stripStageDirections) so transcripts
already generated narrate cleanly without regenerating — strips any
bracketed/parenthetical group containing "pause"/"beat" and tidies whitespace,
while leaving real bracketed content like [H2O] or (correct) intact.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…otices - AbortController plumbed through streamMessage/sendMessage/streamWithRetry and ElevenLabs TTS; module-level abort registry so Stop works after navigation. Stop buttons on every drafting status line (all 8 tabs) and on the batch header; aborts are silent cancels, never error banners. - Unified the two batch flows into one runBatch(mode) engine with fill-the-holes guards, so "Retry failed" re-runs only what's missing. Failures are collected per chapter/material and reported in an end-of-batch summary panel instead of console.error. - Batch header now shows "Class k of N · drafting … · ~M min left" from a rolling per-chapter average; confirm-dialog counts match the real queue. - Audio tab shows the transcript (word count + narrated minutes, editable, downloadable) before any ElevenLabs spend, and says so when a narration exceeds the persistable size cap instead of silently dropping it. - Slides tab explains that rendered images don't survive reloads and shows per-row thumbnails; per-tab error banners gained a retry action. - "Generate all materials" is a real button with remaining-count and a rough time hint; tab bar is a proper tablist with arrow-key switching. - A successful full-state IndexedDB write clears any stale persistError. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Download the whole course (sans binaries and keys) from Export as a portable project file; restore it from the landing page with a replace-confirmation when a course is already in progress. One bad IndexedDB write or a cleared browser profile no longer means losing the course. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each drafted chapter row gains a quiet edit affordance (input/textarea, Enter/Escape handling) backed by a new updateSyllabusChapter store action that preserves the curriculum map, plus a small-caps "interactives" line listing each planned widget with its rationale on hover. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AI-cited references that haven't been verified get a warning badge per source (and an aggregate count on the Build page dossier strip), with a footer reminding teachers to check them before teaching. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
aria-describedby/aria-invalid + role=alert on Input/Textarea errors, labelled Select hints, a real label + aria-valuetext on Slider, and a visible keyboard focus ring on Checkbox/Radio (the hidden native input's opacity:0 was suppressing its own outline). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Setup: when the Anthropic key is missing (or failed verification) an "Add Anthropic key →" button sits beside the disabled Begin CTA instead of a buried footer link; key validation now uses the free countTokens endpoint; the length slider notes ≈ one chapter per week. - "Begin · Syllabus" confirms before wiping an in-progress course, and Syllabus "Regenerate" confirms + clears built chapters/dossiers rather than silently orphaning them; both point at the project-file backup. - Header: "Save failed" is now a retry button (a no-op set re-runs the full persist write; success clears the banner), and the new-course dialog mentions the project file. - Reduced motion honoured app-wide (CSS media query + MotionConfig reducedMotion="user"); --cb-text-subtle nudged to clear WCAG AA on both parchment and vespers grounds. - Chapter sidebar speaks one voice: "— complete / — 3 of 8 drafted / — awaiting"; header chip says "practice quiz" instead of "quiz". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The landing markup was inline-styled for the 1100px reading column and broke badly on phones (674px of content in a 375px viewport — hero squeezed against an off-screen panel, three-up grids crushed, four-column footer unreadable). Now: - Hero headline scales clamp(46px → 96px); panel stacks below the CTA (capped width) under 1020px. - How-it-works, deliverables, CLI, and footer grids restack at 1020/860/ 720px; course cards hold two-up until 640px since they're designed narrow; principle rows put the description beneath the label on phones. - Section kickers no longer wrap mid-phrase; anchored scrolls glide (scroll-behavior: smooth, reduced-motion safe) and land below the fixed header via scroll-margin; card links get real tap targets; closing CTA centres on phones; header padding tightens at phone widths. Verified 375/768/1440 full-page via CDP — zero horizontal overflow at every width. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same changes as the classbuild-cli codexViewerTemplate (the two stay in lockstep): welcome screen gains Begin/Browse CTAs and fluid type, the sidebar gets a scrim + close-state toggle that no longer overlaps the brand, the tab bar becomes one swipeable row on phones, .main picks up min-width:0 against the flex min-content trap, plus theme-color/ description metas and reduced-motion support. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Viewer: welcome screen gains a "Begin Class 01" CTA and a Browse-classes button (phone visitors were stranded with only a bare hamburger), fluid clamp() type for long course titles, a tap-to-close scrim behind the slide-out sidebar with an open/close toggle that no longer overlaps the brand, a single swipeable tab row on phones, min-width:0 on .main (the nowrap tab bar otherwise widened it past the viewport — flex min-content trap), stacked activity meta rows, theme-color/description metas, and prefers-reduced-motion support. Gallery: theme-color/description metas, eager-load the first thumbnail, reduced-motion support, tightened phone spacing. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The machinery that builds courses.classbuild.ai was living only in this working copy: build-staging.ts (themed rebuild of all six bundles into tmp/staging-deploy + Codex gallery), build-image-pptx.ts (full-bleed image decks), the render/rewrite image scripts, publish.ts's themed-dir AssembleOptions + codex viewer path, and HANDOFF.md documenting the theme-conversion workflow. Committed so tonight's deploy is reproducible from git. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every stage past the landing page overflowed a 375px viewport by 140-160px. The two global culprits: the stage indicator's five cells couldn't shrink below their labels' min-content (~514px), and CodexModal ignored the viewport for its fixed width. Stage cells now use minmax(0,1fr) and compress to a centred label-only strip on phones; modals clamp to min(width, 100vw - 28px). Per-stage: page banners (Syllabus/Research/Build) stack their title over a wrapping action row instead of squeezing it beside three buttons — Research's header previously wrapped one word per line and stamped its buttons over the subtitle. Setup's five audience pills go two-up (labels were chopped mid-glyph: "Undergr", "Professi"), starter briefs stack, and the what-comes-next strip pairs up. Build's eight-tab bar becomes a single swipeable row scrolling inside its own bar (plus min-width hygiene), and the keyboard-shortcuts chip — meaningless on touch — is hidden at phone widths. Export's six theme cards go two-up with the preview chip wrapping below long names instead of clipping, and artifact rows give chapter titles the full width with pills beneath. Verified at 375px via CDP sweep: zero horizontal overflow on every stage, with an independent visual QA pass on the before/after captures. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stepping back to Setup while a syllabus was drafting stranded you: the stage strip showed Syllabus as unclickable "awaiting" (it's neither completed nor the current route), so the only forward affordance was "Begin · Syllabus" — which threatens to replace the course. The stage strip now treats the store's currentStage as the journey's high-water mark: any reached stage stays clickable, and the stage the flow is actually at reads "↵ return" in the accent colour when viewed from another page. Setup additionally gets a plain "← Back to <stage>" button beside the primary CTA, which relabels to "Begin again · Syllabus →" when a course exists. The begin-confirm now also gates on the journey (not just settled content), so a mid-draft Begin can't silently race a second generation over the running one. Verified headlessly: return via strip and via button land on /syllabus with no modal and no reset; Begin again raises the replace-confirm. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings in 31 upstream commits (May 28 - Jun 10): the Codex design system redesign, Opus 4.8 adaptive thinking, OpenAI gpt-image-2 + ElevenLabs (replacing Gemini), project backup/import, multi-file publish, SCORM and IndexedDB content-loss fixes, stoppable batch generation, a11y/mobile polish. Fork features re-homed onto the new Codex UI: - Setup: Canvas template picker + outline upload; provider switch (Claude / Ollama Cloud), Ollama key + model, research backend (Claude / Tavily / Wikipedia) + Tavily key, and Canvas-focused vs Everything build outputs, all in the API keys modal. Begin is gated on the active provider's key. - Research: runResearch backend dispatch + the "backend not configured" gate with Skip to Build; upstream's enrichDossier verification kept. - Syllabus: Canvas template title editor. - Build: new Canvas Module tab (single + "generate all", cancellable), advanced-mode tab gating, keyboard shortcuts follow visible tabs. - Export: "Export for Canvas (.imscc)" action (template-aware). - Landing: demo-course loader, Canvas templates link, fork credit. - LLM layer (services/llm): Opus 4.8 adaptive thinking + AbortSignal ported into the Anthropic and Ollama backends; no retry on user cancel. Fixes found along the way: - Ollama routing forwarded Claude model ids (MODELS.opus etc.) to Ollama. - CodexModal re-focused the dialog on every parent render, stealing focus from inputs while typing (effect depended on an inline onClose). - IMSCC export no longer silently spends gpt-image-2 credits: slides.pptx is only bundled when every slide is already rendered. - Lint errors inherited from upstream (dead wavToMp3, setState-in-effect). Tests: rewrote Setup/Research/Syllabus/Build/Export page tests and the pptx exporter tests for the new UI/exporter; removed tests for deleted modules (gemini-tts, ApiKeyPanel). tsc, eslint (0 errors), vitest 265/265, build OK. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Welcome to Codecov 🎉Once you merge this PR into your default branch, you're all set! Codecov will compare coverage reports and display results in all future pull requests. ℹ️ You can also turn on project coverage checks and project coverage reporting on Pull Request comment Thanks for integrating Codecov - We've got you covered ☂️ |
Override the Codex emphasis tokens (parchment + vespers modes), their Tailwind aliases, and the legacy violet-* tokens with the Canvas palette the fork used before the upstream redesign. Light mode uses #c72a26 for text/CTA contrast on parchment; dark mode uses #ee6c4d. Exported course themes are unchanged. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
In Canvas-focused mode the challenge/activities/audio/slides tabs are hidden, but "Draft all chapters", per-chapter "Generate all", and the retry path still generated (and billed for) them. They now only run when "Everything" is on. The batch confirm dialog and the sidebar's "N of M drafted" count reflect the active mode (4 materials vs 8). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Rendered slide images aren't persisted across reloads (upstream strips them to keep IndexedDB writes small), so after a reload slides.pptx silently dropped out of Canvas cartridges. Exporting now detects unrendered decks and asks: render the images and include the decks (spends gpt-image-2 credits), or export without them. Without an OpenAI key the dialog only offers the latter. Rendered images are cached into the store as they arrive and the single-render mutex is respected. Template-mode cartridges don't bundle decks so they skip the prompt. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
tools/validate-imscc.py checks archive integrity, XML well-formedness, manifest identifier/reference integrity, resource types, QTI shape and Canvas course_settings extras. Structural only; a real Canvas import is still the source of truth. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Importing the demo cartridge into a live Canvas showed it "imported with no issues" but contained only the syllabus and one discussion: no modules, pages or quizzes. Canvas's PackageIdentifier treats any package whose manifest has course_settings/canvas_export.txt as a native Canvas export, and the native converter only reads Canvas's own files (module_meta.xml, wiki_content/, non_cc_assessments/) and ignores plain manifest content. The marker was added earlier to get the syllabus recognised. Rework assembleImscc() to emit exactly what Canvas's own exporter writes (taken from a reference course exported by the running Canvas): - one published Module per chapter via module_meta.xml, plus a "Course resources" module for the curriculum matrix - reading as a native Canvas Page (wiki_content/, scripts/styles stripped), with the original interactive HTML attached as a downloadable file - every quiz as CC QTI + Canvas-native QTI (non_cc_assessments) + assessment_meta.xml; practice quiz is an ungraded practice_quiz, in-class quiz and weekly challenge are graded assignments in an "Assignments" group; all published, correct answers wired, feedback preserved - discussions as imsdt topics + topicMeta, published - slides/infographic/teaching DOCX as web_resources files + files_meta.xml - course_settings.xml, assignment_groups.xml, syllabus (overview + outline) - deterministic Canvas-style identifiers so re-exports match up Verified by importing into Canvas 2026-04-22 (prod) via the native importer: 3 modules / 13 items, 2 published Pages, 5 published quizzes with correct answers, 3 graded assignments, published discussion, files and syllabus. tools/validate-imscc.py gains native-format rules; it passes this cartridge and Canvas's own export and fails the old cartridge. Rewrote the exporter tests for the new layout (module/manifest consistency, both QTI flavours, graded vs practice, escaping, determinism). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Built an instructor-style template inside a live Canvas (verbatim "do not publish" + "Begin Here" modules, an example-pattern module, a placeholder pattern module), exported it with Canvas's own exporter, ran it through the parser + template exporter, and imported the result. Canvas reported no issues, but the import exposed four real bugs (a control import of the untouched template showed the loss was ours): - Verbatim modules were re-serialised from a small parsed subset, which dropped an external link's <url> (Canvas then discarded the item entirely), LTI link settings, and module prerequisites / completion requirements / sequential-progress flags (hard-coded away). Verbatim <module> blocks are now carried through byte-for-byte; only <position> is renumbered. - Verbatim organization items were linked via the module_meta identifierref, which left dangling manifest references for external links. They now keep the template's original manifest linkage. - Manifest resources were rebuilt without <dependency> links or intendeduse, so a verbatim quiz/discussion would lose its assessment_meta/topicMeta and the syllabus resource its tag. Both are parsed and re-emitted. - Files of the replaced pattern modules stayed in the archive. Canvas's native importer scans wiki_content/ directly, so the old "Module N Overview" and example notes were imported as stray duplicate pages. Dropped resources, and dependency resources nothing else needs, are now removed with their files. Parser: the example (few-shot) discussion was never extracted from real Canvas exports, because discussion/quiz resources there have no href attribute (file only in a <file> child). The resource map now falls back to it. Tests: regression tests for each (verified to fail against the old code). tools/validate-imscc.py now flags unclaimed wiki_content pages as errors in native mode and no longer false-positives on sub-headers / inline external links (both appear in Canvas's own exports). Verified by import into Canvas: all 5 Begin-Here items incl. the external link, no stray pages (12 vs 16), new Module 1-3 with notes + discussions, syllabus from outline fields. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
Merges
jtangen/classbuild@main(31 commits, May 28 – Jun 10) into the fork. Upstream shipped a full Codex design-system redesign that deleted the setup/syllabus component trees the fork's Canvas features were built on, so this is a merge plus a port, not just conflict resolution.Upstream brought in
Codex UI redesign · Opus 4.8 adaptive thinking · OpenAI gpt-image-2 + ElevenLabs (Gemini removed) · project backup/import · multi-file publish · SCORM, IndexedDB content-loss and Opus-truncation fixes · stoppable batch generation · a11y/mobile polish.
Fork features re-homed on the new UI
runResearchbackend dispatch + "backend not configured" gate / Skip to Build; upstreamenrichDossierkeptservices/llmAbortSignalported into both backends; no retry on user cancelBugs found and fixed along the way
MODELS.opus…) to Ollama. Claude ids are now ignored on that path.CodexModalstole focus on every keystroke (effect depended on an inlineonClose). Pasting worked, typing didn't. Regression test added.slides.pptxis now only bundled when every slide is already rendered.wavToMp3, setState-in-effect).Verification
tsc -bclean ·eslint0 errors (7 pre-existingexhaustive-depswarnings) ·vitest265/265 ·vite buildOK · Setup page + keys modal checked in a browser, no console errors.Tests: rewrote Setup/Research/Syllabus/Build/Export page tests and the PPTX exporter tests for the new UI; removed tests for deleted modules (
gemini-tts,ApiKeyPanel).Canvas export rewritten to the native format (ac264d6)
Importing the demo cartridge into a live Canvas (built on a Unraid VM for this) showed "imported, no issues" but only the syllabus and one discussion arrived — no modules, pages or quizzes. Cause: a manifest containing
course_settings/canvas_export.txtmakes Canvas use its native importer, which ignores plain Common Cartridge content.assembleImscc()now emits the exact layout Canvas's own exporter writes (copied from a reference course exported by the running Canvas):module_meta.xmlmodules,wiki_content/Pages, quizzes as CC + native QTI withassessment_meta.xml(practice = ungraded, in-class / challenge = graded in an "Assignments" group), published discussions,web_resources/files, syllabus.Verified by import: 3 modules / 13 items, 2 published Pages, 5 published quizzes with correct answers, 3 graded assignments, published discussion, files, syllabus.
tools/validate-imscc.pygained native-format rules (passes the new cartridge and Canvas's own export, fails the old one).Template-based export tested against Canvas and fixed (582ab4f)
Built an instructor-style template inside the live Canvas (verbatim "do not publish" + "Begin Here" modules, an example-pattern module, a placeholder pattern module), exported it with Canvas's own exporter, ran it through our parser + template exporter, and imported the result. It "imported with no issues", but compared with a control import of the untouched template it exposed four exporter bugs and one parser bug, all now fixed with regression tests (each verified to fail against the old code):
<dependency>links andintendedusewere dropped (a verbatim quiz/discussion would lose its metadata) → preserved.wiki_content/directly → now removed with their metadata resources.href).Verified by re-import: all 5 Begin-Here items incl. the link, 12 pages (was 16, no duplicates), Modules 1–3 with notes + published discussions, syllabus from the outline fields.
Needs a human eye
Branding/palette— done (9898fd4): Canvas red/orange restored on the Codex tokens, light + dark.Importing a produced— done against a self-hosted Canvas (see above). Still not exercised: generation against live LLMs..imsccinto real CanvasBatch generation ignoring mode— done (0f88e16): "Draft all", per-chapter "Generate all" and the sidebar count now follow Canvas-focused vs Everything.Slides missing from IMSCC after a reload— done (f77004e): Export for Canvas now asks whether to render the missing deck images (spends OpenAI credits) or export without decks.imscc-canvas-only) is stale/conflicting and should probably be closed or re-cut.🤖 Generated with Claude Code