feat(score): ScoreViewer with locally-bundled pdf.js (clearfolio contract, PR 2/3)#583
Open
seonghobae wants to merge 2 commits into
Open
feat(score): ScoreViewer with locally-bundled pdf.js (clearfolio contract, PR 2/3)#583seonghobae wants to merge 2 commits into
seonghobae wants to merge 2 commits into
Conversation
Add the React viewer for the score PDF feature (PR 2/3), independent of the PR 1 Tauri storage commands and composed with them in PR 3: - ScoreViewer renders validated in-memory PDF bytes on a canvas via pdfjs-dist, following the clearfolio contract: LOADING/FAILED/READY state machine with retry, and a validated-resource-only rule (bytes via props, no URL loading). - Rehearsal-friendly controls: large prev/next page targets, page x/y indicator, zoom in/out clamped to 0.5x-4x, and fit-width mode that re-renders on container resize. - The pdf.js worker is bundled locally by Vite via the pdfjs-dist/build/pdf.worker.min.mjs?url import and served same-origin, satisfying the Tauri script-src 'self' CSP (no CDN). - pdfjs-dist is pinned exactly to 6.1.200 (npm audit clean) per the dependency policy; admission evidence is recorded in the PR body. - New scoreViewer* strings added to both en and ko locales. - 12 vitest cases cover the state machine, nav bounds, zoom clamping, resize re-render, and teardown; ScoreViewer.tsx joins the measured coverage include list at 100%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGVapDZ3k7V7zKYk16P4C
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGVapDZ3k7V7zKYk16P4C
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.
Pull Request
Summary
ScoreViewer, the React viewer for the 악보 (Score PDF) feature (PR 2 of 3). It renders validated in-memory PDF bytes with pdf.js on a canvas, following the clearfolio viewer contract: a strictLOADING -> READY/LOADING -> FAILED -> retrystate machine and a validated-resource-only rule (the component only acceptsUint8Arraybytes via props; it never loads arbitrary URLs).size-14buttons), a "Page x of y" indicator, zoom in/out with clamping (0.5x-4x), and fit-width mode that re-renders on container resize viaResizeObserver.attach_score_pdf/read_score_pdf/remove_score_pdf). PR 3 will wire the IPC bytes into this component'sdataprop.enandkolocales.src/features/score/ScoreViewer.tsxis added to the measured coverage include list inapps/desktop/vite.config.tsand sits at 100% statements/branches/functions/lines.CSP / worker bundling note
Tauri's CSP is
script-src 'self', so the pdf.js worker must never come from a CDN. The worker is bundled locally by Vite:src/features/score/pdfjs.tsimportspdfjs-dist/build/pdf.worker.min.mjs?urland assigns it toGlobalWorkerOptions.workerSrc.dist/assets/pdf.worker.min-<hash>.mjs, verified in an app-mode production build), which pdf.js loads as a module worker from'self'. No CDN, no remote script, nodata:script.Verification
npm run lint(workspace eslint + docs/security-notes/security-gates/supply-chain/github-bootstrap/python checks) — passnpm run typecheck(tsc all workspaces + mypy) — passnpm run test(desktop vitest incl. coverage thresholds + pytest--cov-fail-under=100) — pass; see pre-existing flakiness note belownpm run build— passnpm audit— 0 vulnerabilitiesPre-existing
App.test.tsxflakiness (not introduced here)On a clean
origin/developcheckout, the full-suitevitest run --coverageintermittently fails up to 9src/App.test.tsxcases with 5s timeouts under parallel load (all pass when the file runs in isolation). The same load-dependent behavior appears on this branch unchanged; the final full-suite runs here were fully green (133/133). The 12 newScoreViewertests pass deterministically in isolation and in the full suite.Security Notes
Attack surface
dataprop (in PR 3 this comes from the PR 1 Tauriread_score_pdfcommand, which validates the stored resource). Malformed or hostile PDFs are parsed by pdf.js inside a dedicated worker.Trust boundary
dangerouslySetInnerHTMLis banned repo-wide bycheck:security-gates).Mitigations
pdfjs-dist@6.1.200,npm auditclean; not affected by CVE-2024-4367 which was fixed in 4.2.67).script-src 'self'; pdf.jsisEvalSupportedis irrelevant here because the strict CSP already blocksevalpaths.Test points
ScoreViewer.test.tsxcovers loading -> ready, loading -> failed -> retry -> ready, non-Errorrejection stringification, page-navigation clamping at both bounds, zoom clamping (0.5x-4x), fit-width resize re-render, render-task cancellation, post-destroygetPagefailure, and destroy-on-unmount with late resolve/reject ignored.Dependency and Supply Chain
New dependency admission evidence:
pdfjs-dist@6.1.200(exact pin)@bandscope/desktop(ships in the bundled frontend).react-pdf(wraps pdfjs-dist anyway, adds an extra maintainer surface for no benefit here), embedding a native PDF renderer via Tauri plugin (heavier, platform-divergent),<embed>/WebView-native PDF viewing (not available/portable in Tauri WebViews and violates the validated-resource-only contract). Direct pdfjs-dist is the smallest trusted surface.npm auditreports 0 vulnerabilities for the workspace after admission. Historical CVE-2024-4367 (arbitrary JS via malicious PDF fonts) was patched in 4.2.67; 6.1.200 is unaffected, and the strict CSP (script-src 'self', nounsafe-eval) is a compensating control.pdfjs-distdeclares nodependencies). Its single optional dependency,@napi-rs/canvas(Node-side rasterization), lands inpackage-lock.jsonasoptional: trueplatform packages but is never imported by, or bundled into, the browser build that ships in the app. Supply-chain cost is effectively a single Mozilla-owned package."pdfjs-dist": "6.1.200", no range) perdocs/security/dependency-policy.md;package-lock.jsonupdated in this PR.supply-chain/supplemental-component-inventory.jsonis unchanged.i18n impact
scoreViewer*keys insrc/locales/en/common.jsonandsrc/locales/ko/common.json)Reviewer checklist
feat/score-pdf-viewer->develop)🤖 Generated with Claude Code
https://claude.ai/code/session_01RjGVapDZ3k7V7zKYk16P4C