Skip to content

feat(score): wire score PDF viewer into app (PR 3/3, depends on #581 #583)#584

Merged
seonghobae merged 8 commits into
developfrom
feat/score-pdf-wire
Jul 12, 2026
Merged

feat(score): wire score PDF viewer into app (PR 3/3, depends on #581 #583)#584
seonghobae merged 8 commits into
developfrom
feat/score-pdf-wire

Conversation

@seonghobae

Copy link
Copy Markdown
Collaborator

What

PR 3/3 of the 악보 (Score PDF) feature. Composes the storage backend (#581, feat/score-pdf-storage) and the pdf.js viewer (#583, feat/score-pdf-viewer) into a working, navigable Score view. Branched from develop with both PR branches merged in, so this depends on #581 + #583 landing first — GitHub will shrink the merge base automatically once they merge.

Flows (end-to-end)

  • The previously-disabled sidebar "Notes" slot is now the 악보 / Score view (i18n navScore, ko + en), enabled once a song is loaded. The main pane toggles between the Workspace and Score views; both the desktop sidebar and the compact mobile nav route to it.
  • Attach: "악보 추가" → attach_score_pdf(projectId, songId) (native picker, validated copy) → metadata appended to the song's scoreAttachments and the new PDF opens immediately.
  • List / select: attachments render per current song; selecting one → read_score_pdf(projectId, scoreId) → bytes → <ScoreViewer data={bytes}> (PR2 pdf.js viewer with paging/zoom/fit-width).
  • Remove: trash button → window.confirmremove_score_pdf(projectId, scoreId), drops metadata and resets the viewer if the open score was removed.
  • Storage actions gate on an active project id; songs opened from a .bscope file (no live workspace) show a notice instead of failing.
  • Metadata scoreAttachments: [{ id, fileName }] persists inside the song payload, mirrored in shared-types and the Rust RehearsalSongPayload (both deny_unknown_fields; optional + skipped when empty) so .bscope save/load round-trips and legacy files without the field still load.

Per-section page bookmarks (work item 4) are deferred as a follow-up to keep this PR focused.

Security Notes

  • No filesystem path crosses the IPC boundary from JS. The Rust commands own the OS file dialog, magic-byte/size/symlink validation, and UUID-keyed copies inside the app-owned scores root.
  • Only allowlisted ids (project id shape + UUID v4 score id) are sent for read/remove; the JS bridge fails closed in plain-browser builds where no Tauri bridge exists.
  • The schema addition keeps deny_unknown_fields on both sides, so an unexpected key still fails closed on load_project.

Tests / gates

  • ScoreView.test.tsx: attach / list / select / remove flows plus error and bridge-unavailable paths (mocks @tauri-apps/api invoke + ScoreViewer).
  • shared-types + Rust payload round-trip and rejection tests for scoreAttachments.
  • App score-nav tests (disabled until a song loads, switch into the Score view).
  • Gates green: lint, typecheck, desktop test (150 passed) with coverage 98.8% lines / 96.7% branches (gate 90%), shared-types 100%, build; Rust cargo fmt --check, clippy -D warnings, cargo test (15 passed incl. 3 new).
  • Note: the pre-existing App.test.tsx analysis-flow tests are waitFor-timeout flaky under heavy machine load; they pass on an isolated single-fork run. All new tests are green.

🤖 Generated with Claude Code

seonghobae and others added 2 commits July 7, 2026 10:55
…rigin/feat/score-pdf-viewer' into feat/score-pdf-wire
Enable the previously-disabled sidebar slot as the 악보/Score view and
compose the PR1 storage commands with the PR2 pdf.js viewer end-to-end.

- App: rename the disabled "Notes" nav slot to a routable Score view
  (i18n navScore, ko+en), enabled once a song is loaded; the main pane
  switches between the Workspace and Score views.
- ScoreView: per-song attachment list, "악보 추가" -> attach_score_pdf,
  select -> read_score_pdf -> bytes -> <ScoreViewer>, remove with confirm
  -> remove_score_pdf. Storage actions gate on an active project id.
- scoreStorage: typed Tauri bridge for attach/read/remove following the
  analysis bridge's invoke-detection + fail-closed-in-browser pattern.
- Persist scoreAttachments [{id, fileName}] inside the song payload,
  mirrored in shared-types (validator + deny-unknown-key allowlist) and
  the Rust RehearsalSongPayload (deny_unknown_fields, optional + skipped
  when empty) so .bscope save/load round-trips without breaking legacy
  files.

Security Notes: no filesystem path crosses the IPC boundary from JS; the
Rust commands own the picker, magic-byte/size/symlink validation, and
UUID-keyed copies inside the app-owned scores root. Only allowlisted ids
are sent for read/remove.

Tests: ScoreView vitest suite (attach/list/select/remove + error/bridge
paths), shared-types + Rust payload round-trip/rejection tests, and App
score-nav tests. Desktop coverage 98.8% lines (gate 90%).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RjGVapDZ3k7V7zKYk16P4C
@seonghobae seonghobae enabled auto-merge (squash) July 7, 2026 03:14

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 489a116cdfb0d8e9d8c107aedad02a34c5bef668.

  • Head SHA: 489a116cdfb0d8e9d8c107aedad02a34c5bef668

  • Workflow run: 28844675930

  • Workflow attempt: 1

Coverage evidence

Coverage Evidence

  • Head SHA: 489a116cdfb0d8e9d8c107aedad02a34c5bef668
  • Required test evidence: supported repository test suites must pass.
  • Required docstring evidence: repository-owned docstring gates must pass when configured; otherwise docstring coverage is advisory.

JavaScript/TypeScript dependencies (npm ci)


added 278 packages, and audited 281 packages in 8s

74 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
  • Result: PASS

Repository docstring coverage


> bandscope@0.1.3 check:python-docstrings
> sh -c 'cd services/analysis-engine && uv run ruff check src tests ../../scripts --select D100,D101,D102,D103,D104,D105,D106,D107'

Using CPython 3.12.3 interpreter at: /usr/bin/python3.12
Creating virtual environment at: .venv
   Building bandscope-analysis @ file:///home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine
Downloading numpy (15.8MiB)
Downloading pygments (1.2MiB)
Downloading scipy (33.6MiB)
Downloading soundfile (1.3MiB)
Downloading scikit-learn (8.5MiB)
Downloading mypy (13.0MiB)
Downloading yt-dlp (3.0MiB)
Downloading llvmlite (53.7MiB)
Downloading ruff (10.7MiB)
Downloading numba (3.6MiB)
 Downloaded soundfile
 Downloaded pygments
      Built bandscope-analysis @ file:///home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine
 Downloaded numba
 Downloaded ruff
 Downloaded scikit-learn
 Downloaded yt-dlp
 Downloaded numpy
 Downloaded llvmlite
 Downloaded scipy
 Downloaded mypy
Installed 44 packages in 65ms
All checks passed!
  • Result: PASS

JavaScript/TypeScript test coverage


> bandscope@0.1.3 test
> npm run test --workspaces --if-present && sh -c 'cd services/analysis-engine && uv run pytest tests --cov=src/bandscope_analysis --cov-report=term-missing --cov-fail-under=100' --coverage


> @bandscope/desktop@0.1.0 test
> node -e "require('node:fs').mkdirSync('coverage/.tmp', { recursive: true })" && vitest run --coverage


�[1m�[30m�[46m RUN �[49m�[39m�[22m �[36mv4.1.9 �[39m�[90m/home/runner/work/bandscope/bandscope/pr-head/apps/desktop�[39m
      �[2mCoverage enabled with �[22m�[33mv8�[39m

 �[32m✓�[39m src/features/score/ScoreViewer.test.tsx �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[33m 819�[2mms�[22m�[39m
 �[32m✓�[39m src/features/score/ScoreView.test.tsx �[2m(�[22m�[2m15 tests�[22m�[2m)�[22m�[33m 1132�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m renders the empty attachment list with an enabled attach button �[33m 348�[2mms�[22m�[39m
 �[32m✓�[39m src/lib/export.test.ts �[2m(�[22m�[2m17 tests�[22m�[2m)�[22m�[32m 18�[2mms�[22m�[39m
 �[32m✓�[39m src/lib/analysis.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 22�[2mms�[22m�[39m
 �[32m✓�[39m src/features/workspace/Workspace.test.tsx �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[33m 1928�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m enables bass transcription from selected role metadata rather than role id text �[33m 444�[2mms�[22m�[39m
 �[32m✓�[39m src/i18n/index.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 8�[2mms�[22m�[39m
 �[32m✓�[39m src/components/ui/ui-primitives.test.tsx �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 239�[2mms�[22m�[39m
 �[32m✓�[39m src/features/workspace/RoleSwitcher.test.tsx �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[33m 397�[2mms�[22m�[39m
 �[32m✓�[39m src/features/workspace/SectionRoadmap.test.tsx �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[33m 429�[2mms�[22m�[39m
 �[32m✓�[39m src/App.test.tsx �[2m(�[22m�[2m58 tests�[22m�[2m)�[22m�[33m 16377�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m renders the rehearsal cockpit shell before analysis starts �[33m 814�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m renders the loaded song as a dark rehearsal command board �[33m 485�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m renders a rehearsal song structure timeline from real section ranges �[33m 466�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m does not show unavailable analysis metrics as detected facts �[33m 487�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m summarizes confidence from the lowest-confidence loaded section �[33m 329�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m selects a local audio source and starts a local-audio analysis job �[33m 372�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m starts an analysis job and renders the returned rehearsal result �[33m 843�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m shows the engine stage label and accessible progress value while analysis runs �[33m 365�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m animates rendered progress toward the running job target �[33m 443�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m uses translated progress labels when status payloads omit a progress label �[33m 613�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m falls back to failed progress copy when a pushed status has no error details �[33m 427�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m holds a terminal progress value immediately for pushed failed statuses �[33m 386�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m cleans up a late status subscription when the running view unmounts first �[33m 315�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m marks the active job failed when polling returns a malformed status �[33m 733�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m ignores malformed poll results after a pushed update changes the active job �[33m 667�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m ignores transport poll failures after a pushed update changes the active job �[33m 534�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m keeps handoff metadata tied to the source that produced the current result �[33m 750�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m shows a safe failed status when the job poll returns an error �[33m 484�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m falls back to a generic failure message when the engine omits details �[33m 475�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m keeps polling the active job when one polling request rejects �[33m 759�[2mms�[22m�[39m

�[2m Test Files �[22m �[1m�[32m10 passed�[39m�[22m�[90m (10)�[39m
�[2m      Tests �[22m �[1m�[32m150 passed�[39m�[22m�[90m (150)�[39m
�[2m   Start at �[22m 12:17:39
�[2m   Duration �[22m 19.39s�[2m (transform 1.10s, setup 1.59s, import 3.80s, tests 21.37s, environment 10.84s)�[22m

�[34m % �[39m�[2mCoverage report from �[22m�[33mv8�[39m
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   98.88 |    96.71 |      99 |   98.84 |                   
 src               |   99.53 |    99.48 |   97.67 |   99.52 |                   
  App.tsx          |   99.53 |    99.48 |   97.67 |   99.52 | 604               
 ...features/score |   97.94 |    90.82 |     100 |   97.82 |                   
  ScoreView.tsx    |   95.38 |    79.54 |     100 |   95.23 | 53,81,100         
  ScoreViewer.tsx  |     100 |      100 |     100 |     100 |                   
  scoreStorage.ts  |   96.87 |    96.29 |     100 |   96.77 | 27                
 src/i18n          |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/lib           |     100 |      100 |     100 |     100 |                   
  export.ts        |     100 |      100 |     100 |     100 |                   
-------------------|---------|----------|---------|---------|-------------------

> @bandscope/shared-types@0.1.0 test
> vitest run --coverage


�[1m�[30m�[46m RUN �[49m�[39m�[22m �[36mv4.1.9 �[39m�[90m/home/runner/work/bandscope/bandscope/pr-head/packages/shared-types�[39m
      �[2mCoverage enabled with �[22m�[33mv8�[39m

 �[32m✓�[39m test/index.test.ts �[2m(�[22m�[2m15 tests�[22m�[2m)�[22m�[32m 84�[2mms�[22m�[39m

�[2m Test Files �[22m �[1m�[32m1 passed�[39m�[22m�[90m (1)�[39m
�[2m      Tests �[22m �[1m�[32m15 passed�[39m�[22m�[90m (15)�[39m
�[2m   Start at �[22m 12:17:59
�[2m   Duration �[22m 496ms�[2m (transform 128ms, setup 0ms, import 162ms, tests 84ms, environment 0ms)�[22m

�[34m % �[39m�[2mCoverage report from �[22m�[33mv8�[39m
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 index.ts |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0
rootdir: /home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine
configfile: pyproject.toml
plugins: cov-7.0.0
collected 433 items

tests/test_activity.py ........                                          [  1%]
tests/test_anchors.py ....                                               [  2%]
tests/test_api.py .........................                              [  8%]
tests/test_chord_recognizer.py ....................                      [ 13%]
tests/test_chords.py .........................                           [ 18%]
tests/test_cli.py .................                                      [ 22%]
tests/test_extractor.py ......                                           [ 24%]
tests/test_health.py .                                                   [ 24%]
tests/test_pipeline_integration.py .........                             [ 26%]
tests/test_pitch_tracker.py ...............                              [ 30%]
tests/test_priority.py ...........                                       [ 32%]
tests/test_ranges.py ...................                                 [ 36%]
tests/test_release_asset_selection.py ........                           [ 38%]
tests/test_release_metadata.py .......                                   [ 40%]
tests/test_release_packaging.py .........                                [ 42%]
tests/test_roles.py .......                                              [ 44%]
tests/test_roles_ml.py ...                                               [ 44%]
tests/test_segmenter.py .....................                            [ 49%]
tests/test_separation.py ..................................              [ 57%]
tests/test_supply_chain_policy.py ...................................... [ 66%]
........................................................................ [ 82%]
.........................................                                [ 92%]
tests/test_temporal.py .........                                         [ 94%]
tests/test_transcription.py ...                                          [ 95%]
tests/test_tuning.py .....                                               [ 96%]
tests/test_youtube.py ................                                   [100%]

=============================== warnings summary ===============================
tests/test_pipeline_integration.py::test_pipeline_without_detected_sections_falls_back
tests/test_roles.py::test_role_extractor_falls_back_when_activity_detection_fails
  /home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine/.venv/lib/python3.12/site-packages/librosa/core/pitch.py:103: UserWarning: Trying to estimate tuning from empty frequency set.
    return pitch_tuning(

tests/test_roles.py::test_role_extractor_falls_back_when_activity_detection_fails
  /home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine/.venv/lib/python3.12/site-packages/librosa/core/spectrum.py:266: UserWarning: n_fft=2048 is too large for input signal of length=100
    warnings.warn(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.12.3-final-0 ________________

Name                                                   Stmts   Miss  Cover   Missing
------------------------------------------------------------------------------------
src/bandscope_analysis/__init__.py                         3      0   100%
src/bandscope_analysis/api.py                            571      0   100%
src/bandscope_analysis/chords/__init__.py                  5      0   100%
src/bandscope_analysis/chords/analyzer.py                116      0   100%
src/bandscope_analysis/chords/capo.py                     10      0   100%
src/bandscope_analysis/chords/chord_recognizer.py        192      0   100%
src/bandscope_analysis/chords/model.py                    15      0   100%
src/bandscope_analysis/cli.py                             68      0   100%
src/bandscope_analysis/health.py                           7      0   100%
src/bandscope_analysis/ranges/__init__.py                  4      0   100%
src/bandscope_analysis/ranges/analyzer.py                 77      0   100%
src/bandscope_analysis/ranges/model.py                    19      0   100%
src/bandscope_analysis/ranges/pitch_tracker.py            54      0   100%
src/bandscope_analysis/roles/__init__.py                   4      0   100%
src/bandscope_analysis/roles/activity.py                  59      0   100%
src/bandscope_analysis/roles/extractor.py                118      0   100%
src/bandscope_analysis/roles/model.py                     58      0   100%
src/bandscope_analysis/roles/priority.py                  13      0   100%
src/bandscope_analysis/roles/tuning.py                    11      0   100%
src/bandscope_analysis/sections/__init__.py                6      0   100%
src/bandscope_analysis/sections/anchors.py                 5      0   100%
src/bandscope_analysis/sections/extractor.py              38      0   100%
src/bandscope_analysis/sections/model.py                  35      0   100%
src/bandscope_analysis/sections/segmenter.py             140      0   100%
src/bandscope_analysis/sections/utils.py                   8      0   100%
src/bandscope_analysis/separation/__init__.py              4      0   100%
src/bandscope_analysis/separation/audio_separator.py     145      0   100%
src/bandscope_analysis/separation/model.py                31      0   100%
src/bandscope_analysis/separation/separator.py            34      0   100%
src/bandscope_analysis/temporal/__init__.py                3      0   100%
src/bandscope_analysis/temporal/analyzer.py               49      0   100%
src/bandscope_analysis/temporal/model.py                   9      0   100%
src/bandscope_analysis/transcription/__init__.py           2      0   100%
src/bandscope_analysis/transcription/api.py               11      0   100%
src/bandscope_analysis/youtube.py                         81      0   100%
------------------------------------------------------------------------------------
TOTAL                                                   2005      0   100%
Required test coverage of 100% reached. Total coverage: 100.00%
================== 433 passed, 3 warnings in 90.59s (0:01:30) ==================
  • Result: PASS

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (30 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (30 files)"]
  R1 --> V1["required checks"]
Loading

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

OpenCode Review Overview

  • Head SHA: fdf400358ea6c8d3b3fdeaf788c12f0bc50e2639
  • Workflow run: 29177751296
  • Workflow attempt: 1
  • Gate result: APPROVE (approval step)

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/core/src/lib.rs, apps/desktop/src/App.test.tsx, apps/desktop/src/App.tsx, apps/desktop/src/features/score/ScoreView.test.tsx, apps/desktop/src/features/score/ScoreView.tsx, and 8 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/core/src/lib.rs 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: All checks pass, comprehensive tests, and no unresolved issues.
  • Head SHA: fdf400358ea6c8d3b3fdeaf788c12f0bc50e2639
  • Workflow run: 29177751296
  • Workflow attempt: 1

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (13 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (13 files)"]
  R1 --> V1["required checks"]
Loading

seonghobae and others added 2 commits July 8, 2026 11:34
The OpenCode coverage-evidence job failed with "Rust files changed, but
no root Cargo.toml was found" — cargo-llvm-cov could not locate a
workspace manifest, so cargo coverage never ran (not a missing-tests
issue).

Add a root workspace that contains a new GUI-independent
`bandscope-desktop-core` crate holding the payload contracts, validation
guards, and process helpers that were inlined in the Tauri binary
(src-tauri/src/main.rs), together with the existing unit-test suite. The
Tauri binary now depends on that crate via a path dependency and is
excluded from the workspace, since it links wry/webkit and needs a
bundled frontend and is therefore built on its own (see ci.yml).

This lets `cargo llvm-cov` find the root manifest and run the full test
suite from the workspace root on any platform, no windowing system or
frontend build required.

Verified locally:
- `cargo llvm-cov` runs from the repo root; all 15 tests pass; 93% line
  / 91% region coverage on the core crate.
- `cargo check --manifest-path apps/desktop/src-tauri/Cargo.toml
  --locked` still builds the Tauri binary against the extracted crate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
The Required OpenCode Review coverage-evidence gate mandates 100% statement,
branch, function, and line coverage for apps/desktop. The new score-viewer code
was below that (statements 98.89%, branches 96.72%).

Close the gaps with meaningful tests plus a small, behavior-preserving
refactor:

- scoreStorage: add a test for the non-browser (no `window`) runtime so the
  bridge fails closed with the stable "desktop app only" error.
- ScoreView: cover the error-detail fallback (non-Error/non-string rejection),
  the superseded-read paths (a newer open wins over a stale resolve and a stale
  reject), and removing a score that is not the one currently open.
- ScoreView/App: the `if (!projectId) return` / `if (view)` guards were
  unreachable dead branches — the triggering controls are already `disabled`,
  and React never dispatches onClick for a disabled button, so the guards could
  never run and could never be covered. Narrow `projectId`/`view` at the call
  site instead (onClick is only wired when present), removing the dead code
  while keeping identical UX. App also gains a compact (mobile) nav test.

Desktop coverage is now 100% across statements, branches, functions, and lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTAMs4bpSZS77Xe3RQjv9P
@seonghobae

Copy link
Copy Markdown
Collaborator Author

JS/TS (vitest) coverage → 100% on apps/desktop

Addressed the coverage-evidence JS/TS gap (was statements 98.89% / branches 96.72% / functions 99.01% / lines 98.85%). apps/desktop now reports 100% statements, branches, functions, and lines across the included score-viewer files (verified locally with vitest run --coverage; 156 tests pass).

What changed:

  • scoreStorage.ts – added a test for the non-browser (typeof window === 'undefined') runtime, so the bridge fails closed with the stable "desktop app only" error instead of dereferencing window (line 27).
  • ScoreView.tsx – covered the previously-untested branches: the bridgeErrorDetail fallback (a rejection that is neither an Error nor a string), the superseded-read paths (a newer open winning over a stale resolve and a stale reject), and removing a score that is not the one currently open.
  • ScoreView.tsx / App.tsx – the if (!projectId) return / if (view) early-returns (lines 53/81/100 and 604/495) were unreachable dead branches: their triggering controls are already disabled, and React never dispatches onClick for a disabled button, so the guards could neither run nor be covered. Narrowed projectId/view at the call site instead (the onClick is only wired when present), removing the dead code with identical UX. Added a compact/mobile-nav test for App.

All tests are behavioral (each fails if the underlying logic breaks) — no vacuous asserts or coverage-ignore pragmas.

Note: the coverage-evidence job also had a separate Rust failure ("Rust files changed, but no root Cargo.toml was found"); that is handled by the accompanying ci(rust): add Cargo workspace manifest commit, not this JS/TS change.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

OpenCode cannot approve yet because required coverage evidence did not pass.

Review outcome

1. HIGH .github/workflows/opencode-review.yml:1 - Coverage evidence did not prove required test/docstring evidence

  • Problem: The required coverage-evidence job result was failure, so OpenCode cannot establish approval sufficiency for this head.

  • Root cause: Automated approval is only valid when the same-head coverage-evidence job proves supported repository test suites passed and configured docstring gates passed or were advisory, or reports not applicable because no supported source files or package manifests exist. Missing, failed, skipped, unavailable, or unsupported-tooling test evidence is a blocker.

  • Fix: Install or configure the repository test/docstring evidence tooling when source files or package manifests exist, rerun the current-head coverage-evidence job, and approve only after it reports success with required evidence or explicit no-source not-applicable evidence.

  • Regression test: Keep the approval branch checking needs.coverage-evidence.result == success before posting APPROVE, and publish REQUEST_CHANGES when coverage-evidence blocker states such as cancelled, skipped, failed, unsupported-tooling, or below-100 evidence are present.

  • Result: REQUEST_CHANGES

  • Reason: coverage-evidence result was failure, so required test/docstring evidence was not proven for current head 84b2ae4af9ad2bb2d8ded59fefa03408302da6e9.

  • Head SHA: 84b2ae4af9ad2bb2d8ded59fefa03408302da6e9

  • Workflow run: 28913378790

  • Workflow attempt: 1

Coverage evidence

Coverage Evidence

  • Head SHA: 84b2ae4af9ad2bb2d8ded59fefa03408302da6e9
  • Required test evidence: supported repository test suites must pass.
  • Required docstring evidence: repository-owned docstring gates must pass when configured; otherwise docstring coverage is advisory.

JavaScript/TypeScript dependencies (npm ci)


added 278 packages, and audited 281 packages in 7s

74 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
  • Result: PASS

Repository docstring coverage


> bandscope@0.1.3 check:python-docstrings
> sh -c 'cd services/analysis-engine && uv run ruff check src tests ../../scripts --select D100,D101,D102,D103,D104,D105,D106,D107'

Using CPython 3.12.3 interpreter at: /usr/bin/python3.12
Creating virtual environment at: .venv
   Building bandscope-analysis @ file:///home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine
Downloading pygments (1.2MiB)
Downloading yt-dlp (3.0MiB)
Downloading numba (3.6MiB)
Downloading llvmlite (53.7MiB)
Downloading soundfile (1.3MiB)
Downloading mypy (13.0MiB)
Downloading scikit-learn (8.5MiB)
Downloading scipy (33.6MiB)
Downloading numpy (15.8MiB)
Downloading ruff (10.7MiB)
 Downloaded soundfile
 Downloaded pygments
      Built bandscope-analysis @ file:///home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine
 Downloaded numba
 Downloaded ruff
 Downloaded yt-dlp
 Downloaded scikit-learn
 Downloaded numpy
 Downloaded llvmlite
 Downloaded scipy
 Downloaded mypy
Installed 44 packages in 96ms
All checks passed!
  • Result: PASS

JavaScript/TypeScript test coverage


> bandscope@0.1.3 test
> npm run test --workspaces --if-present && sh -c 'cd services/analysis-engine && uv run pytest tests --cov=src/bandscope_analysis --cov-report=term-missing --cov-fail-under=100' --coverage


> @bandscope/desktop@0.1.0 test
> node -e "require('node:fs').mkdirSync('coverage/.tmp', { recursive: true })" && vitest run --coverage


�[1m�[30m�[46m RUN �[49m�[39m�[22m �[36mv4.1.9 �[39m�[90m/home/runner/work/bandscope/bandscope/pr-head/apps/desktop�[39m
      �[2mCoverage enabled with �[22m�[33mv8�[39m

 �[32m✓�[39m src/features/score/ScoreViewer.test.tsx �[2m(�[22m�[2m12 tests�[22m�[2m)�[22m�[33m 753�[2mms�[22m�[39m
 �[32m✓�[39m src/features/score/ScoreView.test.tsx �[2m(�[22m�[2m19 tests�[22m�[2m)�[22m�[33m 1307�[2mms�[22m�[39m
 �[32m✓�[39m src/lib/export.test.ts �[2m(�[22m�[2m17 tests�[22m�[2m)�[22m�[32m 21�[2mms�[22m�[39m
 �[32m✓�[39m src/lib/analysis.test.ts �[2m(�[22m�[2m14 tests�[22m�[2m)�[22m�[32m 23�[2mms�[22m�[39m
 �[32m✓�[39m src/features/workspace/Workspace.test.tsx �[2m(�[22m�[2m11 tests�[22m�[2m)�[22m�[33m 1815�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m enables bass transcription from selected role metadata rather than role id text �[33m 430�[2mms�[22m�[39m
 �[32m✓�[39m src/i18n/index.test.ts �[2m(�[22m�[2m9 tests�[22m�[2m)�[22m�[32m 9�[2mms�[22m�[39m
 �[32m✓�[39m src/components/ui/ui-primitives.test.tsx �[2m(�[22m�[2m7 tests�[22m�[2m)�[22m�[32m 193�[2mms�[22m�[39m
 �[32m✓�[39m src/features/workspace/RoleSwitcher.test.tsx �[2m(�[22m�[2m4 tests�[22m�[2m)�[22m�[33m 380�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m renders the title and role options �[33m 313�[2mms�[22m�[39m
 �[32m✓�[39m src/features/workspace/SectionRoadmap.test.tsx �[2m(�[22m�[2m3 tests�[22m�[2m)�[22m�[33m 432�[2mms�[22m�[39m
 �[32m✓�[39m src/features/score/scoreStorage.test.ts �[2m(�[22m�[2m1 test�[22m�[2m)�[22m�[32m 6�[2mms�[22m�[39m
 �[32m✓�[39m src/App.test.tsx �[2m(�[22m�[2m59 tests�[22m�[2m)�[22m�[33m 15558�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m renders the rehearsal cockpit shell before analysis starts �[33m 627�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m renders the loaded song as a dark rehearsal command board �[33m 394�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m renders a rehearsal song structure timeline from real section ranges �[33m 422�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m does not show unavailable analysis metrics as detected facts �[33m 419�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m summarizes confidence from the lowest-confidence loaded section �[33m 383�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m selects a local audio source and starts a local-audio analysis job �[33m 390�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m starts an analysis job and renders the returned rehearsal result �[33m 751�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m shows the engine stage label and accessible progress value while analysis runs �[33m 343�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m animates rendered progress toward the running job target �[33m 494�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m uses translated progress labels when status payloads omit a progress label �[33m 418�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m falls back to failed progress copy when a pushed status has no error details �[33m 441�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m holds a terminal progress value immediately for pushed failed statuses �[33m 393�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m cleans up a late status subscription when the running view unmounts first �[33m 309�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m marks the active job failed when polling returns a malformed status �[33m 602�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m ignores malformed poll results after a pushed update changes the active job �[33m 685�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m ignores transport poll failures after a pushed update changes the active job �[33m 578�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m keeps handoff metadata tied to the source that produced the current result �[33m 817�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m shows a safe failed status when the job poll returns an error �[33m 468�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m falls back to a generic failure message when the engine omits details �[33m 458�[2mms�[22m�[39m
     �[33m�[2m✓�[22m�[39m keeps polling the active job when one polling request rejects �[33m 730�[2mms�[22m�[39m

�[2m Test Files �[22m �[1m�[32m11 passed�[39m�[22m�[90m (11)�[39m
�[2m      Tests �[22m �[1m�[32m156 passed�[39m�[22m�[90m (156)�[39m
�[2m   Start at �[22m 11:22:40
�[2m   Duration �[22m 18.27s�[2m (transform 988ms, setup 1.34s, import 3.71s, tests 20.50s, environment 11.06s)�[22m

�[34m % �[39m�[2mCoverage report from �[22m�[33mv8�[39m
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |     100 |      100 |     100 |     100 |                   
 src               |     100 |      100 |     100 |     100 |                   
  App.tsx          |     100 |      100 |     100 |     100 |                   
 ...features/score |     100 |      100 |     100 |     100 |                   
  ScoreView.tsx    |     100 |      100 |     100 |     100 |                   
  ScoreViewer.tsx  |     100 |      100 |     100 |     100 |                   
  scoreStorage.ts  |     100 |      100 |     100 |     100 |                   
 src/i18n          |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/lib           |     100 |      100 |     100 |     100 |                   
  export.ts        |     100 |      100 |     100 |     100 |                   
-------------------|---------|----------|---------|---------|-------------------

> @bandscope/shared-types@0.1.0 test
> vitest run --coverage


�[1m�[30m�[46m RUN �[49m�[39m�[22m �[36mv4.1.9 �[39m�[90m/home/runner/work/bandscope/bandscope/pr-head/packages/shared-types�[39m
      �[2mCoverage enabled with �[22m�[33mv8�[39m

 �[32m✓�[39m test/index.test.ts �[2m(�[22m�[2m15 tests�[22m�[2m)�[22m�[32m 73�[2mms�[22m�[39m

�[2m Test Files �[22m �[1m�[32m1 passed�[39m�[22m�[90m (1)�[39m
�[2m      Tests �[22m �[1m�[32m15 passed�[39m�[22m�[90m (15)�[39m
�[2m   Start at �[22m 11:22:59
�[2m   Duration �[22m 475ms�[2m (transform 126ms, setup 0ms, import 160ms, tests 73ms, environment 0ms)�[22m

�[34m % �[39m�[2mCoverage report from �[22m�[33mv8�[39m
----------|---------|----------|---------|---------|-------------------
File      | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
----------|---------|----------|---------|---------|-------------------
All files |     100 |      100 |     100 |     100 |                   
 index.ts |     100 |      100 |     100 |     100 |                   
----------|---------|----------|---------|---------|-------------------
============================= test session starts ==============================
platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0
rootdir: /home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine
configfile: pyproject.toml
plugins: cov-7.0.0
collected 433 items

tests/test_activity.py ........                                          [  1%]
tests/test_anchors.py ....                                               [  2%]
tests/test_api.py .........................                              [  8%]
tests/test_chord_recognizer.py ....................                      [ 13%]
tests/test_chords.py .........................                           [ 18%]
tests/test_cli.py .................                                      [ 22%]
tests/test_extractor.py ......                                           [ 24%]
tests/test_health.py .                                                   [ 24%]
tests/test_pipeline_integration.py .........                             [ 26%]
tests/test_pitch_tracker.py ...............                              [ 30%]
tests/test_priority.py ...........                                       [ 32%]
tests/test_ranges.py ...................                                 [ 36%]
tests/test_release_asset_selection.py ........                           [ 38%]
tests/test_release_metadata.py .......                                   [ 40%]
tests/test_release_packaging.py .........                                [ 42%]
tests/test_roles.py .......                                              [ 44%]
tests/test_roles_ml.py ...                                               [ 44%]
tests/test_segmenter.py .....................                            [ 49%]
tests/test_separation.py ..................................              [ 57%]
tests/test_supply_chain_policy.py ...................................... [ 66%]
........................................................................ [ 82%]
.........................................                                [ 92%]
tests/test_temporal.py .........                                         [ 94%]
tests/test_transcription.py ...                                          [ 95%]
tests/test_tuning.py .....                                               [ 96%]
tests/test_youtube.py ................                                   [100%]

=============================== warnings summary ===============================
tests/test_pipeline_integration.py::test_pipeline_without_detected_sections_falls_back
tests/test_roles.py::test_role_extractor_falls_back_when_activity_detection_fails
  /home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine/.venv/lib/python3.12/site-packages/librosa/core/pitch.py:103: UserWarning: Trying to estimate tuning from empty frequency set.
    return pitch_tuning(

tests/test_roles.py::test_role_extractor_falls_back_when_activity_detection_fails
  /home/runner/work/bandscope/bandscope/pr-head/services/analysis-engine/.venv/lib/python3.12/site-packages/librosa/core/spectrum.py:266: UserWarning: n_fft=2048 is too large for input signal of length=100
    warnings.warn(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================ tests coverage ================================
_______________ coverage: platform linux, python 3.12.3-final-0 ________________

Name                                                   Stmts   Miss  Cover   Missing
------------------------------------------------------------------------------------
src/bandscope_analysis/__init__.py                         3      0   100%
src/bandscope_analysis/api.py                            571      0   100%
src/bandscope_analysis/chords/__init__.py                  5      0   100%
src/bandscope_analysis/chords/analyzer.py                116      0   100%
src/bandscope_analysis/chords/capo.py                     10      0   100%
src/bandscope_analysis/chords/chord_recognizer.py        192      0   100%
src/bandscope_analysis/chords/model.py                    15      0   100%
src/bandscope_analysis/cli.py                             68      0   100%
src/bandscope_analysis/health.py                           7      0   100%
src/bandscope_analysis/ranges/__init__.py                  4      0   100%
src/bandscope_analysis/ranges/analyzer.py                 77      0   100%
src/bandscope_analysis/ranges/model.py                    19      0   100%
src/bandscope_analysis/ranges/pitch_tracker.py            54      0   100%
src/bandscope_analysis/roles/__init__.py                   4      0   100%
src/bandscope_analysis/roles/activity.py                  59      0   100%
src/bandscope_analysis/roles/extractor.py                118      0   100%
src/bandscope_analysis/roles/model.py                     58      0   100%
src/bandscope_analysis/roles/priority.py                  13      0   100%
src/bandscope_analysis/roles/tuning.py                    11      0   100%
src/bandscope_analysis/sections/__init__.py                6      0   100%
src/bandscope_analysis/sections/anchors.py                 5      0   100%
src/bandscope_analysis/sections/extractor.py              38      0   100%
src/bandscope_analysis/sections/model.py                  35      0   100%
src/bandscope_analysis/sections/segmenter.py             140      0   100%
src/bandscope_analysis/sections/utils.py                   8      0   100%
src/bandscope_analysis/separation/__init__.py              4      0   100%
src/bandscope_analysis/separation/audio_separator.py     145      0   100%
src/bandscope_analysis/separation/model.py                31      0   100%
src/bandscope_analysis/separation/separator.py            34      0   100%
src/bandscope_analysis/temporal/__init__.py                3      0   100%
src/bandscope_analysis/temporal/analyzer.py               49      0   100%
src/bandscope_analysis/temporal/model.py                   9      0   100%
src/bandscope_analysis/transcription/__init__.py           2      0   100%
src/bandscope_analysis/transcription/api.py               11      0   100%
src/bandscope_analysis/youtube.py                         81      0   100%
------------------------------------------------------------------------------------
TOTAL                                                   2005      0   100%
Required test coverage of 100% reached. Total coverage: 100.00%
================== 433 passed, 3 warnings in 92.75s (0:01:32) ==================

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (36 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (36 files)"]
  R1 --> V1["required checks"]
Loading

@opencode-agent opencode-agent Bot disabled auto-merge July 11, 2026 11:46

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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/core/src/lib.rs, apps/desktop/src/App.test.tsx, apps/desktop/src/App.tsx, apps/desktop/src/features/score/ScoreView.test.tsx, apps/desktop/src/features/score/ScoreView.tsx, and 8 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/core/src/lib.rs 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: All checks pass, comprehensive tests, and no unresolved issues.
  • Head SHA: fdf400358ea6c8d3b3fdeaf788c12f0bc50e2639
  • Workflow run: 29177751296
  • Workflow attempt: 1

Changed-File Evidence Map

flowchart LR
  PR["PR changed files"] --> Evidence["OpenCode bounded evidence"]
  Evidence --> S1["Changed file (13 files)"]
  S1 --> I1["repository behavior"]
  I1 --> R1["Review risk: Changed file (13 files)"]
  R1 --> V1["required checks"]
Loading

@seonghobae seonghobae dismissed stale reviews from github-actions[bot] and github-actions[bot] July 12, 2026 03:18

Stale automated coverage-evidence review for superseded head 489a116; current head fdf4003 has passing coverage-evidence and OpenCode approval.

@seonghobae seonghobae merged commit 6f028ed into develop Jul 12, 2026
38 checks passed
@seonghobae seonghobae deleted the feat/score-pdf-wire branch July 12, 2026 03:19
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.

1 participant