Skip to content

feat: griff split in the browser — paginated per-phrase chunks (auto-split #2b: web) - #71

Merged
PhysShell merged 5 commits into
mainfrom
claude/auto-split-web
Jun 18, 2026
Merged

feat: griff split in the browser — paginated per-phrase chunks (auto-split #2b: web)#71
PhysShell merged 5 commits into
mainfrom
claude/auto-split-web

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Second half of auto-split (feature #2), the web side, following #70 (#2a:
core + CLI). The browser capture tool could only emit one chunk per whole
track
; this turns a loaded track into one corpus chunk per phrase — the
browser twin of griff split — and lets you page through, play, and download
each phrase.

What

WASM (web/src/lib.rs)

  • split_chunks_json(track, …capture-meta) -> String — mirrors the CLI's
    griff split. Reuses core's now-public slice::extract_bars +
    split::bar_segments and the existing web build_chunk_meta_record: it cuts
    the selected track at its detected S4 phrase boundaries, slices each segment,
    and measures it on the same detected track. A segment silent on that track
    is a phrase rest and is dropped — never re-measured on another track
    (single-track contract; this is the Codex P2 fix from feat: griff split — one corpus chunk per phrase (auto-split #2a: core + CLI) #70, carried into the
    web split and covered by a web test). Survivors renumber from 0 with inclusive
    bar_range [start, end-1] and inherit the capture form's metadata
    (ids/titles suffixed _p<N> / (phrase <N>)).
  • Returns {error, chunks:[{id, title, bar_lo, bar_hi, notes, chunk}]}, where
    each chunk is a download-ready chunk.json (a serialized corpus::ChunkMeta,
    byte-shape identical to what griff manifest reads) and notes drives playback.

Frontend (web/static/)

  • A ✂ Split into phrases button runs the split over the selected track with
    the capture form's metadata, then a pager (◀ Prev · phrase n/N · Next ▶)
    walks the chunks. Each phrase reuses the shared piano roll and transport synth
    for per-phrase playback, and downloads as its own <id>.chunk.json (one,
    or all). A new file or track selection resets the split.
  • Generation (arrange) is untouched.

Design note

The per-phrase primitives (extract_bars, bar_segments) are shared via core;
the per-phrase assembly is mirrored in CLI and web exactly as
build_chunk_meta_record already mirrors the CLI's build_chunk_meta — the two
fronts read different curate inputs (CLI prompts vs JS string args). Both fronts
now test the track-consistency rule so they stay in step. (decisions.log,
2026-06-18.)

Tests (strict TDD)

  • e040bc1 red → 8511fe4 green for the split seam
    (split_segments_to_json / split_to_json): renumbered sounding phrases with
    inclusive bar_range, dropping phrases silent on the detected track,
    out-of-range errors, and an end-to-end split of the built-in sample. Private
    stubs keep the existing suite running in the red commit.
  • Host suite green: 21 passed.

Validation

  • cargo test (web, host) green; cargo clippy --all-targets -- -D warnings
    clean.
  • wasm32 build green via ./web/build.sh (966 KiB); split_chunks_json
    exported in the generated glue. node --check on app.js and HTML/JS id
    parity verified. web/dist stays gitignored (CI rebuilds on deploy).
  • Note: no live-browser session (no headless JS harness in-repo) — the engine is
    fully unit-tested and the JS is thin glue verified structurally.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TTUbGjzD8ysnVnCJnZJE95


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added in-browser “auto-split” to generate one downloadable chunk.json per detected phrase for a loaded track
    • Introduced phrase pager UI (prev/next) with phrase-specific playback controls
    • Enabled downloading the current phrase chunk or all phrase chunks as a batch
  • Bug Fixes
    • Ensure silent phrases on the selected track are omitted from the split results
  • Tests
    • Added coverage for inclusive bar_range and correct chunk serialization/deserialization
  • Documentation
    • Expanded README with the auto-split capture/export workflow and related APIs
    • Added a decision-log entry describing the behavior

claude added 4 commits June 18, 2026 14:23
Auto-split #2b: the browser capture tool should turn one track into one
corpus chunk per detected phrase (mirroring CLI 'griff split'), so the
page can paginate and play each phrase. Add failing tests for the split
seam — renumbered sounding phrases with inclusive bar_range, dropping
phrases silent on the detected track (single-track contract), out-of-range
errors, and an end-to-end split of the built-in sample. Private stubs keep
the existing suite running.
Adds split_chunks_json — the browser twin of CLI 'griff split'. Reusing
core's now-public extract_bars + bar_segments, it cuts the selected track
at its detected phrase boundaries, slices each segment, and measures it on
the same detected track via the existing build_chunk_meta_record. Segments
silent on that track are dropped (single-track contract); survivors
renumber from 0 with inclusive bar_range and inherit the capture form's
metadata (ids/titles suffixed _p<N>). Each entry carries its notes for
playback and a download-ready pretty chunk.json. Verified on host tests and
the wasm32 build.
Wires split_chunks_json into the capture panel: a Split-into-phrases button
runs the split over the selected track with the capture form's metadata,
then a pager (Prev / phrase n/N / Next) walks the resulting chunks. Each
phrase reuses the shared piano roll and transport synth for per-phrase
playback, and downloads as its own <id>.chunk.json (one, or all). A new
file or track selection resets the split. Generation (arrange) is
untouched.
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d3de67c6-c608-4247-bc6d-6af33b4768ef

📥 Commits

Reviewing files that changed from the base of the PR and between 4206462 and e9edf10.

📒 Files selected for processing (1)
  • web/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/lib.rs

📝 Walkthrough

Walkthrough

Adds a browser-side "auto-split" capture flow: a new split_chunks_json WASM export slices a loaded score into per-sounding-phrase ChunkMeta records using shared core primitives. The web frontend gains a phrase-pager UI (HTML controls, CSS layout, JS state/event wiring) and the feature is documented in web/README.md and docs/decisions.log.md.

Changes

Web auto-split phrase chunking

Layer / File(s) Summary
WASM split helpers and export
web/src/lib.rs
Adds use std::ops::Range and imports extract_bars/bar_segments; implements split_segments_to_json (per-phrase slicing, note-count filter, ChunkMeta building with inclusive bar_range, _p<N> id/title suffixing, JSON envelope), split_to_json (track validation, boundary tick collection, bar-segment delegation), and the exported split_chunks_json WASM entrypoint returning {"error":…,"chunks":[…]}.
Auto-split unit tests
web/src/lib.rs
Extends the test module with synthetic multi-bar score fixtures verifying inclusive bar_range renumbering, silent-phrase dropping, out-of-range track error envelopes, and round-trip ChunkMeta deserialization of the embedded chunk field.
Phrase-pager UI: HTML, CSS, and JS
web/static/index.html, web/static/style.css, web/static/app.js
Adds #capSplit button and hidden #splitView section (pagination, play/stop, per-phrase/all-phrase download) to HTML; adds .splitview, .pager, #splitInfo, and button:disabled rules to CSS; imports split_chunks_json in JS, binds DOM elements, adds splitChunks/splitIdx state, resets on file load and track change, and implements splitIntoPhrases, renderPhrase, stepPhrase, downloadPhrase, downloadAllPhrases, plus all event handlers.
README and decisions log
web/README.md, docs/decisions.log.md
Updates README to document the capture flow and the four exported WASM functions including split_chunks_json; adds a 2026-06-18 decisions-log entry recording the auto-split #2b design choices (shared core primitives, duplicated assembly logic, inclusive bar_range).

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AppJS as app.js
  participant WASM as split_chunks_json (WASM)
  participant Core as griff_core

  User->>AppJS: click "Split into phrases"
  AppJS->>WASM: split_chunks_json(track, id, title, ...)
  WASM->>Core: bar_segments(boundary_ticks, bars)
  Core-->>WASM: Vec<Range<usize>>
  loop each segment
    WASM->>Core: extract_bars(score, range)
    Core-->>WASM: sliced Score
    WASM->>WASM: note_count filter (drop silent phrases)
    WASM->>WASM: build ChunkMeta, set inclusive bar_range
  end
  WASM-->>AppJS: {"error":null,"chunks":[{notes, chunk, id, title}, ...]}
  AppJS->>AppJS: store splitChunks[], renderPhrase(0)
  AppJS-->>User: show splitView pager (phrase 1/N)

  User->>AppJS: click Prev / Next
  AppJS->>AppJS: stepPhrase(±1), renderPhrase(idx)

  User->>AppJS: click Download / Download All
  AppJS->>AppJS: saveBlob(chunk JSON, filename)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PhysShell/griff#70: Implements core+CLI auto-split #2a using the same bar_segments/extract_bars primitives, inclusive bar_range, and _p<N> id/title suffixing that this PR mirrors on the web side.
  • PhysShell/griff#67: Introduced build_chunk_json/detect_boundaries_json in the browser capture pipeline that split_chunks_json extends in this PR.
  • PhysShell/griff#66: Added schema v7 RightsInfo/ChunkMeta expansion with provenance fields that the auto-split implementation uses when building per-phrase chunks.

Poem

🐇 Hop, hop, through the measures I leap,
Each phrase a small bundle, tidy and neat.
split_chunks_json — one chunk per refrain,
Silent bars dropped, none split in vain.
Download them all, or page one by one,
The auto-split rabbit says: "This phrase is done!" 🎵

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main implementation: adding per-phrase chunk splitting to the browser frontend after the core/CLI split feature landed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/auto-split-web

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/src/lib.rs`:
- Around line 688-689: The functions note_count() and push_notes() currently
only examine the first voice in a track (voices.first()), causing tracks with
notes in secondary voices to be incorrectly treated as silent and dropped during
phrase splitting. Create split-specific helper functions that iterate through
all voices using track.voices.iter() to check for notes across all voices, not
just the primary voice. Apply these new helpers where note_count() is used in
the track matching logic around line 688, and add a regression test that
verifies a track with an empty voice 0 and note-bearing voice 1 is correctly
retained and processed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 14698387-cc56-4e8e-a3f6-c07be419e5e8

📥 Commits

Reviewing files that changed from the base of the PR and between 7ab6ec1 and 4206462.

📒 Files selected for processing (6)
  • docs/decisions.log.md
  • web/README.md
  • web/src/lib.rs
  • web/static/app.js
  • web/static/index.html
  • web/static/style.css

Comment thread web/src/lib.rs
Characterization test for the existing behavior CodeRabbit flagged on #71:
the split's note_count/push_notes read voice 0 because the whole analysis
stack — boundary::detect_phrase_boundaries, structure/gesture/complexity,
novelty/closure/complement, MIDI export — reads voices.first(). Phrases are
cut from voice 0, so a track sounding only in a secondary voice is
unmeasurable and is dropped, matching the CLI's primary_voice_note_count
contract. Scanning all voices would cut on voice 0 yet keep/measure on
voice 1 and reintroduce the measurement-less chunks PR #70's Codex P2
removed.

Copy link
Copy Markdown
Owner Author

@codex review

(CodeRabbit is clean — its one comment was withdrawn as the intended voice-0 convention. Requesting a Codex pass before merge, as on the #2a PR #70.)


Generated by Claude Code

@PhysShell
PhysShell merged commit c19ca6c into main Jun 18, 2026
1 check passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9edf10dfa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/static/style.css

/* ---- auto-split phrase pager (#2b) ---- */
.splitview {
display: grid;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the split pager hidden until a split exists

When the page first loads, or when resetSplit() sets splitView.hidden = true after a track/file change, this author display rule can override the browser's default [hidden] styling and still render the empty pager/download controls. Make the grid display conditional on the element not being hidden (for example .splitview:not([hidden])) or add an explicit [hidden] { display: none !important; } rule so the split UI only appears after a successful split.

Useful? React with 👍 / 👎.

This was referenced Jun 19, 2026
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.

2 participants