Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ac52bc3
Add /pitch.html — Codex-themed hackathon one-pager
jtangen May 27, 2026
0f457ec
Pitch: point thumbnails at /pitch-courses/ with updated images
jtangen May 27, 2026
830bae8
Pitch: classbuild.app → classbuild.ai in footer
jtangen May 27, 2026
b49831e
README: match the redesigned ClassBuild + new hero
jtangen May 28, 2026
811c8b2
Redesign: Codex design system, chapter themes, OpenAI + ElevenLabs
jtangen May 28, 2026
973fc4b
Fix favicons: square tab icons, square SVG, add favicon.ico
jtangen May 28, 2026
f85bdfe
Upgrade Claude Opus 4.6 → 4.8 (adaptive thinking + effort)
jtangen May 28, 2026
a7b5b94
Fix outbound credit links
jtangen May 28, 2026
87ed41c
Speed up syllabus: Opus effort max → high, stream the planning trace
jtangen May 28, 2026
f1c2791
Harden Build phase: fix Activities crash + Opus truncation reverts
jtangen May 28, 2026
73e4a27
Fix invisible error text on gilt status boxes
jtangen May 28, 2026
de834c2
Fix SCORM export failure + stale cross-page error banner
jtangen May 28, 2026
a40fb61
Fix in-browser Weekly Challenge (and SCORM): btoa, not Node Buffer
jtangen May 28, 2026
1e73307
Audio: honest error for blocked ElevenLabs requests (not a CORS/conne…
jtangen May 28, 2026
06768b9
Build phase: include Challenge in "Generate all"; fix challenge preview
jtangen May 29, 2026
9309358
Publish: multi-file course site (.zip) matching the example courses
jtangen May 29, 2026
fb45ad4
Fix content loss: stop IndexedDB writes stalling on giant base64 payl…
jtangen May 29, 2026
857c851
Audio: stop narrating "[short pause]" stage directions
jtangen May 29, 2026
a74fcb0
Build UX: stoppable generation, batch summary + retry, honest asset n…
jtangen Jun 9, 2026
b885d6f
Project backup: export/import a .classbuild.json course file
jtangen Jun 9, 2026
92a2974
Syllabus: inline title/narrative editing + visible widget plan
jtangen Jun 9, 2026
c9a9e9b
Research: badge unverified sources
jtangen Jun 9, 2026
d4f0daf
A11y: wire up form semantics on the codex controls
jtangen Jun 9, 2026
e09a41d
Onboarding, destructive-action confirms, and a11y polish
jtangen Jun 9, 2026
4c53fce
Setup: keep helper + key + begin on one action row
jtangen Jun 9, 2026
ae028ef
Landing: full mobile reflow + fluid hero type
jtangen Jun 10, 2026
e607806
Publish viewer: mirror the courses-site mobile polish
jtangen Jun 10, 2026
d1b9504
Course viewer + gallery: mobile polish for courses.classbuild.ai
jtangen Jun 10, 2026
c49d7cc
Commit the example-courses staging pipeline
jtangen Jun 10, 2026
cc79971
Builder stages: full phone-width reflow
jtangen Jun 10, 2026
824c4e1
Fix the Setup navigation trap: always a way back to the course
jtangen Jun 10, 2026
1fdb84e
Merge upstream jtangen/classbuild main into the Canvas fork
daTechGuy Oct 3, 2026
9898fd4
Restore Canvas LMS red/orange accent on the Codex design system
daTechGuy Oct 3, 2026
0f88e16
Gate batch generation and progress counts by build-output mode
daTechGuy Oct 3, 2026
f77004e
IMSCC export: let the user render missing slide decks, with consent
daTechGuy Oct 3, 2026
ec75fb7
Add offline IMSCC validator for exported Canvas cartridges
daTechGuy Oct 3, 2026
ac264d6
Export Canvas cartridges in the native Canvas course-export format
daTechGuy Oct 3, 2026
582ab4f
Fix template export fidelity bugs found by importing into Canvas
daTechGuy Oct 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,14 @@ deploy-courses/
# Local template uploads
*.imscc
*.docx

# Design scratch + internal references (not for the public repo)
ClassBuild Redesign/
public/ClassBuild Themes.zip
public/gpt-image-2.md

# Stale Gemini-era voice samples — app fetches previews from the ElevenLabs API
public/voices/

# Pitch scratch — not part of the app
pitch/
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public/ # Static assets (hero, previews, course thum

```typescript
// src/services/claude/client.ts
opus: 'claude-opus-4-6'
opus: 'claude-opus-4-8'
sonnet: 'claude-sonnet-4-6'
haiku: 'claude-haiku-4-5-20251001'
```
Expand Down
143 changes: 143 additions & 0 deletions HANDOFF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Course Redo Handoff — Tea is done, Puppy in progress

## What we're doing

Converting the 6 ClassBuild example courses to the new chapter themes (Press / Notebook / Almanac / Storybook / Studio / Terminal). Each course's **chapter text stays verbatim** (audio narration already exists for it and must keep matching), but we re-theme the chapter HTML, render fresh figures via OpenAI, and add interactive widgets that match the new theme.

Image renders go through OpenAI gpt-image-2; everything else (text) is done by you (Claude) directly. The API key lives in `/Users/uqjtange/Documents/research/current_projects/classbuild-cli/.env.local` (gitignored via `*.local`). All scripts use `npx tsx --env-file=.env.local ...` to pick it up.

## Theme map (locked in)

| Course | Slug | Theme | Status |
|---|---|---|---|
| Science and Art of Tea | `science-and-art-of-tea` | **Press** | ✅ Complete — 6 chapters, original prose, figures, widgets, slides, weekly challenges |
| Puppy School | `raising-a-puppy` | **Almanac** | 🟡 Ch1 + Ch2 done · Ch3–Ch8 pending |
| Understanding Your Sleep | `understanding-your-sleep` | **Notebook** | ⏳ Not started |
| Leadership through Crisis | `leadership-through-crisis` | **Studio** | ⏳ Not started |
| Training for Your First Marathon | `training-for-your-first-marathon` | **Storybook** | ⏳ Not started |
| The Strategy of Everything (Game Theory) | `game-theory` | **Terminal** | ⏳ Not started |

## Workflow (per chapter)

For each chapter that hasn't been redone:

### 1. Extract the original prose

```bash
grep -nE "<h1|<h2>|<h3>|<p>|<li>|takeaway|<blockquote>|<cite>|<aside>" \
output/<slug>/chapters/0X_*.html | head -120
```

The original HTML has the old ocean-theme CSS that we throw away; we keep the prose only.

### 2. Build the new chapter HTML

- Create `output/<slug>/chapters-<theme>/0X_<slug-name>.html`
- Copy the full CSS block from the existing template chapter (see "Templates" below)
- Replace the article body with the original prose, marked up in the `.ch-*` contract
- Add `<link rel="stylesheet" href="widgets-<theme>.css">` after the Google Fonts link
- Add 2 `<figure class="ai-image" data-prompt="..." data-aspect="landscape">` placeholders in spots that fit the chapter's content. **Image idiom is clean modern editorial — NOT painterly:**
- When the subject is a real-world thing (puppy, person, food, environment, equipment): a clean editorial photograph (35mm/50mm/85mm prime, soft natural light, sharp focus, photoreal). Do NOT ask for watercolour, pen-and-ink, woodcut, field-guide plate, hand-lettered serif, lab-notebook on graph paper, taped sticky-notes, or any palette restated as paint pigments. If labels are essential (e.g. a 6-pose comparison grid), put them in a slim dark-grey caption strip beneath each frame in clean white sans-serif (Inter / Helvetica, all-caps).
- When the subject is abstract data or process (hypnogram, phase-response curve, dose-decay, flowchart, anatomy cross-section): a clean modern editorial data visualisation in the style of an NYT or Bloomberg science graphic — flat dark navy line, single dark-coral accent, modern sans-serif type, generous white space. No graph paper, no faux-handwriting, no taped sticky-notes, no faux-notebook patina.
- The theme controls the page chrome around the image (typography, layout, colour rules), not the image itself. The image should look like clean editorial photography or clean editorial design that could ship in The Atlantic, regardless of which theme the chapter is in.
- If you're converting an older course that still uses painterly prompts, see "Re-photographing old courses" below.
- Add 2–3 widgets per chapter from the syllabus widget specs (syllabus lives at `output/<slug>/syllabus.json`)

### 3. Render the figures

```bash
cd /Users/uqjtange/Documents/research/current_projects/classbuild-cli
npx tsx --env-file=.env.local scripts/render-ai-images.ts \
output/<slug>/chapters-<theme>/0X_<slug-name>.html
```

Background it with `run_in_background: true` — each figure takes 90–150s. Script is idempotent: it skips existing files, so you can re-run safely.

### 4. (Optional) Slides JSON + weekly challenge JSON

The Tea course has these in `slides-new/` and `weekly-challenge/`. For Puppy and onward, you can write them per the schemas in `classbuild/src/prompts/slides.ts` and `classbuild/src/prompts/weeklyChallenge.ts`. Slide images render via `scripts/render-slides.ts` with the same env-file flag.

## Templates

The cleanest reference templates to copy CSS + structure from:

| Theme | Reference template |
|---|---|
| Press | `output/science-and-art-of-tea/chapters-press/01_one-leaf-a-thousand-mountains.html` |
| Almanac | `output/raising-a-puppy/chapters-almanac/01_before-they-arrive.html` |
| Notebook | (To be created — see `classbuild/src/themes/notebook.css` for the markup contract) |
| Studio | (To be created — see `classbuild/src/themes/studio.css`) |
| Storybook | (To be created — see `classbuild/src/themes/storybook.css`) |
| Terminal | (To be created — see `classbuild/src/themes/terminal.css`) |

Shared widget styles for each theme live next to the chapters as e.g. `chapters-press/widgets.css`, `chapters-almanac/widgets-almanac.css`. Create a new shared widget CSS file the first time you start a new theme.

## Key paths

```
/Users/uqjtange/Documents/research/current_projects/classbuild-cli/
├── .env.local ← OPENAI_API_KEY (gitignored)
├── scripts/
│ ├── render-ai-images.ts ← renders <figure class="ai-image"> placeholders
│ └── render-slides.ts ← renders slides JSON to images
└── output/
├── science-and-art-of-tea/ ✅ DONE
├── raising-a-puppy/ 🟡 Ch1+Ch2 done
├── understanding-your-sleep/
├── leadership-through-crisis/
├── training-for-your-first-marathon/
└── game-theory/

/Users/uqjtange/Documents/research/current_projects/classbuild/src/themes/
├── almanac.css ← theme spec reference
├── notebook.css
├── press.css
├── storybook.css
├── studio.css
└── terminal.css
```

## What's stable / what's not

- **Audio recordings** at `output/<slug>/audio/*.mp3` — DO NOT regenerate. They match the original chapter prose exactly. Keep them in place; make the new chapter HTML's prose match them.
- **Transcripts** at `output/<slug>/audio/*_transcript.{md,docx}` — keep as-is.
- **Practice quizzes, in-class quizzes, activities, discussion, research, infographic** — left untouched. They reference the chapter topics broadly and don't need updates.
- **Old chapter HTML** at `output/<slug>/chapters/0X_*.html` — leave intact as the prose source. The new versions go alongside in `chapters-<theme>/`.
- **course.json** at `output/<slug>/course.json` — the `themeId` field still says `"ocean"` (the legacy theme). Update to the new theme ID when you're ready to re-publish, OR leave it and have the publish step override it.

## Resume prompt to paste in a fresh conversation

> I'm resuming a multi-course theme conversion for ClassBuild. The full handoff doc is at `/Users/uqjtange/Documents/research/current_projects/classbuild-cli/HANDOFF.md` — please read it.
>
> Current state: Tea (Press) is fully complete. Puppy (Almanac) Ch1 and Ch2 are done. I need you to continue with Puppy Ch3 ("The Socialisation Window — A Once-in-a-Lifetime Window"), following the workflow in HANDOFF.md. Template to copy from: `output/raising-a-puppy/chapters-almanac/01_before-they-arrive.html`. Shared widget CSS: `output/raising-a-puppy/chapters-almanac/widgets-almanac.css`.
>
> The OpenAI API key is already in `.env.local`. Render commands should use `npx tsx --env-file=.env.local scripts/render-ai-images.ts <path>`.
>
> Please continue with Ch3 end-to-end (HTML + 2 figures + 2–3 widgets), background the figure renders, and proceed to Ch4 while they cook. When Ch3–Ch8 are done, move on to Sleep → Notebook. The theme map is in HANDOFF.md.

## Re-photographing old courses

The first wave of courses (Tea, Puppy, partial Sleep) shipped with painterly "field-guide watercolour" / "lab-notebook on graph paper" image prompts that came out as recognisable AI-slop sketches. The fix is to rewrite the existing `data-prompt` attributes in the chapter HTML (and `imagePrompt` strings in the slide JSONs) as clean editorial photography / clean modern data viz, then re-render.

Mechanics:

1. Read the existing prompts (`grep -oE 'data-prompt="[^"]*"' <chapter.html>` or `jq '.[].imagePrompt' <slides.json>`).
2. Draft replacements in a Claude Code conversation — do NOT call the Anthropic API from a script (uses Max plan credits, not API).
3. Put the replacements into a JSON sidecar at `tmp/rewrites/<course>-<ch>.json` keyed by figure index or slide index. Values are either `"new prompt"` strings or `{"prompt": "...", "caption": "..."}` objects when the figcaption also needs updating to drop illustration-language ("field-guide reference", "in one plate", "boundary colours").
4. Apply with `scripts/rewrite-image-prompts.ts <target> --rewrites <sidecar.json>`. The script preserves a `.pre-photo-rewrite` backup on first touch, reverts rendered chapter `<figure class="ch-figure">` blocks back to `<figure class="ai-image">` placeholder form, and patches `imagePrompt` fields in slide JSONs in place.
5. Re-render: `scripts/render-ai-images.ts <chapter.html>` for chapter figures, `scripts/render-slides.ts <slides.json> --force` for slide images (the `--force` flag bypasses the idempotent skip-if-jpeg-exists check).

Puppy (Almanac) and Sleep (Notebook) have both been done this way. The applied rewrites and rendered images are checked into `output/<slug>/chapters-<theme>/` and `output/<slug>/slides-<theme>/img/<chN>/`. Each `.pre-photo-rewrite` backup retains the original painterly prompt in case you ever want to roll back.

## Cost watch

First-wave painterly: Tea cost roughly **$19** in OpenAI image renders (76 slide + 12 chapter figures at gpt-image-2 high quality).

Editorial-photo re-render of Puppy + Sleep added ~**$50** on top (32 chapter figures + ~210 slide images at gpt-image-2 high quality). Each chapter is ~$0.80 for its 2 figures; each slide deck is ~$3 for 13–14 slide images. Budget ~$25/course if you also re-render slides.

## Known issues / wishlist for later

- Puppy Ch1 widgets render correctly but were not stress-tested with screen readers
- Tea slides include a few historical anecdotes (Linnaeus, Robert Bruce, Lu Yu) not in the original chapter audio — students hearing the audio + clicking through slides will get them as bonus context. Could be tightened in a polish pass
- The CLI's `course.json` schema still references `themeId: 'ocean'` for all courses — needs migration to the new theme IDs when re-publishing
- `chapters-press/` etc. are sibling directories to the original `chapters/` — the publish pipeline at `classbuild-cli/deploy-courses/` may need an update to point at the new directory
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ Open [localhost:5173](http://localhost:5173).
| Anthropic Claude | LLM provider = Anthropic, **OR** research backend = Claude web search | Course-content generation; Claude's built-in web search for the Research stage |
| Ollama Cloud | LLM provider = Ollama Cloud | Course-content generation. Free tier doesn't include cloud models — see ollama.com/settings/keys |
| Tavily | Research backend = Tavily | Web search for the Research stage. Free tier covers ~1,000 searches/month |
| Google Gemini | Advanced mode + you want infographics or audiobook narration | TTS + image generation (optional) |
| OpenAI | Advanced mode + you want slide images / chapter figures | Image generation (gpt-image-2, optional) |
| ElevenLabs | Advanced mode + you want audiobook narration | Text-to-speech (optional) |

The Wikipedia research backend needs no key.

Expand All @@ -71,7 +72,7 @@ The upstream ClassBuild also produces:
- Gamified practice quiz with confidence calibration
- In-class quiz (5 shuffled versions + answer keys)
- PowerPoint slides with speaker notes
- AI-narrated audiobook (Gemini TTS)
- AI-narrated audiobook (ElevenLabs)
- AI-generated infographic (Gemini)
- Weekly mastery challenge with 6 question types and SCORM 2004 wrapper
- Discussion starters and classroom activities
Expand All @@ -96,9 +97,15 @@ across the instrumented modules (parser, exporters, research backends,
services, setup components). Pages — `BuildPage`, `SyllabusPage`,
`ExportPage`, `ResearchPage` — are not yet instrumented for unit tests.

### The no-template Canvas export

Without an uploaded template, "Export for Canvas (.imscc)" writes a **native Canvas course export** — the same file layout Canvas's own exporter produces (`module_meta.xml`, `wiki_content/` pages, `non_cc_assessments/` quizzes, `assessment_meta.xml`, `assignment_groups.xml`, syllabus). This matters: a package carrying Canvas's `course_settings/canvas_export.txt` marker is read by Canvas's *native* importer, which silently ignores plain Common Cartridge manifest content, so a "generic" cartridge with that marker imports with no modules, pages or quizzes.

Check any cartridge offline with `python tools/validate-imscc.py file.imscc` (it flags exactly that class of problem). To test imports end to end you need a Canvas with Instructure's QTIMigrationTool installed, otherwise quizzes are skipped silently.

Current coverage:
- `tests/template-parser.test.ts` — module classification (verbatim / pattern / example-pattern), prefix detection (`Module N:`, `MN Instructor Notes:`, fully-locked `Module N Overview`), `(Example to Edit)` placeholder marker, `**EDIT**` markers, example-pattern content extraction.
- `tests/imscc-exporter.test.ts` — manifest shape, course_settings extension files, reading webcontent, QTI 1.2 quiz emission (practice + in-class + weekly challenge), Canvas auto-publish sidecars, native discussion topics.
- `tests/imscc-exporter.test.ts` — the native-Canvas cartridge (the no-template export): module_meta/manifest consistency, reading as a Canvas Page, both QTI flavours per quiz with correct answers, graded vs practice quizzes + assignment group, published discussions, syllabus, deterministic ids, HTML escaping, slide-deck rendering rules.
- `tests/template-imscc-exporter.test.ts` — round-trip: build template fixture → emit IMSCC → re-parse → verify verbatim modules preserved, pattern modules replaced, `web_resources/` + `lti_resource_links/` pass through. Outline-field overrides on title / syllabus body / manifest LOM.
- `tests/parse-outline-docx.test.ts` — outline-DOCX field extraction with the LLM mocked: clean JSON, code-fenced JSON, partial / empty / malformed responses, char-cap on long input, provider override forwarding.
- `tests/generate-template-chapter.test.ts` — Canvas Module generation with the LLM mocked: parse success / failure / missing-required-field, few-shot exemplar embedding, no-example case, Ollama provider plumbing, Anthropic Sonnet default.
Expand All @@ -107,7 +114,6 @@ Current coverage:
- `tests/components/TemplateTitleEditor.test.tsx` — locked-prefix display, save reassembles `Module N: <suffix>` with prefix intact, blanking the suffix preserves just the prefix, off-pattern titles get a synthesized prefix, Reset rolls drafts back.
- `tests/research-wikipedia.test.ts` — Wikipedia backend with LLM + `fetch` both mocked: query gen → per-query API call → synthesis call ordering, URL builder + dedup, `<span class="searchmatch">` snippet stripping, per-query failure doesn't abort batch, progress phase transitions, fallback query when query-gen returns nothing parseable.
- `tests/research-tavily.test.ts` — Tavily backend with LLM + `fetch` both mocked: missing-key throw, POST body shape (`max_results`, `search_depth`, `include_answer`) + bearer auth, cross-query dedup by URL, content snippets feed into synthesis prompt, hits without a URL are dropped, per-query failure doesn't abort batch, progress phase order, `published_date` preserved as `pageAge`.
- `tests/components/ApiKeyPanel.test.tsx` — provider toggle (Claude/Ollama) writes to apiStore, research backend toggle (Claude / Tavily / Wikipedia), hint text changes per selection, Ollama model input visibility gated on `provider==='ollama' && key.trim() !== ''`, model edits write through, auto-validate on mount POSTs to the right endpoints with bearer auth (Ollama → `/api/ollama-proxy`, Tavily → `api.tavily.com/search`, Gemini → `googleapis.com/.../models?key=…`), non-2xx sets `keyValid=false`, no re-validation when already validated or key is blank.
- `tests/research-anthropic.test.ts` — Anthropic research backend with `streamWithRetry` mocked: streamWithRetry called with the Claude key + `web_search_20250305` tool + forced `provider:'anthropic'`, progress phases emitted in `thinking → searching → compiling` order, queries appended via `appendQueries`, streamed text appended via `appendSynthesisText`, dedup by URL across batches, `setLatestSource` is the last fresh hit, valid JSON parses into a dossier, malformed text falls back to a dossier built from collected web results.

## Deploying to production
Expand Down Expand Up @@ -203,7 +209,7 @@ src/

## Built with

React 19 · Vite 7 · TypeScript 5.9 · Tailwind CSS 4 · Zustand · JSZip · mammoth.js · Claude (Sonnet 4.6 / Opus 4.6 / Haiku 4.5) · Ollama Cloud · Tavily · Gemini
React 19 · Vite 7 · TypeScript 5.9 · Tailwind CSS 4 · Zustand · JSZip · mammoth.js · Claude (Opus 4.8 / Sonnet 4.6 / Haiku 4.5) · Ollama Cloud · Tavily · OpenAI gpt-image-2 · ElevenLabs

## Contributing

Expand Down
Loading
Loading