Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
166 commits
Select commit Hold shift + click to select a range
0eeec7b
feat(workspace): loop tonight's first section from the map
seonghobae Aug 20, 2026
d329fbc
test(player): require explicit audio and one-shot start authority
seonghobae Aug 20, 2026
aa43a84
fix(player): bind starts to fresh audio-authorized requests
seonghobae Aug 20, 2026
58cf249
test(workspace): bind role loop action to local audio
seonghobae Aug 20, 2026
de00811
fix(workspace): gate role loop by local audio authority
seonghobae Aug 21, 2026
80265c2
test(workspace): reject duplicate section id selection aliasing
seonghobae Aug 21, 2026
28e82d1
fix(workspace): select loop sections by renderer position
seonghobae Aug 21, 2026
1fb2ad1
test(workspace): revoke loop authority fail closed
seonghobae Aug 21, 2026
6675a83
fix(workspace): stop loop when local audio is revoked
seonghobae Aug 21, 2026
9e8f605
test(workspace): reject malformed loop section entries
seonghobae Aug 21, 2026
705b0f7
fix(workspace): validate loop sections before id lookup
seonghobae Aug 21, 2026
65da84c
test(workspace): preserve paused count-in progress
seonghobae Aug 21, 2026
5a839da
fix(workspace): resume paused count-in before looping
seonghobae Aug 21, 2026
eafc121
test(workspace): disable duplicate start during active loop timing
seonghobae Aug 21, 2026
65c73c8
fix(workspace): disable start while loop timing is active
seonghobae Aug 21, 2026
0476b6d
docs: use analysis-engine working directory for Python checks
seonghobae Aug 21, 2026
78527c7
docs(workspace): describe finite non-negative transport guard
seonghobae Aug 21, 2026
ff20581
test(workspace): scope timeline range assertion
seonghobae Aug 21, 2026
affa8da
test(workspace): restore focused App integration coverage
seonghobae Aug 21, 2026
1fc6317
fix(workspace): structure loop selector label fields
seonghobae Aug 21, 2026
6a58bad
test(workspace): preserve live loop across metadata updates
seonghobae Aug 21, 2026
b100551
fix(workspace): preserve live loop on unrelated song updates
seonghobae Aug 21, 2026
646f470
docs: align Python verification working directory
seonghobae Aug 21, 2026
96a191a
test(i18n): name selected-section loop action honestly
seonghobae Aug 22, 2026
b4a5a0d
fix(i18n): name selected-section loop action honestly
seonghobae Aug 22, 2026
3de2b62
fix(i18n): localize selected-section loop action
seonghobae Aug 22, 2026
110ca67
test(workspace): follow selected-section loop copy
seonghobae Aug 22, 2026
ab660b6
test(i18n): keep rehearsal clock copy honest
seonghobae Aug 22, 2026
139e820
fix(i18n): describe timer-only loop honestly
seonghobae Aug 22, 2026
7617f0d
fix(i18n): localize rehearsal clock honestly
seonghobae Aug 22, 2026
c9fb0f6
test(workspace): follow rehearsal clock copy
seonghobae Aug 22, 2026
0512f30
test(player): align loop assertions with shipped next-action copy
seonghobae Aug 23, 2026
f5f753d
test(player): verify revocation after the loop advances
seonghobae Aug 23, 2026
7c1b497
test(player): advance the live-loop clock before revocation
seonghobae Aug 23, 2026
ad2d219
test(workspace): pin loop section descriptor authority
seonghobae Aug 25, 2026
6b8abe5
fix(workspace): snapshot loop section authority
seonghobae Aug 25, 2026
0c7b293
test(workspace): pin loop picker snapshot authority
seonghobae Aug 25, 2026
3d5ac83
fix(workspace): snapshot song loop windows
seonghobae Aug 25, 2026
f420ffe
fix(workspace): render snapshotted loop windows
seonghobae Aug 25, 2026
2a8a96b
docs(changelog): describe map-clock snapshot boundary
seonghobae Aug 25, 2026
d8e0c4f
docs(changelog): preserve historical release text
seonghobae Aug 25, 2026
0a8d316
Merge branch 'develop' into feat/rehearsal-player-first-section-loop
seonghobae Aug 25, 2026
d2f9a2f
Merge remote-tracking branch 'origin/develop' into HEAD
seonghobae Aug 26, 2026
cfcfa1e
fix(desktop): bound rehearsal loop inputs
seonghobae Aug 28, 2026
6de3f55
fix(a11y): announce rehearsal transport status
seonghobae Aug 29, 2026
7a59047
fix(player): restart externally requested loops
seonghobae Aug 29, 2026
2803109
feat(player): play real audio section loops
seonghobae Aug 29, 2026
c911b41
fix(player): harden cross-platform audio loop boundaries
seonghobae Aug 29, 2026
5187e03
fix(player): cap long loop boundary timers
seonghobae Aug 29, 2026
6a9c576
test(player): reproduce unusable audio authority start
seonghobae Aug 29, 2026
2ff2615
fix(player): require playable media authority
seonghobae Aug 29, 2026
d53758b
test(player): use real media authority in loop tests
seonghobae Aug 29, 2026
8ab7e7d
feat(player): add bounded rehearsal playback rates
seonghobae Aug 29, 2026
9491543
fix(player): share playable audio authority
seonghobae Aug 29, 2026
308c802
Merge real-audio loop authority fix
seonghobae Aug 29, 2026
e517a98
fix(player): align loop timing with playback rate
seonghobae Aug 29, 2026
39927fb
feat(player): filter loops by rehearsal role
seonghobae Aug 29, 2026
43698a6
fix(player): preserve role loop selection
seonghobae Aug 29, 2026
2490ce2
feat(player): add keyboard section cue navigation
seonghobae Aug 29, 2026
04396d9
fix(player): follow focused cue during arrow navigation
seonghobae Aug 29, 2026
9c61a80
feat(player): allow manual cue boundary corrections
seonghobae Aug 29, 2026
8a5160a
feat(player): add bounded cue seeking
seonghobae Aug 29, 2026
81efbe3
fix(player): preserve cue editor focus
seonghobae Aug 29, 2026
c37e589
feat(player): add safe transport shortcuts
seonghobae Aug 29, 2026
dbd15a9
fix(player): ignore repeated transport shortcuts
seonghobae Aug 29, 2026
4d13e56
feat(player): add safe transport shortcuts
seonghobae Aug 30, 2026
b8c17fa
fix(player): scope transport shortcuts safely
seonghobae Aug 30, 2026
c6fb104
feat(player): allow manual cue boundary corrections
seonghobae Aug 30, 2026
e279396
feat(player): filter loops by rehearsal role
seonghobae Aug 30, 2026
aa1c36f
fix(player): ignore modified escape shortcuts
seonghobae Aug 30, 2026
b8a4e1c
fix(player): preserve duplicate cue identity
seonghobae Aug 30, 2026
b96e454
fix(player): preserve cue selection across edits
seonghobae Aug 30, 2026
82ec453
feat(player): play real audio section loops
seonghobae Aug 30, 2026
d92ad78
feat(player): add keyboard section cue navigation
seonghobae Aug 30, 2026
b8b4241
fix(player): preserve count-in timing across rate changes
seonghobae Aug 30, 2026
826b1df
docs: align agent guidance with playback rate
seonghobae Aug 30, 2026
d99c935
test(player): cover repeated count-in rate changes
seonghobae Aug 30, 2026
435cc02
feat(player): add bounded rehearsal playback rates
seonghobae Aug 30, 2026
7c24f59
fix(player): preserve scroll-region Space behavior
seonghobae Aug 30, 2026
b7fd151
Merge latest rehearsal-player base
seonghobae Aug 30, 2026
e29739f
feat(player): add bounded cue seeking
seonghobae Aug 30, 2026
a802ace
test(player): preserve cumulative count-in progress
seonghobae Sep 1, 2026
c207737
fix(player): preserve cumulative count-in progress
seonghobae Sep 1, 2026
8ae5cff
style(player): preserve canonical source formatting
seonghobae Sep 1, 2026
a8b7170
test(player): reproduce media lifecycle interruptions
seonghobae Sep 1, 2026
7bba2de
fix(player): distinguish media interruption from failure
seonghobae Sep 1, 2026
3f4e1f4
test(player): reproduce stale play rejection race
seonghobae Sep 1, 2026
6467f57
fix(player): ignore superseded play rejections
seonghobae Sep 1, 2026
a34f090
style(player): restore canonical trailing newline
seonghobae Sep 1, 2026
08a9900
test(player): reproduce hidden playback-scope preparation error
seonghobae Sep 1, 2026
c5e1acd
fix(player): preserve safe playback preparation failure
seonghobae Sep 1, 2026
598b8c2
test(player): scope timer-only copy guard to no-audio state
seonghobae Sep 1, 2026
f1f31fc
docs(player): distinguish media playback from decode ownership
seonghobae Sep 1, 2026
fe4d7ee
docs(player): document scoped Tauri media playback boundary
seonghobae Sep 1, 2026
7f3df1a
test(player): reproduce Tauri string rejection boundary
seonghobae Sep 1, 2026
3ee7989
fix(player): surface allowlisted Tauri string errors
seonghobae Sep 1, 2026
1f1c7ac
test(player): reproduce stale boundary snapshot edit
seonghobae Sep 1, 2026
8cdb2d0
chore(player): keep snapshot regression out until causal fix lands
seonghobae Sep 1, 2026
0a6e6c1
test(player): reject stale boundary edits
seonghobae Sep 1, 2026
a2fa4cc
fix(player): fail closed on stale boundary snapshots
seonghobae Sep 1, 2026
a215330
test(player): round adjusted tempo in rehearsal copy
seonghobae Sep 1, 2026
797bf39
fix(player): round adjusted rehearsal tempo
seonghobae Sep 1, 2026
86fbda6
test(player): align cumulative count-in timing
seonghobae Sep 1, 2026
302b53c
test(player): preserve renderer-owned roadmap focus contract
seonghobae Sep 1, 2026
b6d01a5
fix(player): preserve renderer-owned roadmap focus targets
seonghobae Sep 1, 2026
4534e84
test(player): require audible transport count-in
seonghobae Sep 2, 2026
e21a6cc
feat(player): sound transport count-in beats
seonghobae Sep 2, 2026
07c001c
test(player): cover audible count-in engine
seonghobae Sep 2, 2026
a3bc430
refactor(player): bound count-in node cleanup
seonghobae Sep 2, 2026
effb237
feat(player): integrate audible count-in
seonghobae Sep 2, 2026
dca1cfa
test(player): require count-in context disposal
seonghobae Sep 2, 2026
2aa27c6
fix(player): dispose count-in audio context
seonghobae Sep 2, 2026
dfb8519
test(player): cover count-in disposal during pending resume
seonghobae Sep 2, 2026
5d5b102
fix(player): dispose count-in context on unmount
seonghobae Sep 2, 2026
cb9c347
test(player): reproduce Strict Mode count-in disposal regression
seonghobae Sep 2, 2026
f260547
fix(player): preserve count-in through Strict Mode effect replay
seonghobae Sep 2, 2026
c7e53f7
test(workspace): expose missing loop roadmap selection wiring
seonghobae Sep 2, 2026
27a4a58
chore(ci): stage bounded PR 971 roadmap wiring repair
seonghobae Sep 2, 2026
762506b
chore(ci): run bounded PR 971 roadmap wiring repair
seonghobae Sep 2, 2026
75328c3
refactor(ci): remove self-modifying player repair workflow
seonghobae Sep 2, 2026
6a537f3
refactor(ci): remove temporary source repair helper
seonghobae Sep 2, 2026
cafe635
fix(workspace): project player loop selection to roadmap
seonghobae Sep 2, 2026
dd8e136
fix(player): publish admitted loop selection to workspace
seonghobae Sep 2, 2026
b449fa9
test(workspace): isolate duplicate-section chord edits
seonghobae Sep 2, 2026
375ede4
fix(workspace): scope chord edits to renderer occurrence
seonghobae Sep 2, 2026
78a4de9
test(player): reject loop boundaries beyond loaded audio
seonghobae Sep 2, 2026
d3abeef
fix(player): bind loop authority to loaded media duration
seonghobae Sep 2, 2026
e106757
test(player): reject replay of uncovered start request
seonghobae Sep 2, 2026
c9830a0
fix(player): consume rejected uncovered start request
seonghobae Sep 2, 2026
c90d855
test(player): require revocable native playback authority
seonghobae Sep 2, 2026
f6fc519
feat(player): add revocable native playback protocol
seonghobae Sep 2, 2026
ae8aa46
test(player): pin opaque playback authority handle
seonghobae Sep 2, 2026
d0112f3
fix(player): consume opaque native playback authority
seonghobae Sep 2, 2026
784ff40
fix(player): expose revocable playback authority helper
seonghobae Sep 2, 2026
c2b0802
fix(player): disable additive asset protocol scope
seonghobae Sep 2, 2026
e9c4c8f
fix(player): scope CSP to revocable playback protocol
seonghobae Sep 2, 2026
4711542
fix(player): wire revocable native playback authority
seonghobae Sep 2, 2026
3e0b6d7
fix(player): preserve score root while wiring playback
seonghobae Sep 2, 2026
d057566
test(player): reject unbounded full-recording playback reads
seonghobae Sep 2, 2026
8716377
fix(player): bound unranged playback responses
seonghobae Sep 2, 2026
788df68
test(playback): reject same-size source replacement
seonghobae Sep 3, 2026
2ed840b
fix(playback): bind authority to native source identity
seonghobae Sep 3, 2026
9fb7863
test(playback): reject same-inode source mutation
seonghobae Sep 3, 2026
7c5adfd
fix(playback): revoke mutated native source
seonghobae Sep 3, 2026
3fda1a7
test(player): prove playback revocation is linearizable
seonghobae Sep 3, 2026
d5dc2fc
fix(player): serialize playback authority revocation
seonghobae Sep 3, 2026
22d41c1
test(player): use renderer-safe playback authority fixture
seonghobae Sep 3, 2026
00daa49
test(player): require roadmap focus for selected loop
seonghobae Sep 3, 2026
8c49b47
fix(player): focus selected roadmap occurrence
seonghobae Sep 3, 2026
161c954
test(player): preserve arrow-key focus across roadmap sync
seonghobae Sep 3, 2026
90e74b4
fix(player): preserve keyboard focus during roadmap sync
seonghobae Sep 3, 2026
a7831a6
test(player): require timeline-to-transport selection
seonghobae Sep 3, 2026
069fa5a
fix(player): route timeline selection through transport
seonghobae Sep 3, 2026
ee6e32f
test(workspace): cover timeline selection outside active role
seonghobae Sep 3, 2026
73c3b9c
fix(workspace): honor full-form timeline selection across role filters
seonghobae Sep 3, 2026
449300f
test(workspace): preserve timeline roadmap focus succession
seonghobae Sep 3, 2026
2535f71
fix(workspace): preserve roadmap focus on timeline actions
seonghobae Sep 3, 2026
c58eb3b
test(player): retain role on rejected timeline section
seonghobae Sep 3, 2026
f395479
fix(player): preserve role on inadmissible timeline selection
seonghobae Sep 3, 2026
eb4fe64
test(ui): reject false-active bass transcription control
seonghobae Sep 3, 2026
e1d850e
fix(ui): stop advertising unavailable transcription as active
seonghobae Sep 3, 2026
175e5f7
test(ui): align bass transcription contract with actual capability
seonghobae Sep 3, 2026
803019e
test(ui): consolidate transcription regression coverage
seonghobae Sep 3, 2026
d1ca68d
Merge branch 'develop' into feat/rehearsal-player-first-section-loop
opencode-agent[bot] Sep 4, 2026
9c1b20e
Merge branch 'develop' into feat/rehearsal-player-first-section-loop
seonghobae Sep 4, 2026
09bedd8
fix(player): remove stale Tauri lock orphan
seonghobae Sep 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# AGENTS.md

## Project overview

- BandScope is a local-first desktop app for rehearsal prep: a practical song view with likely harmony by section and by instrument or vocal role, form and groove cues, stems, playable ranges, simplification guidance, transposition or setup cues, part-overlap cues, visible confidence, and rehearsal priorities.
- The ready workspace must name a next rehearsal action. Tonight's first playable section loop (count-in, pause, stop) is the #961 transport slice; #1063 adds bounded pitch-preserving playback rate, while stem playback remains later work.
- Authoritative delivery rules live in `ARCHITECTURE.md`, `docs/plans/`, and the root verification scripts.
- Brand, tone, UX copy, and prioritization rules live in `docs/brand-story.md` and must be applied to PRDs, TRDs, UI copy, onboarding, empty states, and error messages.
- App security rules live in `docs/security/app-security.md` and must be applied to file handling, URL intake, subprocesses, IPC, WebView usage, model loading, updates, logging, cache handling, and export behavior.
Expand All @@ -12,15 +14,19 @@
- Repository governance and Gitflow rules live in `docs/repository/governance.md`, `docs/repository/bootstrap-plan.md`, and `docs/repository/gitflow.md`.

## Security workflow

- Before writing PRDs, TRDs, UX copy, architecture changes, or implementation plans that touch risky boundaries, read `docs/security/app-security.md`.
- If a task touches files, URLs, subprocesses, ffmpeg or native tools, WebView, local backend or IPC, updates, model downloads, project formats, logs, telemetry, or exports, the result must include `Security Notes`.
- `Security Notes` should cover untrusted inputs, trust boundaries, allowlists or validation, safe failure, logging/privacy impact, and test points.

<!-- BEGIN cwl-agent-guidance -->

## Agent guidance (CWL governance)

This section applies to any agent (Claude, Codex, Cursor, opencode, ...) working in this repo.

### Security & review gate

- Every PR runs a central **Security Scan** required gate: `osv-scan` + `dependency-review` (diff-scoped) and `trivy-fs` (repo-wide, CRITICAL/HIGH, fixable). It runs on every PR base, **including stacked PRs**. Gating is by the Security Scan **job result**.
- A failing `trivy-fs` is a **REAL finding, not a flake.** Read the job log (it prints each finding's rule id / severity / file) or the run's SARIF results, then **remediate**:
- This repo ships **no Dockerfile and no k8s manifests**, so findings are almost always dependency vulns. Bump the offending package in the relevant lockfile — `apps/desktop/src-tauri/Cargo.lock` (Rust/Tauri), `package-lock.json` (Node), or `uv.lock` / `services/analysis-engine` (Python).
Expand All @@ -30,37 +36,45 @@ This section applies to any agent (Claude, Codex, Cursor, opencode, ...) working
- The org `code_scanning` ruleset is intentionally **CodeQL-only** (multiple code-scanning tools can't converge on one PR ref). Do **not** add tools to the `code_scanning` rule; enforcement stays on the Security Scan job.

### Code exploration

- This repo has **no `.codegraph/` index**, so use normal search (grep/find/ripgrep) to locate and understand code. If a `.codegraph/` directory is later added at the repo root, prefer CodeGraph (`codegraph explore "<query>"`, or the code-review-graph MCP tools) **before** grep/find — it surfaces callers/callees/impact that text search misses.

<!-- END cwl-agent-guidance -->

## Supply chain workflow

- Before adding or changing dependencies, GitHub Actions, bundled binaries, or model artifacts, read `docs/security/dependency-policy.md`.
- New direct dependencies must include admission rationale covering purpose, dependency class, alternatives, maintainer trust, license fit, known security issues, transitive footprint, and BandScope release risk.
- Lockfiles, dependency review, audit, SBOM generation, and supplemental component inventory are mandatory and must not be skipped or loosened.
- Supply-chain work is not complete without evidence: workflow paths, required check names for `main` and `develop`, SBOM format and retention, Dependabot baseline state, and the bundled-binary/model inventory path.
- Use `FAILED` when repo-controlled supply-chain artifacts are missing; use `BLOCKED` only when GitHub permission, auth, network, or platform capability prevents enforcement.

## Cross-platform build workflow

- Before changing CI, packaging, release flows, or native desktop build settings, read `docs/security/cross-platform-build-policy.md`.
- Windows and macOS builds are required security controls for `develop`, `main`, and release validation.
- Protected-branch build checks for Windows and macOS must not be removed, downgraded, or treated as optional.

## GitHub bootstrap workflow

- Before declaring a GitHub task blocked, read `docs/workflow/github-bootstrap-execution-policy.md`.
- Missing local git state, missing GitHub repo, missing `main`, missing `develop`, or missing initial workflows are bootstrap conditions, not default blockers.
- For GitHub tasks, only use `BLOCKED` when the failure is caused by missing GitHub permissions, missing auth, missing network access, or platform-level feature limits.
Comment thread
seonghobae marked this conversation as resolved.

## Setup commands

- Node: `npm install`
- Python: `uv sync --project services/analysis-engine --group dev`

## Build / Test commands

- Full harness check: `./scripts/harness/quickcheck.sh`
- Frontend tests: `npm run test --workspaces --if-present`
- Python tests: `uv run --project services/analysis-engine pytest --cov=src/bandscope_analysis --cov-report=term-missing --cov-fail-under=100`
- Typecheck: `npm run typecheck --workspaces --if-present && uv run --project services/analysis-engine mypy src`
- Python tests: `uv run --directory services/analysis-engine pytest --cov=src/bandscope_analysis --cov-report=term-missing --cov-fail-under=100`
- Typecheck: `npm run typecheck --workspaces --if-present && uv run --directory services/analysis-engine mypy src`

## Architecture references

- `ARCHITECTURE.md`
- `docs/engineering/acceptance-criteria.md`
- `docs/engineering/harness-engineering.md`
Expand All @@ -80,13 +94,15 @@ This section applies to any agent (Claude, Codex, Cursor, opencode, ...) working
- `docs/plans/2026-03-10-bandscope-harness.md`

## Code style

- Keep UI and analysis engine decoupled through shared contracts.
- Prefer minimal, test-first changes for production code.
- Prefer practical, friendly, rehearsal-first wording over academic or authority-heavy language.
- Do not reduce the product to a chord analyzer when form, timing, player coordination, playable ranges, simplification, and setup cues are the real rehearsal blockers.
- Do not frame usability as a reason to accept weak analysis quality; BandScope should aim for both easy use and high accuracy.

## Safety

- Do not add network-dependent runtime paths for local analysis.
- Treat YouTube import as policy-constrained and fallback-friendly.
- Treat files, URLs, metadata, model artifacts, and project files as untrusted input.
Expand Down
3 changes: 2 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ARCHITECTURE.md

Last updated: 2026-03-11
Last updated: 2026-08-21

## Brand source

Expand Down Expand Up @@ -86,6 +86,7 @@ Last updated: 2026-03-11
- simplification, transposition, capo, tuning, or setup cues where applicable
- role-specific rehearsal priorities and confidence flags
- cue-sheet or chart-style exports that summarize the analysis in rehearsal-friendly form
- a local rehearsal transport that arms the first valid section loop, counts in at the admitted tempo, and names the next play/pause/stop action without pretending disk audio is playing when no local song is loaded

## Confidence, edits, and provenance

Expand Down
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

### Added

- Tonight's rehearsal player now arms the first valid section loop, runs a tempo count-in, and plays validated local audio through a scoped Tauri asset URL in the browser media element; the React layer does not decode audio bytes itself, and admitted section timing and picker copy use the same descriptor-snapshotted transport window.
- Name tonight's first playable range on the ready rehearsal map and tell the player to check that span on their instrument before the section.
- Display the analyzed song tempo (BPM) as a badge in the rehearsal workspace.
- Move between playable section cues with Left and Right Arrow and keep the selected cue focused.
- 각 합주 역할(Role)별 개인 연습 진행도를 0~100% 범위로 기록 및 시각화할 수 있는 연습 진척도(`practiceProgress`) 트래커 기능 추가. UI 컨트롤(슬라이더 및 +/- 버튼)과 한/영 다국어 지원 포함.

### Changed
Expand All @@ -15,6 +17,8 @@

### Fixed

- Kept the rehearsal player section picker aligned with the selected player or
vocal role while preserving the full song-form roadmap.
- Upgraded the local score PDF parser to `pdfjs-dist` 6.2.108, pinned Undici 7.29.0 across the workspace, and constrained PDF loading to copied in-memory bytes with a same-origin bundled worker and npm-generated lock provenance.

## [0.1.3] - 2026-04-29
Expand Down Expand Up @@ -49,7 +53,6 @@
- Resolve npm audit vulnerabilities
- Fix ruff import sorting and formatting errors
- Add missing docstrings to tests
- Fix test configuration and typing issues

## [0.1.0] - 2026-03-27

Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ npm --workspace @bandscope/desktop exec vitest run src/lib/export.test.ts # on
npm run dev --workspace @bandscope/desktop # Vite dev server (browser fallback mode)
npm run storybook --workspace @bandscope/desktop # component workbench

uv run --project services/analysis-engine pytest tests/test_chords.py # one Python test file (no coverage gate)
uv run --project services/analysis-engine pytest --cov=src/bandscope_analysis --cov-report=term-missing --cov-fail-under=100 # full Python gate
uv run --directory services/analysis-engine pytest tests/test_chords.py # one Python test file (no coverage gate)
uv run --directory services/analysis-engine pytest --cov=src/bandscope_analysis --cov-report=term-missing --cov-fail-under=100 # full Python gate
```

## Architecture
Expand All @@ -51,7 +51,7 @@ BandScope is a local-first desktop app for rehearsal prep: it turns a song into

Three layers, decoupled through shared contracts:

- `apps/desktop` — Tauri 2 + Vite + React 19 shell (Tailwind 4, Base UI, Storybook). Feature screens live in `src/features/` (home, workspace, chords, ranges, player, settings). The ready workspace names tonight's first playable range and the next instrument check. `src/lib/analysis.ts` and `src/lib/job_runner.ts` call typed Tauri IPC commands, with a browser fallback that serves demo data when not running inside Tauri.
- `apps/desktop` — Tauri 2 + Vite + React 19 shell (Tailwind 4, Base UI, Storybook). Feature screens live in `src/features/` (home, workspace, chords, ranges, player, settings). The workspace `RehearsalPlayer` owns tonight's first section loop and count-in clock. It does not decode local audio bytes in React; after native admission, its browser media element plays the authorized source through a scoped Tauri asset URL. The ready workspace also names tonight's first playable range and the next instrument check. `src/lib/analysis.ts` and `src/lib/job_runner.ts` call typed Tauri IPC commands, with a browser fallback that serves demo data when not running inside Tauri.
- `apps/desktop/src-tauri/src/main.rs` — the Rust orchestration boundary. Tauri commands (`start_analysis_job`, `get_analysis_job_status`, `select_local_audio_source`, `import_youtube_url`) validate untrusted input (project IDs, file paths, URLs) and spawn the Python engine as a subprocess. There is no loopback HTTP listener and no network path for local analysis.
- `services/analysis-engine` — Python package `bandscope_analysis` (librosa/numpy). Entry point `cli.py` reads a JSON job request on stdin and prints a structured job-status JSON envelope on stdout (`--progress-jsonl` streams progress lines). `api.py` orchestrates the pipeline across the `separation`, `sections`, `roles`, `chords`, `ranges`, `temporal`, `transcription`, and `youtube` modules.

Expand Down
29 changes: 26 additions & 3 deletions apps/desktop/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

mod playback_protocol;

use bandscope_desktop_core::*;
use playback_protocol::{playback_authority_uri, PlaybackAuthority, PLAYBACK_SCHEME};
use rfd::FileDialog;
use serde_json::{json, Value};
use std::{
io::{BufRead, BufReader, Read, Write},
path::{Path, PathBuf},
process::{Command, Stdio},
sync::{atomic::Ordering, mpsc},
sync::{atomic::Ordering, mpsc, Arc},
thread,
time::Instant,
};
Expand Down Expand Up @@ -317,6 +320,15 @@ fn lookup_bootstrap_source(
.ok_or_else(|| "Analysis job source was not found. Choose local audio again.".to_string())
}

/// Return a renderer-safe bootstrap while retaining the native source only in
/// BandScope's process-local analysis and playback authority stores.
fn renderer_bootstrap_summary(
mut summary: ProjectBootstrapSummaryPayload,
) -> Result<ProjectBootstrapSummaryPayload, String> {
summary.source.source_path = playback_authority_uri(&summary.project_id)?;
Ok(summary)
}

fn drain_analysis_status_updates(
state: &AppState,
app: &tauri::AppHandle<impl Runtime>,
Expand Down Expand Up @@ -637,6 +649,7 @@ fn get_analysis_job_status(job_id: String, state: tauri::State<'_, AppState>) ->
fn select_local_audio_source(
app: tauri::AppHandle<impl Runtime>,
state: tauri::State<'_, AppState>,
playback_authority: tauri::State<'_, Arc<PlaybackAuthority>>,
) -> Result<ProjectBootstrapSummaryPayload, String> {
let path = FileDialog::new()
.add_filter("Audio", &AUDIO_EXTENSIONS)
Expand All @@ -656,16 +669,18 @@ fn select_local_audio_source(
temp_root: temp_root.to_string_lossy().into_owned(),
source,
};
playback_authority.activate(&summary.project_id, &summary.source)?;
Comment thread
seonghobae marked this conversation as resolved.
store_bootstrap_source(&state, summary.clone());

Ok(summary)
renderer_bootstrap_summary(summary)
}

#[tauri::command]
async fn import_youtube_url(
url: String,
app: tauri::AppHandle<impl Runtime>,
state: tauri::State<'_, AppState>,
playback_authority: tauri::State<'_, Arc<PlaybackAuthority>>,
) -> Result<ProjectBootstrapSummaryPayload, String> {
if !is_supported_youtube_url(&url) {
return Err("Only standard YouTube URLs are supported.".to_string());
Expand Down Expand Up @@ -721,8 +736,9 @@ async fn import_youtube_url(
temp_root: temp_root.to_string_lossy().into_owned(),
source,
};
playback_authority.activate(&summary.project_id, &summary.source)?;
store_bootstrap_source(&state, summary.clone());
return Ok(summary);
return renderer_bootstrap_summary(summary);
}
return Err(youtube_missing_metadata_error(&parsed));
}
Expand Down Expand Up @@ -866,8 +882,15 @@ fn remove_score_pdf(
}

fn main() {
let playback_authority = Arc::new(PlaybackAuthority::default());
let protocol_authority = Arc::clone(&playback_authority);

tauri::Builder::default()
.manage(AppState::default())
.manage(playback_authority)
.register_uri_scheme_protocol(PLAYBACK_SCHEME, move |_context, request| {
protocol_authority.respond(request)
})
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
.invoke_handler(tauri::generate_handler![
select_local_audio_source,
import_youtube_url,
Expand Down
Loading
Loading