Skip to content

refactor(export): extract shared speech-text walk into lib/export/narration.ts - #1621

Open
zdnemz wants to merge 1 commit into
THU-MAIC:mainfrom
zdnemz:refactor/narration-walk
Open

zdnemz wants to merge 1 commit into
THU-MAIC:mainfrom
zdnemz:refactor/narration-walk

Conversation

@zdnemz

@zdnemz zdnemz commented Sep 20, 2026

Copy link
Copy Markdown

Closes #1142

What

Adds lib/export/narration.ts exporting a single collectSpeechText(scene, options) walk. collectSceneScripts (script .md/.docx export) and buildSpeakerNotes (PPTX speaker notes) now both delegate to it, so the two cannot drift on what counts as narration.

Why

use-export-script.ts re-implemented the speech-text traversal that already existed in use-export-pptx.ts, and the two had already drifted — see #1142.

Correction to the issue's premise

The issue names use-download-subtitles.ts as a third walk site. It isn't: it calls compileSubtitles from ./build-export-zip, which has no speech walk either. There were only ever two genuine narration walks in lib/export (the two above). The type === 'speech' checks in classroom-zip-utils.ts are manifest transformation, not narration collection, and were left alone.

The drift is bigger than the issue describes

The issue describes the difference as a trim filter. There are actually two independent knobs, and the second one changes output even for well-formed scenes:

whitespace-only speech per-part trimming
collectSceneScripts (before) dropped text.trim() pushed
buildSpeakerNotes (before) kept raw text pushed

The script exporter didn't just filter whitespace-only speech — it pushed the trimmed text. So for text: ' Hello ' the script export emitted Hello while the PPTX notes emitted Hello . Collapsing this to a single keepWhitespaceOnly flag (as the issue and the original plan suggested) would have silently changed the script exporter's output and broken the existing assertion in tests/export/narration-script-export.test.ts:92 ('omits whitespace-only speech text and trims kept text').

Both knobs are therefore preserved explicitly as keepWhitespaceOnly and trim, defaulting to the PPTX behaviour:

collectSpeechText(scene, { keepWhitespaceOnly: false, trim: true }) // script export
collectSpeechText(scene)                                           // pptx notes

collectSceneScripts keeps its name, signature, SceneScript shape and its trim semantics; buildSpeakerNotes keeps its name and output. No callers change.

Behavior change

None for output. Two deliberate robustness changes:

  • collectSpeechText tolerates a speech action with a missing text field (the old script walk did action.text.trim() and threw TypeError: Cannot read properties of undefined — verified against main). The helper now reads action.text ?? ''.
  • The parameter is structural/readonly, so readonly Scene[] is accepted.

Verification

  • npx prettier --check clean, npx eslint --fix clean (no changes), npx tsc --noEmit clean (needs NODE_OPTIONS=--max-old-space-size=8192 — default heap OOMs on this repo)
  • npx vitest run tests/export — 16 files, 230 tests passed (was 15/217; +13 in the new tests/export/narration.test.ts)
  • New unit tests cover: no actions, undefined actions, null/undefined scene, mixed action types, order, both keepWhitespaceOnly values (whitespace-only and empty string), both trim values, per-part independent trimming, internal newlines preserved, single-newline join
  • Equivalence harness — ran the two pre-refactor implementations (copied verbatim from main) against the new helper over 11 action-shape cases × both call-site option sets: 21/22 byte-identical. The single non-match is the missing text field case where the old code crashes and the new one returns a string — the intended robustness fix above, not a regression.
  • Manual: export .md script → narration in order, no empty paragraphs (pending — headless environment)
  • Manual: export .pptx → speaker notes match pre-refactor output (pending, same reason)

Notes

  • Refactor only; no Dockerfile/compose/package changes. Filed by maintainer @SomSamantray with maintainability reviewer metadata.
  • AI-assisted PR. Self-reviewed before requesting human review.

@zdnemz
zdnemz marked this pull request as ready for review September 20, 2026 10:45
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.

collectSceneScripts re-derives the speech-text walk used across the export family

1 participant