BREAKING: Bundle media entries are now flat filenames (no directories).
ProBundleWriter flattens all media paths to basename() automatically.
ProFileGenerator supports bundleRelative flag for ROOT_CURRENT_RESOURCE
URLs, enabling bundles that work on any machine without absolute paths.
ProPresenter .probundle files are ZIP archives containing a single .pro
presentation with embedded media assets. This adds read/write support
verified against actual ProPresenter 7 exports.
- PresentationBundle: wrapper class (Song + media files + .pro filename)
- ProBundleReader: reads .probundle ZIPs, applies Zip64Fixer for PP exports
- ProBundleWriter: writes standard ZIP with media-first entry order
- ProFileGenerator: media URLs now include URL.local with LocalRelativePath
- 9 tests covering error handling, round-trip, PP export compat, ZIP format
- ref/TestBild.probundle: verified importable by ProPresenter 7
Replace byte-identical round-trip assertion with JSON-decoded semantic
comparison. Protobuf serialization does not guarantee byte-level
ordering, so the previous test failed on all 169 fixtures despite
data being functionally identical.
- Modified generate() method to loop through arrangements and find 'normal' (case-insensitive)
- Falls back to first arrangement if 'normal' not found
- Added test: testGenerateSelectsNormalArrangementWhenPresent
- Added test: testGenerateFallsBackToFirstArrangementWhenNoNormal
- All 12 ProFileGenerator tests pass (82 assertions)
All acceptance criteria verified and marked complete:
- Task 1-13: Implementation acceptance criteria (all verified)
- Definition of Done: All 5 criteria met
- Final Verification: All 4 phases approved
- Final Checklist: All 7 items complete
Total: 87/87 checkboxes complete (29 main tasks + 58 acceptance criteria)
- Record final project status in learnings.md
- Add all task evidence files (43 files)
- Add work plan with all 29 checkboxes complete
- Add boulder state tracking
Project complete: 99 tests passing, all deliverables verified
- Implement ProPlaylistReader::read() following ProFileReader pattern
- ZIP handling: fix headers with Zip64Fixer, extract data + embedded files
- Comprehensive error handling: missing file, empty file, invalid ZIP, missing data entry
- Temp file cleanup in finally block (no leaks on error paths)
- 11 tests, 31 assertions — all pass
- Verified with all 4 .proplaylist test files
Task 7 of proplaylist-module plan complete
- Implement PlaylistEntry wrapper for PlaylistItem proto (23 tests, 40 assertions)
- Support all 4 item types: header, presentation, placeholder, cue
- Expose arrangement_name (field 5) for presentation items
- Type-specific getters with null safety
- Implement PlaylistNode wrapper for Playlist proto (15 tests, 37 assertions)
- Handle both container nodes (child playlists) and leaf nodes (items)
- Recursive wrapping of nested playlist structures
- Implement PlaylistArchive wrapper for PlaylistDocument proto (18 tests, 37 assertions)
- Top-level integration of nodes, entries, and embedded files
- Lazy parsing of embedded .pro files into Song objects
- File partitioning: .pro files vs media files
Wave 2 of proplaylist-module plan complete (Tasks 4-6)
- Add arrangement_name field 5 to PlaylistItem.Presentation proto
- Regenerate PHP proto classes with new field
- Implement Zip64Fixer utility to patch ProPresenter's broken ZIP headers
- Add comprehensive test suite for Zip64Fixer (7 tests, 37 assertions)
- Create pp_playlist_spec.md documenting .proplaylist file format
Wave 1 of proplaylist-module plan complete (Tasks 1-3)