feat(score): ScoreViewer with locally-bundled pdf.js (clearfolio contract, PR 2/3)#583
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
OpenCode Review Overview
Pull request overviewOpenCode reviewed the current-head bounded evidence and found no blocking issues. FindingsNo blocking findings. SummaryApproval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Changed-File Evidence Mapflowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Changed file (8 files)"]
S1 --> I1["repository behavior"]
I1 --> R1["Review risk: Changed file (8 files)"]
R1 --> V1["required checks"]
|
There was a problem hiding this comment.
Pull request overview
OpenCode reviewed the current-head bounded evidence and found no blocking issues.
Findings
No blocking findings.
Summary
Approval sufficiency: bounded evidence supplied affirmative approval evidence for changed files, coverage/docstring posture, risk surfaces, and current-head verification; approval is not based merely on the absence of known blockers.
Verification posture: CodeGraph evidence was initialized and bounded current-head evidence reviewed for changed-file evidence including apps/desktop/package.json, apps/desktop/src/features/score/ScoreViewer.test.tsx, apps/desktop/src/features/score/ScoreViewer.tsx, apps/desktop/src/features/score/pdfjs.ts, apps/desktop/src/locales/en/common.json, and 3 more.
Linter/static: workflow/static review evidence is bounded by the current-head GitHub Checks gate and changed-file evidence.
TDD/regression: coverage execution evidence and focused changed hunks were reviewed from bounded-review-evidence.md.
Coverage: coverage execution evidence reports supported repository test suites passed.
Docstring coverage: coverage execution evidence reports configured repository docstring gates passed or docstring coverage was advisory.
DAG: CodeGraph/source-backed behavior map connects apps/desktop/package.json to the affected review, runtime, or workflow path and required checks.
PoC/execution: coverage-evidence job executed on the current head and reported PASS.
DDD/domain: workflow and repository-governance invariants were reviewed against changed files in bounded evidence.
CDD/context: CodeGraph evidence, changed-file history, and focused hunks were reviewed from bounded-review-evidence.md.
Similar issues: changed-file history evidence was reviewed for comparable local precedents.
Claim/concept check: bounded evidence, repository source, current-head workflow evidence, and, where numeric, scientific, statistical, or literature-backed claims are affected, original-paper/formula evidence and parameter-recovery expectations were used for claims.
Standards search: standards and external-source checks are delegated to configured OpenCode web_search/Context7/DeepWiki sources when applicable; no evidence-backed standards blocker is present in bounded evidence.
Compatibility/convention: changed workflow/script conventions, object naming, and reserved-word safety for schema/API/config/code surfaces were checked in bounded evidence.
Breaking-change/backcompat: deployment evidence and changed-file history were checked for backward-compatibility risk.
Performance: changed surfaces were checked for performance risk in bounded evidence.
Developer experience: changed automation, review, test, setup, and maintenance surfaces were checked for helpful or obstructive DX impact in bounded evidence.
User experience: connected user, operator, API, CLI, documentation, review-comment, status-check, rendering, and workflow-reader behavior was checked for contradictions against code, docs, and tests in bounded evidence.
Visual/DOM: Playwright visual, DOM locator, ARIA snapshot, console, and responsive evidence were checked when a web UI surface was present; for non-web surfaces, API/CLI/log/docs/workflow interaction evidence was reviewed instead.
Accessibility/i18n: accessibility, localization, and human-readable text surfaces were checked where UI, CLI, API message, docs, logs, or review text changed.
Supply-chain/license: dependency, package, model, container, and external-tool changes were checked in bounded evidence.
Packaging: package, build, test, lint, and security contracts were checked in bounded evidence.
Security/privacy: workflow-token, review-gate, and repository-automation security/privacy boundaries were checked in bounded evidence.
- Result: APPROVE
- Reason: PR introduces a well-tested ScoreViewer component with PDF.js integration, adhering to repository standards.
- Head SHA:
6050020d6cc4e069db869b7f37b728245feb1454 - Workflow run: 29176966698
- Workflow attempt: 1
Changed-File Evidence Map
flowchart LR
PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
Evidence --> S1["Changed file (8 files)"]
S1 --> I1["repository behavior"]
I1 --> R1["Review risk: Changed file (8 files)"]
R1 --> V1["required checks"]
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