Skip to content

feat: classroom export and import (ZIP) (#408)#418

Merged
cosarah merged 14 commits intomainfrom
feat/classroom-export-import
Apr 13, 2026
Merged

feat: classroom export and import (ZIP) (#408)#418
cosarah merged 14 commits intomainfrom
feat/classroom-export-import

Conversation

@wyuc
Copy link
Copy Markdown
Contributor

@wyuc wyuc commented Apr 13, 2026

Summary

  • Export a full classroom as .maic.zip containing course structure JSON + TTS audio + generated media
  • Import on another device via home page button — lightweight sharing without server-side storage
  • All IDs regenerated on import (no conflicts, multiple imports create independent copies)
  • Lenient version parsing (formatVersion included but no strict gating)

Changes

  • New: lib/export/classroom-zip-types.ts — manifest type definitions
  • New: lib/export/classroom-zip-utils.ts — media collection, ID remapping, reference rewriting
  • New: lib/export/use-export-classroom.ts — export hook (collect store + IndexedDB → build manifest → JSZip → file-saver)
  • New: lib/import/use-import-classroom.ts — import hook (parse ZIP → validate → remap IDs → write IndexedDB)
  • New: tests/export/classroom-zip.test.ts — 7 unit/integration tests
  • Modified: components/header.tsx — added "Export Classroom ZIP" to export dropdown
  • Modified: app/page.tsx — added import button (hover-expand) next to "Recent Classrooms"
  • Modified: 4 i18n locale files (en-US, zh-CN, ja-JP, ru-RU)

Code Review Summary

Independent code review was conducted covering spec compliance and code quality:

Strengths:

  • Clean architectural decomposition (types → utils → hooks → UI)
  • Correct bidirectional audioId ↔ audioRef transformation with tests
  • Memory-conscious media writing (one file at a time for large ZIPs)
  • Dynamic JSZip import to avoid bundle bloat
  • Appropriate upstream coupling — uses only public Dexie/Zustand APIs

Issues found & fixed:

  • appVersion was hardcoded → now reads from package.json
  • Import button placement broke layout centering → redesigned as hover-expand icon

Known limitations (out of scope):

  • voiceConfig not exported (DB schema doesn't persist it — upstream limitation)
  • No export progress phases in toast (import has them; export is typically fast)
  • No transaction rollback on import failure (partial data can be manually deleted)

Test Plan

  • npx tsc --noEmit — 0 errors
  • pnpm check (Prettier) — all files formatted
  • pnpm lint (ESLint) — 0 errors (12 pre-existing warnings)
  • npx vitest run — 101 tests passed (8 test files)
  • Manual: export a course with TTS + images from instance A, import on instance B, verify playback

Closes #408

🤖 Generated with Claude Code

Copy link
Copy Markdown
Collaborator

@cosarah cosarah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues

Important

  • lib/export/use-export-classroom.ts:148require('../../../package.json') resolves above project root from lib/export/. Use ../../package.json, or better, process.env.npm_package_version / a Next.js build-time constant.
  • lib/export/classroom-zip-utils.ts:10-16buildIdMap is exported and tested but never called in production code. Either integrate it into the import flow or remove it.
  • lib/export/classroom-zip-types.ts:35voiceConfig on ManifestAgent is declared but never populated during export or restored during import. Remove it or add a comment explaining it's reserved for forward-compat.
  • lib/import/use-import-classroom.ts:48JSZip.loadAsync(file) loads the entire file into memory with no size check. For classrooms with many TTS/video files this could be hundreds of MB. Consider adding a size limit warning/rejection.
  • lib/import/use-import-classroom.ts:222 — Catch-all shows import.error.invalidZip for non-manifest errors (e.g., IndexedDB quota exceeded). This is misleading — use a separate error key for write failures.
  • lib/import/use-import-classroom.ts:137-146 — Media record is put to DB, then if poster exists, record.poster is set and put again. Move the poster check before the first put to avoid the redundant write.

Minor

  • lib/export/classroom-zip-types.ts:8CLASSROOM_ZIP_EXTENSION is declared but never imported or used. The export hook hardcodes .maic.zip.
  • Test coverage is limited to pure utility functions. No tests for collectAudioFiles/collectMediaFiles, import ID remapping, or export→import round-trip.

Summary

Clean architecture with good separation of concerns. Dynamic JSZip import, ID regeneration, and bidirectional audio ref mapping are well done. The issues above are mostly about robustness (size limits, error messages) and dead code cleanup — nothing blocking, but worth addressing before merge.

wyuc and others added 12 commits April 13, 2026 21:58
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Read appVersion from process.env.npm_package_version instead of hardcoding
- Remove unused buildIdMap export and its tests
- Add forward-compat comment on ManifestAgent.voiceConfig
- Add file size warning for large ZIP imports
- Distinguish storage quota errors from parse errors in import
- Move poster check before first DB put to avoid redundant write
- Use CLASSROOM_ZIP_EXTENSION constant in export hook
- Add storageFull i18n key for all 4 locales
@wyuc wyuc force-pushed the feat/classroom-export-import branch from a3e76b0 to 8c11d8e Compare April 13, 2026 14:00
Copy link
Copy Markdown
Collaborator

@cosarah cosarah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM
verified locally

@cosarah cosarah merged commit db7f187 into main Apr 13, 2026
3 checks passed
@wyuc wyuc mentioned this pull request Apr 14, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: classroom export and import (ZIP)

2 participants